diff options
Diffstat (limited to 'core/java')
29 files changed, 654 insertions, 258 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4880db1..81b1583 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -254,18 +254,21 @@ public final class ActivityThread { } } + static final class AcquiringProviderRecord { + IActivityManager.ContentProviderHolder holder; + boolean acquiring = true; + int requests = 1; + } + // The lock of mProviderMap protects the following variables. - final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap - = new ArrayMap<ProviderKey, ProviderClientRecord>(); - final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap - = new ArrayMap<IBinder, ProviderRefCount>(); - final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders - = new ArrayMap<IBinder, ProviderClientRecord>(); - final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName - = new ArrayMap<ComponentName, ProviderClientRecord>(); + final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<>(); + final ArrayMap<ProviderKey, AcquiringProviderRecord> mAcquiringProviderMap = new ArrayMap<>(); + final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap = new ArrayMap<>(); + final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders = new ArrayMap<>(); + final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName = new ArrayMap<>(); final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners - = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); + = new ArrayMap<>(); final GcIdler mGcIdler = new GcIdler(); boolean mGcIdlerScheduled = false; @@ -345,7 +348,7 @@ public final class ActivityThread { } } - final class ProviderClientRecord { + static final class ProviderClientRecord { final String[] mNames; final IContentProvider mProvider; final ContentProvider mLocalProvider; @@ -4693,22 +4696,57 @@ public final class ActivityThread { public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { - final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); + final ProviderKey key = new ProviderKey(auth, userId); + final IContentProvider provider = acquireExistingProvider(c, key, stable); if (provider != null) { return provider; } + AcquiringProviderRecord r; + boolean first = false; + synchronized (mAcquiringProviderMap) { + r = mAcquiringProviderMap.get(key); + if (r == null) { + r = new AcquiringProviderRecord(); + mAcquiringProviderMap.put(key, r); + first = true; + } else { + r.requests++; + } + } - // There is a possible race here. Another thread may try to acquire - // the same provider at the same time. When this happens, we want to ensure - // that the first one wins. - // Note that we cannot hold the lock while acquiring and installing the - // provider since it might take a long time to run and it could also potentially - // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; - try { - holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), auth, userId, stable); - } catch (RemoteException ex) { + if (first) { + // Multiple threads may try to acquire the same provider at the same time. + // When this happens, we only let the first one really gets provider. + // Other threads just wait for its result. + // Note that we cannot hold the lock while acquiring and installing the + // provider since it might take a long time to run and it could also potentially + // be re-entrant in the case where the provider is in the same process. + try { + holder = ActivityManagerNative.getDefault().getContentProvider( + getApplicationThread(), auth, userId, stable); + } catch (RemoteException ex) { + } + synchronized (r) { + r.holder = holder; + r.acquiring = false; + r.notifyAll(); + } + } else { + synchronized (r) { + while (r.acquiring) { + try { + r.wait(); + } catch (InterruptedException e) { + } + } + holder = r.holder; + } + } + synchronized (mAcquiringProviderMap) { + if (--r.requests == 0) { + mAcquiringProviderMap.remove(key); + } } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); @@ -4792,8 +4830,12 @@ public final class ActivityThread { public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) { + return acquireExistingProvider(c, new ProviderKey(auth, userId), stable); + } + + final IContentProvider acquireExistingProvider( + Context c, ProviderKey key, boolean stable) { synchronized (mProviderMap) { - final ProviderKey key = new ProviderKey(auth, userId); final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; @@ -4804,7 +4846,7 @@ public final class ActivityThread { if (!jBinder.isBinderAlive()) { // The hosting process of the provider has died; we can't // use this one. - Log.i(TAG, "Acquiring provider " + auth + " for user " + userId + Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId + ": existing object's process dead"); handleUnstableProviderDiedLocked(jBinder, true); return null; @@ -5126,18 +5168,12 @@ public final class ActivityThread { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } - // We need to transfer our new reference to the existing - // ref count, releasing the old one... but only if - // release is needed (that is, it is not running in the - // system process). + // The provider has already been installed, so we need + // to increase reference count to the existing one, but + // only if release is needed (that is, it is not running + // in the system process or local to the process). if (!noReleaseNeeded) { incProviderRefLocked(prc, stable); - try { - ActivityManagerNative.getDefault().removeContentProvider( - holder.connection, stable); - } catch (RemoteException e) { - //do nothing content provider object is dead any way - } } } else { ProviderClientRecord client = installProviderAuthoritiesLocked( diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index a0f40f6..b3aa6be 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -99,8 +99,8 @@ import android.os.Vibrator; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; -import android.service.fingerprint.FingerprintManager; -import android.service.fingerprint.IFingerprintService; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintService; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.telecom.TelecomManager; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index be26eac..edb768d 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; @@ -206,6 +207,23 @@ public final class BluetoothAdapter { "android.bluetooth.adapter.action.REQUEST_ENABLE"; /** + * Activity Action: Show a system activity that allows user to enable BLE scans even when + * Bluetooth is turned off.<p> + * + * Notification of result of this activity is posted using + * {@link android.app.Activity#onActivityResult}. The <code>resultCode</code> will be + * {@link android.app.Activity#RESULT_OK} if BLE scan always available setting is turned on or + * {@link android.app.Activity#RESULT_CANCELED} if the user has rejected the request or an + * error occurred. + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = + "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; + + /** * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter * has changed. * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link @@ -916,6 +934,22 @@ public final class BluetoothAdapter { } /** + * Returns {@code true} if BLE scan is always available, {@code false} otherwise. <p> + * + * If this returns {@code true}, application can issue {@link BluetoothLeScanner#startScan} and + * fetch scan results even when Bluetooth is turned off.<p> + * + * To change this setting, use {@link #ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE}. + * + * @hide + */ + @SystemApi + public boolean isBleScanAlwaysAvailable() { + // TODO: implement after Settings UI change. + return false; + } + + /** * Returns whether peripheral mode is supported. * * @hide diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 7a99a79..514802e9 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1041,6 +1041,18 @@ public class Intent implements Parcelable, Cloneable { */ public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED"; /** + * Activity action: Activate the current SIM card. If SIM cards do not require activation, + * sending this intent is a no-op. + * <p>Input: No data should be specified. get*Extra may have an optional + * {@link #EXTRA_SIM_ACTIVATION_RESPONSE} field containing a PendingIntent through which to + * send the activation result. + * <p>Output: nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SIM_ACTIVATION_REQUEST = + "android.intent.action.SIM_ACTIVATION_REQUEST"; + /** * Activity Action: Send a message to someone specified by the data. * <p>Input: {@link #getData} is URI describing the target. * <p>Output: nothing. @@ -3620,6 +3632,15 @@ public class Intent implements Parcelable, Cloneable { /** {@hide} */ public static final String EXTRA_REASON = "android.intent.extra.REASON"; + /** + * Optional {@link android.app.PendingIntent} extra used to deliver the result of the SIM + * activation request. + * TODO: Add information about the structure and response data used with the pending intent. + * @hide + */ + public static final String EXTRA_SIM_ACTIVATION_RESPONSE = + "android.intent.extra.SIM_ACTIVATION_RESPONSE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 6d79626..342ee38 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -20,6 +20,9 @@ import android.annotation.ArrayRes; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; +import java.util.Objects; + /** * Applications can expose restrictions for a restricted user on a * multiuser device. The administrator can configure these restrictions that will then be @@ -33,19 +36,19 @@ import android.os.Parcelable; public class RestrictionEntry implements Parcelable { /** - * A type of restriction. Use this type for information that needs to be transferred across - * but shouldn't be presented to the user in the UI. Stores a single String value. + * Hidden restriction type. Use this type for information that needs to be transferred + * across but shouldn't be presented to the user in the UI. Stores a single String value. */ public static final int TYPE_NULL = 0; /** - * A type of restriction. Use this for storing a boolean value, typically presented as + * Restriction of type "bool". Use this for storing a boolean value, typically presented as * a checkbox in the UI. */ public static final int TYPE_BOOLEAN = 1; /** - * A type of restriction. Use this for storing a string value, typically presented as + * Restriction of type "choice". Use this for storing a string value, typically presented as * a single-select list. Call {@link #setChoiceEntries(String[])} and * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user * and the corresponding values, respectively. @@ -53,7 +56,7 @@ public class RestrictionEntry implements Parcelable { public static final int TYPE_CHOICE = 2; /** - * A type of restriction. Use this for storing a string value, typically presented as + * Internal restriction type. Use this for storing a string value, typically presented as * a single-select list. Call {@link #setChoiceEntries(String[])} and * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user * and the corresponding values, respectively. @@ -64,8 +67,8 @@ public class RestrictionEntry implements Parcelable { public static final int TYPE_CHOICE_LEVEL = 3; /** - * A type of restriction. Use this for presenting a multi-select list where more than one - * entry can be selected, such as for choosing specific titles to white-list. + * Restriction of type "multi-select". Use this for presenting a multi-select list where more + * than one entry can be selected, such as for choosing specific titles to white-list. * Call {@link #setChoiceEntries(String[])} and * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user * and the corresponding values, respectively. @@ -75,18 +78,30 @@ public class RestrictionEntry implements Parcelable { public static final int TYPE_MULTI_SELECT = 4; /** - * A type of restriction. Use this for storing an integer value. The range of values + * Restriction of type "integer". Use this for storing an integer value. The range of values * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. */ public static final int TYPE_INTEGER = 5; /** - * A type of restriction. Use this for storing a string value. + * Restriction of type "string". Use this for storing a string value. * @see #setSelectedString * @see #getSelectedString */ public static final int TYPE_STRING = 6; + /** + * Restriction of type "bundle". Use this for storing {@link android.os.Bundle bundles} of + * restrictions + */ + public static final int TYPE_BUNDLE = 7; + + /** + * Restriction of type "bundle_array". Use this for storing arrays of + * {@link android.os.Bundle bundles} of restrictions + */ + public static final int TYPE_BUNDLE_ARRAY = 8; + /** The type of restriction. */ private int mType; @@ -100,13 +115,13 @@ public class RestrictionEntry implements Parcelable { private String mDescription; /** The user-visible set of choices used for single-select and multi-select lists. */ - private String [] mChoiceEntries; + private String[] mChoiceEntries; /** The values corresponding to the user-visible choices. The value(s) of this entry will * one or more of these, returned by {@link #getAllSelectedStrings()} and * {@link #getSelectedString()}. */ - private String [] mChoiceValues; + private String[] mChoiceValues; /* The chosen value, whose content depends on the type of the restriction. */ private String mCurrentValue; @@ -115,6 +130,12 @@ public class RestrictionEntry implements Parcelable { private String[] mCurrentValues; /** + * List of nested restrictions. Used by {@link #TYPE_BUNDLE bundle} and + * {@link #TYPE_BUNDLE_ARRAY bundle_array} restrictions. + */ + private RestrictionEntry[] mRestrictions; + + /** * Constructor for specifying the type and key, with no initial value; * * @param type the restriction type. @@ -170,6 +191,35 @@ public class RestrictionEntry implements Parcelable { } /** + * Constructor for {@link #TYPE_BUNDLE}/{@link #TYPE_BUNDLE_ARRAY} type. + * @param key the unique key for this restriction + * @param restrictionEntries array of nested restriction entries. If the entry, being created + * represents a {@link #TYPE_BUNDLE_ARRAY bundle-array}, {@code restrictionEntries} array may + * only contain elements of type {@link #TYPE_BUNDLE bundle}. + * @param isBundleArray true if this restriction represents + * {@link #TYPE_BUNDLE_ARRAY bundle-array} type, otherwise the type will be set to + * {@link #TYPE_BUNDLE bundle}. + */ + public RestrictionEntry(String key, RestrictionEntry[] restrictionEntries, + boolean isBundleArray) { + mKey = key; + if (isBundleArray) { + mType = TYPE_BUNDLE_ARRAY; + if (restrictionEntries != null) { + for (RestrictionEntry restriction : restrictionEntries) { + if (restriction.getType() != TYPE_BUNDLE) { + throw new IllegalArgumentException("bundle_array restriction can only have " + + "nested restriction entries of type bundle"); + } + } + } + } else { + mType = TYPE_BUNDLE; + } + setRestrictions(restrictionEntries); + } + + /** * Sets the type for this restriction. * @param type the type for this restriction. */ @@ -283,6 +333,22 @@ public class RestrictionEntry implements Parcelable { } /** + * Returns array of possible restriction entries that this entry may contain. + */ + public RestrictionEntry[] getRestrictions() { + return mRestrictions; + } + + /** + * Sets an array of possible restriction entries, that this entry may contain. + * <p>This method is only relevant for types {@link #TYPE_BUNDLE} and + * {@link #TYPE_BUNDLE_ARRAY} + */ + public void setRestrictions(RestrictionEntry[] restrictions) { + mRestrictions = restrictions; + } + + /** * Returns the list of possible string values set earlier. * @return the list of possible values. */ @@ -362,27 +428,30 @@ public class RestrictionEntry implements Parcelable { this.mTitle = title; } - private boolean equalArrays(String[] one, String[] other) { - if (one.length != other.length) return false; - for (int i = 0; i < one.length; i++) { - if (!one[i].equals(other[i])) return false; - } - return true; - } - @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof RestrictionEntry)) return false; final RestrictionEntry other = (RestrictionEntry) o; - // Make sure that either currentValue matches or currentValues matches. - return mType == other.mType && mKey.equals(other.mKey) - && - ((mCurrentValues == null && other.mCurrentValues == null - && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue)) - || - (mCurrentValue == null && other.mCurrentValue == null - && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues))); + if (mType != other.mType || mKey.equals(other.mKey)) { + return false; + } + if (mCurrentValues == null && other.mCurrentValues == null + && mRestrictions == null && other.mRestrictions == null + && Objects.equals(mCurrentValue, other.mCurrentValue)) { + return true; + } + if (mCurrentValue == null && other.mCurrentValue == null + && mRestrictions == null && other.mRestrictions == null + && Arrays.equals(mCurrentValues, other.mCurrentValues)) { + return true; + } + if (mCurrentValue == null && other.mCurrentValue == null + && mCurrentValue == null && other.mCurrentValue == null + && Arrays.equals(mRestrictions, other.mRestrictions)) { + return true; + } + return false; } @Override @@ -397,28 +466,28 @@ public class RestrictionEntry implements Parcelable { result = 31 * result + value.hashCode(); } } + } else if (mRestrictions != null) { + result = 31 * result + Arrays.hashCode(mRestrictions); } return result; } - private String[] readArray(Parcel in) { - int count = in.readInt(); - String[] values = new String[count]; - for (int i = 0; i < count; i++) { - values[i] = in.readString(); - } - return values; - } - public RestrictionEntry(Parcel in) { mType = in.readInt(); mKey = in.readString(); mTitle = in.readString(); mDescription = in.readString(); - mChoiceEntries = readArray(in); - mChoiceValues = readArray(in); + mChoiceEntries = in.readStringArray(); + mChoiceValues = in.readStringArray(); mCurrentValue = in.readString(); - mCurrentValues = readArray(in); + mCurrentValues = in.readStringArray(); + Parcelable[] parcelables = in.readParcelableArray(null); + if (parcelables != null) { + mRestrictions = new RestrictionEntry[parcelables.length]; + for (int i = 0; i < parcelables.length; i++) { + mRestrictions[i] = (RestrictionEntry) parcelables[i]; + } + } } @Override @@ -426,27 +495,17 @@ public class RestrictionEntry implements Parcelable { return 0; } - private void writeArray(Parcel dest, String[] values) { - if (values == null) { - dest.writeInt(0); - } else { - dest.writeInt(values.length); - for (int i = 0; i < values.length; i++) { - dest.writeString(values[i]); - } - } - } - @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeString(mKey); dest.writeString(mTitle); dest.writeString(mDescription); - writeArray(dest, mChoiceEntries); - writeArray(dest, mChoiceValues); + dest.writeStringArray(mChoiceEntries); + dest.writeStringArray(mChoiceValues); dest.writeString(mCurrentValue); - writeArray(dest, mCurrentValues); + dest.writeStringArray(mCurrentValues); + dest.writeParcelableArray(mRestrictions, 0); } public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() { @@ -461,6 +520,16 @@ public class RestrictionEntry implements Parcelable { @Override public String toString() { - return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}"; + return "RestrictionEntry{" + + "mType=" + mType + + ", mKey='" + mKey + '\'' + + ", mTitle='" + mTitle + '\'' + + ", mDescription='" + mDescription + '\'' + + ", mChoiceEntries=" + Arrays.toString(mChoiceEntries) + + ", mChoiceValues=" + Arrays.toString(mChoiceValues) + + ", mCurrentValue='" + mCurrentValue + '\'' + + ", mCurrentValues=" + Arrays.toString(mCurrentValues) + + ", mRestrictions=" + Arrays.toString(mRestrictions) + + '}'; } } diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java index 21a6a0d..1fac06e 100644 --- a/core/java/android/content/RestrictionsManager.java +++ b/core/java/android/content/RestrictionsManager.java @@ -32,12 +32,14 @@ import android.util.Log; import android.util.Xml; import com.android.internal.R; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -71,12 +73,15 @@ import java.util.List; * android:key="string" * android:title="string resource" * android:restrictionType=["bool" | "string" | "integer" - * | "choice" | "multi-select" | "hidden"] + * | "choice" | "multi-select" | "hidden" + * | "bundle" | "bundle_array"] * android:description="string resource" * android:entries="string-array resource" * android:entryValues="string-array resource" - * android:defaultValue="reference" - * /> + * android:defaultValue="reference" > + * <restriction ... /> + * ... + * </restriction> * <restriction ... /> * ... * </restrictions> @@ -97,6 +102,9 @@ import java.util.List; * administrator controlling the values, if the title is not sufficient.</li> * </ul> * <p> + * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested + * restriction elements. + * <p> * In your manifest's <code>application</code> section, add the meta-data tag to point to * the restrictions XML file as shown below: * <pre> @@ -537,9 +545,7 @@ public class RestrictionsManager { XmlResourceParser xml = appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS); - List<RestrictionEntry> restrictions = loadManifestRestrictions(packageName, xml); - - return restrictions; + return loadManifestRestrictions(packageName, xml); } private List<RestrictionEntry> loadManifestRestrictions(String packageName, @@ -550,23 +556,16 @@ public class RestrictionsManager { } catch (NameNotFoundException nnfe) { return null; } - ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>(); + ArrayList<RestrictionEntry> restrictions = new ArrayList<>(); RestrictionEntry restriction; try { int tagType = xml.next(); while (tagType != XmlPullParser.END_DOCUMENT) { if (tagType == XmlPullParser.START_TAG) { - if (xml.getName().equals(TAG_RESTRICTION)) { - AttributeSet attrSet = Xml.asAttributeSet(xml); - if (attrSet != null) { - TypedArray a = appContext.obtainStyledAttributes(attrSet, - com.android.internal.R.styleable.RestrictionEntry); - restriction = loadRestriction(appContext, a); - if (restriction != null) { - restrictions.add(restriction); - } - } + restriction = loadRestrictionElement(appContext, xml); + if (restriction != null) { + restrictions.add(restriction); } } tagType = xml.next(); @@ -582,7 +581,21 @@ public class RestrictionsManager { return restrictions; } - private RestrictionEntry loadRestriction(Context appContext, TypedArray a) { + private RestrictionEntry loadRestrictionElement(Context appContext, XmlResourceParser xml) + throws IOException, XmlPullParserException { + if (xml.getName().equals(TAG_RESTRICTION)) { + AttributeSet attrSet = Xml.asAttributeSet(xml); + if (attrSet != null) { + TypedArray a = appContext.obtainStyledAttributes(attrSet, + com.android.internal.R.styleable.RestrictionEntry); + return loadRestriction(appContext, a, xml); + } + } + return null; + } + + private RestrictionEntry loadRestriction(Context appContext, TypedArray a, XmlResourceParser xml) + throws IOException, XmlPullParserException { String key = a.getString(R.styleable.RestrictionEntry_key); int restrictionType = a.getInt( R.styleable.RestrictionEntry_restrictionType, -1); @@ -633,9 +646,90 @@ public class RestrictionsManager { restriction.setSelectedState( a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false)); break; + case RestrictionEntry.TYPE_BUNDLE: + case RestrictionEntry.TYPE_BUNDLE_ARRAY: + final int outerDepth = xml.getDepth(); + List<RestrictionEntry> restrictionEntries = new ArrayList<>(); + while (XmlUtils.nextElementWithin(xml, outerDepth)) { + RestrictionEntry childEntry = loadRestrictionElement(appContext, xml); + if (childEntry == null) { + Log.w(TAG, "Child entry cannot be loaded for bundle restriction " + key); + } else { + restrictionEntries.add(childEntry); + if (restrictionType == RestrictionEntry.TYPE_BUNDLE_ARRAY + && childEntry.getType() != RestrictionEntry.TYPE_BUNDLE) { + Log.w(TAG, "bundle_array " + key + + " can only contain entries of type bundle"); + } + } + } + restriction.setRestrictions(restrictionEntries.toArray(new RestrictionEntry[ + restrictionEntries.size()])); + break; default: Log.w(TAG, "Unknown restriction type " + restrictionType); } return restriction; } + + /** + * Converts a list of restrictions to the corresponding bundle, using the following mapping: + * <table> + * <tr><th>RestrictionEntry</th><th>Bundle</th></tr> + * <tr><td>{@link RestrictionEntry#TYPE_BOOLEAN}</td><td>{@link Bundle#putBoolean}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_CHOICE}, {@link RestrictionEntry#TYPE_CHOICE}</td> + * <td>{@link Bundle#putStringArray}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_INTEGER}</td><td>{@link Bundle#putInt}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_STRING}</td><td>{@link Bundle#putString}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE}</td><td>{@link Bundle#putBundle}</td></tr> + * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE_ARRAY}</td> + * <td>{@link Bundle#putParcelableArray}</td></tr> + * </table> + * @param entries list of restrictions + */ + public static Bundle convertRestrictionsToBundle(List<RestrictionEntry> entries) { + final Bundle bundle = new Bundle(); + for (RestrictionEntry entry : entries) { + addRestrictionToBundle(bundle, entry); + } + return bundle; + } + + private static Bundle addRestrictionToBundle(Bundle bundle, RestrictionEntry entry) { + switch (entry.getType()) { + case RestrictionEntry.TYPE_BOOLEAN: + bundle.putBoolean(entry.getKey(), entry.getSelectedState()); + break; + case RestrictionEntry.TYPE_CHOICE: + case RestrictionEntry.TYPE_CHOICE_LEVEL: + case RestrictionEntry.TYPE_MULTI_SELECT: + bundle.putStringArray(entry.getKey(), entry.getAllSelectedStrings()); + break; + case RestrictionEntry.TYPE_INTEGER: + bundle.putInt(entry.getKey(), entry.getIntValue()); + break; + case RestrictionEntry.TYPE_STRING: + case RestrictionEntry.TYPE_NULL: + bundle.putString(entry.getKey(), entry.getSelectedString()); + break; + case RestrictionEntry.TYPE_BUNDLE: + RestrictionEntry[] restrictions = entry.getRestrictions(); + Bundle childBundle = convertRestrictionsToBundle(Arrays.asList(restrictions)); + bundle.putBundle(entry.getKey(), childBundle); + break; + case RestrictionEntry.TYPE_BUNDLE_ARRAY: + restrictions = entry.getRestrictions(); + Bundle[] bundleArray = new Bundle[restrictions.length]; + for (int i = 0; i < restrictions.length; i++) { + bundleArray[i] = addRestrictionToBundle(new Bundle(), restrictions[i]); + } + bundle.putParcelableArray(entry.getKey(), bundleArray); + break; + default: + throw new IllegalArgumentException( + "Unsupported restrictionEntry type: " + entry.getType()); + } + return bundle; + } + } diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index d5dfaf6..9bc2f46 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -75,4 +75,11 @@ interface ICameraService out BinderHolder device); int setTorchMode(String CameraId, boolean enabled, IBinder clientBinder); + + /** + * Notify the camera service of a system event. Should only be called from system_server. + * + * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission. + */ + oneway void notifySystemEvent(int eventId, int arg0); } diff --git a/core/java/android/service/fingerprint/Fingerprint.aidl b/core/java/android/hardware/fingerprint/Fingerprint.aidl index c9fd989..4743354 100644 --- a/core/java/android/service/fingerprint/Fingerprint.aidl +++ b/core/java/android/hardware/fingerprint/Fingerprint.aidl @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; // @hide parcelable Fingerprint; diff --git a/core/java/android/service/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java index 37552eb..c307634 100644 --- a/core/java/android/service/fingerprint/Fingerprint.java +++ b/core/java/android/hardware/fingerprint/Fingerprint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.os.Parcel; import android.os.Parcelable; diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index bb90e40..e3572a2 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.app.ActivityManagerNative; import android.content.ContentResolver; @@ -28,7 +28,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; -import android.service.fingerprint.FingerprintManager.EnrollmentCallback; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; import android.util.Log; import android.util.Slog; diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/hardware/fingerprint/FingerprintUtils.java index 62acbb9..ae3d4a4 100644 --- a/core/java/android/service/fingerprint/FingerprintUtils.java +++ b/core/java/android/hardware/fingerprint/FingerprintUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.content.ContentResolver; import android.provider.Settings; diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index e5d3ad4..c5a45e2 100644 --- a/core/java/android/service/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.os.Bundle; -import android.service.fingerprint.IFingerprintServiceReceiver; -import android.service.fingerprint.Fingerprint; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.hardware.fingerprint.Fingerprint; import java.util.List; /** diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index f025064..e82395f 100644 --- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.fingerprint; +package android.hardware.fingerprint; import android.os.Bundle; import android.os.UserHandle; diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index bf3d9aa..f305b2a 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -366,7 +366,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public String toSafeString() { String scheme = getScheme(); String ssp = getSchemeSpecificPart(); - String authority = null; if (scheme != null) { if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") @@ -385,9 +384,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } return builder.toString(); - } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) { - ssp = null; - authority = "//" + getAuthority() + "/..."; + } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https") + || scheme.equalsIgnoreCase("ftp")) { + ssp = "//" + ((getHost() != null) ? getHost() : "") + + ((getPort() != -1) ? (":" + getPort()) : "") + + "/..."; } } // Not a sensitive scheme, but let's still be conservative about @@ -401,9 +402,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { if (ssp != null) { builder.append(ssp); } - if (authority != null) { - builder.append(authority); - } return builder.toString(); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 8b3ecae..508fdee 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -4415,7 +4415,7 @@ public abstract class BatteryStats implements Parcelable { if (!checkin) { pw.println(header); } - String[] lineArgs = new String[4]; + String[] lineArgs = new String[5]; for (int i=0; i<count; i++) { long duration = steps.getDurationAt(i); int level = steps.getLevelAt(i); @@ -4430,7 +4430,7 @@ public abstract class BatteryStats implements Parcelable { case Display.STATE_ON: lineArgs[2] = "s+"; break; case Display.STATE_DOZE: lineArgs[2] = "sd"; break; case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break; - default: lineArgs[1] = "?"; break; + default: lineArgs[2] = "?"; break; } } else { lineArgs[2] = ""; @@ -4441,9 +4441,9 @@ public abstract class BatteryStats implements Parcelable { lineArgs[3] = ""; } if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) { - lineArgs[3] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-"; + lineArgs[4] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-"; } else { - lineArgs[3] = ""; + lineArgs[4] = ""; } dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs); } else { @@ -4459,7 +4459,7 @@ public abstract class BatteryStats implements Parcelable { case Display.STATE_ON: pw.print("screen-on"); break; case Display.STATE_DOZE: pw.print("screen-doze"); break; case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break; - default: lineArgs[1] = "screen-?"; break; + default: pw.print("screen-?"); break; } haveModes = true; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 6517f35..7d57233 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -454,7 +454,6 @@ public class CallLog { long start, int duration, Long dataUsage, boolean addForAllUsers) { final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; - boolean isHidden = false; TelecomManager tm = null; try { @@ -469,12 +468,6 @@ public class CallLog { if (address != null) { accountAddress = address.getSchemeSpecificPart(); } - } else { - // We could not find the account through telecom. For call log entries that - // are added with a phone account which is not registered, we automatically - // mark them as hidden. They are unhidden once the account is registered. - Log.i(LOG_TAG, "Marking call log entry as hidden."); - isHidden = true; } } @@ -520,7 +513,6 @@ public class CallLog { values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString); values.put(PHONE_ACCOUNT_ID, accountId); values.put(PHONE_ACCOUNT_ADDRESS, accountAddress); - values.put(PHONE_ACCOUNT_HIDDEN, Integer.valueOf(isHidden ? 1 : 0)); values.put(NEW, Integer.valueOf(1)); if (callType == MISSED_TYPE) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8e5d245..f79ef35 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6375,6 +6375,14 @@ public final class Settings { "wifi_scan_always_enabled"; /** + * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for + * connectivity. + * @hide + */ + public static final String BLE_SCAN_ALWAYS_AVAILABLE = + "ble_scan_always_enabled"; + + /** * Used to save the Wifi_ON state prior to tethering. * This state will be checked to restore Wifi after * the user turns off tethering. diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index c2ebbc6..e94a312 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -181,7 +181,7 @@ public final class KeymasterDefs { public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6; public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7; public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8; - public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9; + public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9; public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10; public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11; public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12; @@ -237,8 +237,8 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size"); sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode"); sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode"); - sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG_LENGTH, - "Unsupported authentication tag length"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH, + "Unsupported MAC or authentication tag length"); sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode"); sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode"); sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest"); @@ -261,6 +261,7 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag"); sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag"); sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 7d2e1ef..239b386 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -79,7 +79,8 @@ public class DynamicLayout extends Layout boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, - spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth); + spacingmult, spacingadd, includepad, StaticLayout.BREAK_STRATEGY_SIMPLE, + ellipsize, ellipsizedWidth); } /** @@ -95,7 +96,7 @@ public class DynamicLayout extends Layout TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, - boolean includepad, + boolean includepad, int breakStrategy, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { super((ellipsize == null) ? display @@ -120,6 +121,7 @@ public class DynamicLayout extends Layout mObjects = new PackedObjectVector<Directions>(1); mIncludePad = includepad; + mBreakStrategy = breakStrategy; /* * This is annoying, but we can't refer to the layout until @@ -279,10 +281,9 @@ public class DynamicLayout extends Layout sBuilder = null; } - // TODO: make sure reflowed is properly initialized if (reflowed == null) { reflowed = new StaticLayout(null); - b = StaticLayout.Builder.obtain(); + b = StaticLayout.Builder.obtain(text, where, where + after, getWidth()); } b.setText(text, where, where + after) @@ -292,7 +293,8 @@ public class DynamicLayout extends Layout .setSpacingMult(getSpacingMultiplier()) .setSpacingAdd(getSpacingAdd()) .setEllipsizedWidth(mEllipsizedWidth) - .setEllipsize(mEllipsizeAt); + .setEllipsize(mEllipsizeAt) + .setBreakStrategy(mBreakStrategy); reflowed.generate(b, false, true); int n = reflowed.getLineCount(); @@ -717,6 +719,7 @@ public class DynamicLayout extends Layout private boolean mEllipsize; private int mEllipsizedWidth; private TextUtils.TruncateAt mEllipsizeAt; + private int mBreakStrategy; private PackedIntVector mInts; private PackedObjectVector<Directions> mObjects; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 22abb18..16ae5e2 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -16,6 +16,7 @@ package android.text; +import android.annotation.IntDef; import android.emoji.EmojiFactory; import android.graphics.Canvas; import android.graphics.Paint; @@ -33,6 +34,8 @@ import android.text.style.TabStopSpan; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -43,6 +46,31 @@ import java.util.Arrays; * For text that will not change, use a {@link StaticLayout}. */ public abstract class Layout { + /** @hide */ + @IntDef({BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED}) + @Retention(RetentionPolicy.SOURCE) + public @interface BreakStrategy {} + + /** + * Value for break strategy indicating simple line breaking. Automatic hyphens are not added + * (though soft hyphens are respected), and modifying text generally doesn't affect the layout + * before it (which yields a more consistent user experience when editing), but layout may not + * be the highest quality. + */ + public static final int BREAK_STRATEGY_SIMPLE = 0; + + /** + * Value for break strategy indicating high quality line breaking, including automatic + * hyphenation and doing whole-paragraph optimization of line breaks. + */ + public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; + + /** + * Value for break strategy indicating balanced line breaking. The breaks are chosen to + * make all lines as close to the same length as possible, including automatic hyphenation. + */ + public static final int BREAK_STRATEGY_BALANCED = 2; + private static final ParagraphStyle[] NO_PARA_SPANS = ArrayUtils.emptyArray(ParagraphStyle.class); diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 4174df0..2bcb352 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -23,6 +23,7 @@ import android.text.style.LineHeightSpan; import android.text.style.MetricAffectingSpan; import android.text.style.TabStopSpan; import android.util.Log; +import android.util.Pools.SynchronizedPool; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -56,28 +57,23 @@ public class StaticLayout extends Layout { mNativePtr = nNewBuilder(); } - static Builder obtain() { - Builder b = null; - synchronized (sLock) { - for (int i = 0; i < sCached.length; i++) { - if (sCached[i] != null) { - b = sCached[i]; - sCached[i] = null; - break; - } - } - } + public static Builder obtain(CharSequence source, int start, int end, int width) { + Builder b = sPool.acquire(); if (b == null) { b = new Builder(); } // set default initial values - b.mWidth = 0; + b.mText = source; + b.mStart = start; + b.mEnd = end; + b.mWidth = width; + b.mAlignment = Alignment.ALIGN_NORMAL; b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR; b.mSpacingMult = 1.0f; b.mSpacingAdd = 0.0f; b.mIncludePad = true; - b.mEllipsizedWidth = 0; + b.mEllipsizedWidth = width; b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; @@ -85,18 +81,11 @@ public class StaticLayout extends Layout { return b; } - static void recycle(Builder b) { + private static void recycle(Builder b) { b.mPaint = null; b.mText = null; MeasuredText.recycle(b.mMeasuredText); - synchronized (sLock) { - for (int i = 0; i < sCached.length; i++) { - if (sCached[i] == null) { - sCached[i] = b; - break; - } - } - } + sPool.release(b); } // release any expensive state @@ -129,6 +118,11 @@ public class StaticLayout extends Layout { return this; } + public Builder setAlignment(Alignment alignment) { + mAlignment = alignment; + return this; + } + public Builder setTextDir(TextDirectionHeuristic textDir) { mTextDir = textDir; return this; @@ -166,6 +160,11 @@ public class StaticLayout extends Layout { return this; } + public Builder setBreakStrategy(@BreakStrategy int breakStrategy) { + mBreakStrategy = breakStrategy; + return this; + } + /** * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. @@ -207,10 +206,8 @@ public class StaticLayout extends Layout { } public StaticLayout build() { - // TODO: can optimize based on whether ellipsis is needed - StaticLayout result = new StaticLayout(mText); - result.generate(this, this.mIncludePad, this.mIncludePad); - recycle(this); + StaticLayout result = new StaticLayout(this); + Builder.recycle(this); return result; } @@ -230,6 +227,7 @@ public class StaticLayout extends Layout { int mEnd; TextPaint mPaint; int mWidth; + Alignment mAlignment; TextDirectionHeuristic mTextDir; float mSpacingMult; float mSpacingAdd; @@ -237,6 +235,7 @@ public class StaticLayout extends Layout { int mEllipsizedWidth; TextUtils.TruncateAt mEllipsize; int mMaxLines; + int mBreakStrategy; Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); @@ -245,8 +244,7 @@ public class StaticLayout extends Layout { Locale mLocale; - private static final Object sLock = new Object(); - private static final Builder[] sCached = new Builder[3]; + private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3); } public StaticLayout(CharSequence source, TextPaint paint, @@ -316,10 +314,9 @@ public class StaticLayout extends Layout { : new Ellipsizer(source), paint, outerwidth, align, textDir, spacingmult, spacingadd); - Builder b = Builder.obtain(); - b.setText(source, bufstart, bufend) + Builder b = Builder.obtain(source, bufstart, bufend, outerwidth) .setPaint(paint) - .setWidth(outerwidth) + .setAlignment(align) .setTextDir(textDir) .setSpacingMult(spacingmult) .setSpacingAdd(spacingadd) @@ -366,6 +363,35 @@ public class StaticLayout extends Layout { mLines = new int[mLineDirections.length]; } + private StaticLayout(Builder b) { + super((b.mEllipsize == null) + ? b.mText + : (b.mText instanceof Spanned) + ? new SpannedEllipsizer(b.mText) + : new Ellipsizer(b.mText), + b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd); + + if (b.mEllipsize != null) { + Ellipsizer e = (Ellipsizer) getText(); + + e.mLayout = this; + e.mWidth = b.mEllipsizedWidth; + e.mMethod = b.mEllipsize; + mEllipsizedWidth = b.mEllipsizedWidth; + + mColumns = COLUMNS_ELLIPSIZE; + } else { + mColumns = COLUMNS_NORMAL; + mEllipsizedWidth = b.mWidth; + } + + mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns); + mLines = new int[mLineDirections.length]; + mMaximumVisibleLineCount = b.mMaxLines; + + generate(b, b.mIncludePad, b.mIncludePad); + } + /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { CharSequence source = b.mText; int bufStart = b.mStart; @@ -477,10 +503,9 @@ public class StaticLayout extends Layout { } } - int breakStrategy = 0; // 0 = kBreakStrategy_Greedy nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, firstWidth, firstWidthLineCount, restWidth, - variableTabStops, TAB_INCREMENT, breakStrategy); + variableTabStops, TAB_INCREMENT, b.mBreakStrategy); // measurement has to be done before performing line breaking // but we don't want to recompute fontmetrics or span ranges the diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 87f3e94..31c3fe8 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5727,12 +5727,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be - resultSize = 0; + resultSize = size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be - resultSize = 0; + resultSize = size; resultMode = MeasureSpec.UNSPECIFIED; } break; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 77082b0..ec527d5 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1562,7 +1562,7 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Sets whether this node is visible to the user. + * Gets whether this node is visible to the user. * * @return Whether the node is visible to the user. */ diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index e2f8efc..ec2528f 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -187,7 +187,6 @@ class DayPickerView extends ViewPager { * @param setSelected whether to set the specified day as selected */ private void setDate(long timeInMillis, boolean animate, boolean setSelected) { - // Set the selected day if (setSelected) { mSelectedDay.setTimeInMillis(timeInMillis); } @@ -196,6 +195,9 @@ class DayPickerView extends ViewPager { if (position != getCurrentItem()) { setCurrentItem(position, animate); } + + mTempCalendar.setTimeInMillis(timeInMillis); + mAdapter.setSelectedDay(mTempCalendar); } public long getDate() { diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 349f3f0..a50941b 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -815,12 +815,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback mContext = context; mIntent = intent; - mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); - - mLayoutInflater = LayoutInflater.from(context); if (mIntent == null) { throw new IllegalArgumentException("Non-null Intent must be specified."); } + + mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); + mLayoutInflater = LayoutInflater.from(context); mRequestedViews = new RemoteViewsFrameLayoutRefSet(); // Strip the previously injected app widget id from service intent diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 3fb096c..d9f1f0e 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -80,11 +80,12 @@ class SimpleMonthView extends View { private final SimpleDateFormat mTitleFormatter; private final SimpleDateFormat mDayOfWeekFormatter; - private final int mMonthHeight; - private final int mDayOfWeekHeight; - private final int mDayHeight; - private final int mCellWidth; - private final int mDaySelectorRadius; + // Desired dimensions. + private final int mDesiredMonthHeight; + private final int mDesiredDayOfWeekHeight; + private final int mDesiredDayHeight; + private final int mDesiredCellWidth; + private final int mDesiredDaySelectorRadius; // Next/previous drawables. private final Drawable mPrevDrawable; @@ -99,6 +100,13 @@ class SimpleMonthView extends View { private int mMonth; private int mYear; + // Dimensions as laid out. + private int mMonthHeight; + private int mDayOfWeekHeight; + private int mDayHeight; + private int mCellWidth; + private int mDaySelectorRadius; + private int mPaddedWidth; private int mPaddedHeight; @@ -158,11 +166,11 @@ class SimpleMonthView extends View { super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); - mMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); - mDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); - mDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); - mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); - mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius); + mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height); + mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height); + mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height); + mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width); + mDesiredDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius); mPrevDrawable = context.getDrawable(R.drawable.ic_chevron_left); mNextDrawable = context.getDrawable(R.drawable.ic_chevron_right); @@ -400,7 +408,7 @@ class SimpleMonthView extends View { final TextPaint p = mDayOfWeekPaint; final int headerHeight = mMonthHeight; final int rowHeight = mDayOfWeekHeight; - final int colWidth = mPaddedWidth / DAYS_IN_WEEK; + final int colWidth = mCellWidth; // Text is vertically centered within the day of week height. final float halfLineHeight = (p.ascent() + p.descent()) / 2f; @@ -426,7 +434,7 @@ class SimpleMonthView extends View { final TextPaint p = mDayPaint; final int headerHeight = mMonthHeight + mDayOfWeekHeight; final int rowHeight = mDayHeight; - final int colWidth = mPaddedWidth / DAYS_IN_WEEK; + final int colWidth = mCellWidth; // Text is vertically centered within the row height. final float halfLineHeight = (p.ascent() + p.descent()) / 2f; @@ -627,9 +635,9 @@ class SimpleMonthView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int preferredHeight = mDayHeight * mNumWeeks + mDayOfWeekHeight + mMonthHeight - + getPaddingTop() + getPaddingBottom(); - final int preferredWidth = mCellWidth * DAYS_IN_WEEK + final int preferredHeight = mDesiredDayHeight * mNumWeeks + mDesiredDayOfWeekHeight + + mDesiredMonthHeight + getPaddingTop() + getPaddingBottom(); + final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK + getPaddingStart() + getPaddingEnd(); final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec); @@ -637,16 +645,46 @@ class SimpleMonthView extends View { } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - final int paddedLeft = getPaddingLeft(); - final int paddedTop = getPaddingTop(); - final int paddedRight = w - getPaddingRight(); - final int paddedBottom = h - getPaddingBottom(); - mPaddedWidth = paddedRight - paddedLeft; - mPaddedHeight = paddedBottom - paddedTop; - - final int monthHeight = mMonthHeight; + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!changed) { + return; + } + + // Let's initialize a completely reasonable number of variables. + final int w = right - left; + final int h = bottom - top; + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + final int paddingRight = getPaddingRight(); + final int paddingBottom = getPaddingBottom(); + final int paddedRight = w - paddingRight; + final int paddedBottom = h - paddingBottom; + final int paddedWidth = paddedRight - paddingLeft; + final int paddedHeight = paddedBottom - paddingTop; + if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) { + return; + } + + mPaddedWidth = paddedWidth; + mPaddedHeight = paddedHeight; + + // We may have been laid out smaller than our preferred size. If so, + // scale all dimensions to fit. + final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom; + final float scaleH = paddedHeight / (float) measuredPaddedHeight; + final int monthHeight = (int) (mDesiredMonthHeight * scaleH); final int cellWidth = mPaddedWidth / DAYS_IN_WEEK; + mMonthHeight = monthHeight; + mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH); + mDayHeight = (int) (mDesiredDayHeight * scaleH); + mCellWidth = cellWidth; + + // Compute the largest day selector radius that's still within the clip + // bounds and desired selector radius. + final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight); + final int maxSelectorHeight = mDayHeight / 2 + paddingBottom; + mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius, + Math.min(maxSelectorWidth, maxSelectorHeight)); // Vertically center the previous/next drawables within the month // header, horizontally center within the day cell, then expand the @@ -660,7 +698,7 @@ class SimpleMonthView extends View { // Button bounds don't include padding, but hit area does. prevDrawable.setBounds(iconLeft, iconTop, iconLeft + dW, iconTop + dH); - mPrevHitArea.set(0, 0, paddedLeft + cellWidth, paddedTop + monthHeight); + mPrevHitArea.set(0, 0, paddingLeft + cellWidth, paddingTop + monthHeight); } final Drawable nextDrawable = mNextDrawable; @@ -668,11 +706,11 @@ class SimpleMonthView extends View { final int dW = nextDrawable.getIntrinsicWidth(); final int dH = nextDrawable.getIntrinsicHeight(); final int iconTop = (monthHeight - dH) / 2; - final int iconRight = mPaddedWidth - (cellWidth - dW) / 2; + final int iconRight = paddedWidth - (cellWidth - dW) / 2; // Button bounds don't include padding, but hit area does. nextDrawable.setBounds(iconRight - dW, iconTop, iconRight, iconTop + dH); - mNextHitArea.set(paddedRight - cellWidth, 0, w, paddedTop + monthHeight); + mNextHitArea.set(paddedRight - cellWidth, 0, w, paddingTop + monthHeight); } // Invalidate cached accessibility information. @@ -753,7 +791,7 @@ class SimpleMonthView extends View { // Compute left edge. final int col = index % DAYS_IN_WEEK; - final int colWidth = mPaddedWidth / DAYS_IN_WEEK; + final int colWidth = mCellWidth; final int left = getPaddingLeft() + col * colWidth; // Compute top edge. diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 718ef93..2723080 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -543,6 +543,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mSpacingMult = 1.0f; private float mSpacingAdd = 0.0f; + private int mBreakStrategy; + private int mMaximum = Integer.MAX_VALUE; private int mMaxMode = LINES; private int mMinimum = 0; @@ -680,6 +682,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean elegant = false; float letterSpacing = 0; String fontFeatureSettings = null; + mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; final Resources.Theme theme = context.getTheme(); @@ -1133,6 +1136,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_fontFeatureSettings: fontFeatureSettings = a.getString(attr); break; + + case com.android.internal.R.styleable.TextView_breakStrategy: + mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); } } a.recycle(); @@ -2960,6 +2966,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Sets the break strategy for breaking paragraphs into lines. The default value for + * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for + * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the + * text "dancing" when being edited. + * + * @attr ref android.R.styleable#TextView_breakStrategy + * @see #getBreakStrategy() + */ + public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { + mBreakStrategy = breakStrategy; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + + /** + * @return the currently set break strategy. + * + * @attr ref android.R.styleable#TextView_breakStrategy + * @see #setBreakStrategy(int) + */ + @Layout.BreakStrategy + public int getBreakStrategy() { + return mBreakStrategy; + } + + /** * Sets font feature settings. The format is the same as the CSS * font-feature-settings attribute: * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings @@ -6492,27 +6527,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hintBoring, mIncludePad, mEllipsize, ellipsisWidth); } - } else if (shouldEllipsize) { - mHintLayout = new StaticLayout(mHint, - 0, mHint.length(), - mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, mEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - mHintLayout = new StaticLayout(mHint, mTextPaint, - hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); } - } else if (shouldEllipsize) { - mHintLayout = new StaticLayout(mHint, - 0, mHint.length(), - mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, mEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - mHintLayout = new StaticLayout(mHint, mTextPaint, - hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); + } + // TODO: code duplication with makeSingleLayout() + if (mHintLayout == null) { + StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, + mHint.length(), hintWidth) + .setPaint(mTextPaint) + .setAlignment(alignment) + .setTextDir(mTextDir) + .setSpacingMult(mSpacingMult) + .setSpacingAdd(mSpacingAdd) + .setIncludePad(mIncludePad) + .setBreakStrategy(mBreakStrategy); + if (shouldEllipsize) { + builder.setEllipsize(mEllipsize) + .setEllipsizedWidth(ellipsisWidth) + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + } + mHintLayout = builder.build(); } } @@ -6544,9 +6577,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Layout result = null; if (mText instanceof Spannable) { result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, - alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null, - ellipsisWidth); + alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mBreakStrategy, + getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth); } else { if (boring == UNKNOWN_BORING) { boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); @@ -6583,29 +6615,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boring, mIncludePad, effectiveEllipsize, ellipsisWidth); } - } else if (shouldEllipsize) { - result = new StaticLayout(mTransformed, - 0, mTransformed.length(), - mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, effectiveEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - result = new StaticLayout(mTransformed, mTextPaint, - wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); } - } else if (shouldEllipsize) { - result = new StaticLayout(mTransformed, - 0, mTransformed.length(), - mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, - mSpacingAdd, mIncludePad, effectiveEllipsize, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); - } else { - result = new StaticLayout(mTransformed, mTextPaint, - wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, - mIncludePad); } } + if (result == null) { + StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, + 0, mTransformed.length(), wantWidth) + .setPaint(mTextPaint) + .setAlignment(alignment) + .setTextDir(mTextDir) + .setSpacingMult(mSpacingMult) + .setSpacingAdd(mSpacingAdd) + .setIncludePad(mIncludePad) + .setBreakStrategy(mBreakStrategy); + if (shouldEllipsize) { + builder.setEllipsize(effectiveEllipsize) + .setEllipsizedWidth(ellipsisWidth) + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + } + // TODO: explore always setting maxLines + result = builder.build(); + } return result; } diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index c58d5cb..2365b48 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -25,8 +25,10 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.text.SpannableStringBuilder; import android.text.format.DateFormat; import android.text.format.DateUtils; +import android.text.style.TtsSpan; import android.util.AttributeSet; import android.util.Log; import android.util.StateSet; @@ -155,13 +157,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl mHourView.setMinWidth(computeStableWidth(mHourView, 24)); mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60)); + final SpannableStringBuilder amLabel = new SpannableStringBuilder() + .append(amPmStrings[0], new TtsSpan.VerbatimBuilder(amPmStrings[0]).build(), 0); + // Set up AM/PM labels. mAmPmLayout = mainView.findViewById(R.id.ampm_layout); mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label); - mAmLabel.setText(amPmStrings[0]); + mAmLabel.setText(obtainVerbatim(amPmStrings[0])); mAmLabel.setOnClickListener(mClickListener); mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label); - mPmLabel.setText(amPmStrings[1]); + mPmLabel.setText(obtainVerbatim(amPmStrings[1])); mPmLabel.setOnClickListener(mClickListener); // For the sake of backwards compatibility, attempt to extract the text @@ -220,6 +225,11 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX); } + private static final CharSequence obtainVerbatim(String text) { + return new SpannableStringBuilder().append(text, + new TtsSpan.VerbatimBuilder(text).build(), 0); + } + /** * The legacy text color might have been poorly defined. Ensures that it * has an appropriate activated state, using the selected state if one diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 93dc995..05ed3ab 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -104,7 +104,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 122 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 123 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; |
