Merge "Piping through ability for an Activity to remove its own task. (Bug 13735914)"
diff --git a/api/current.txt b/api/current.txt
index 59aac1e..ef6aed7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6928,6 +6928,7 @@
field public static final java.lang.String ACTION_PASTE = "android.intent.action.PASTE";
field public static final java.lang.String ACTION_PICK = "android.intent.action.PICK";
field public static final java.lang.String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY";
+ field public static final java.lang.String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
field public static final java.lang.String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED";
field public static final java.lang.String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
field public static final java.lang.String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
@@ -7076,6 +7077,7 @@
field public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 16; // 0x10
field public static final int FLAG_FROM_BACKGROUND = 4; // 0x4
field public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 64; // 0x40
+ field public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 128; // 0x80
field public static final int FLAG_GRANT_READ_URI_PERMISSION = 1; // 0x1
field public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 2; // 0x2
field public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; // 0x20
@@ -16812,11 +16814,24 @@
package android.nfc.cardemulation {
+ public final class AidGroup implements android.os.Parcelable {
+ ctor public AidGroup(java.util.ArrayList<java.lang.String>, java.lang.String);
+ method public int describeContents();
+ method public java.util.ArrayList<java.lang.String> getAids();
+ method public java.lang.String getCategory();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int MAX_NUM_AIDS = 256; // 0x100
+ }
+
public final class CardEmulation {
+ method public android.nfc.cardemulation.AidGroup getAidGroupForService(android.content.ComponentName, java.lang.String);
method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
method public int getSelectionModeForCategory(java.lang.String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
+ method public boolean registerAidGroupForService(android.content.ComponentName, android.nfc.cardemulation.AidGroup);
+ method public boolean removeAidGroupForService(android.content.ComponentName, java.lang.String);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
field public static final java.lang.String CATEGORY_OTHER = "other";
field public static final java.lang.String CATEGORY_PAYMENT = "payment";
@@ -19255,11 +19270,12 @@
field public static final int L = 10000; // 0x2710
}
- public final class Bundle implements java.lang.Cloneable android.os.Parcelable {
+ public final class Bundle extends android.os.CommonBundle {
ctor public Bundle();
ctor public Bundle(java.lang.ClassLoader);
ctor public Bundle(int);
ctor public Bundle(android.os.Bundle);
+ ctor public Bundle(android.os.PersistableBundle);
method public void clear();
method public java.lang.Object clone();
method public boolean containsKey(java.lang.String);
@@ -19297,6 +19313,7 @@
method public T getParcelable(java.lang.String);
method public android.os.Parcelable[] getParcelableArray(java.lang.String);
method public java.util.ArrayList<T> getParcelableArrayList(java.lang.String);
+ method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
method public java.io.Serializable getSerializable(java.lang.String);
method public short getShort(java.lang.String);
method public short getShort(java.lang.String, short);
@@ -19310,6 +19327,7 @@
method public boolean isEmpty();
method public java.util.Set<java.lang.String> keySet();
method public void putAll(android.os.Bundle);
+ method public void putAll(android.os.PersistableBundle);
method public void putBinder(java.lang.String, android.os.IBinder);
method public void putBoolean(java.lang.String, boolean);
method public void putBooleanArray(java.lang.String, boolean[]);
@@ -19333,6 +19351,7 @@
method public void putParcelable(java.lang.String, android.os.Parcelable);
method public void putParcelableArray(java.lang.String, android.os.Parcelable[]);
method public void putParcelableArrayList(java.lang.String, java.util.ArrayList<? extends android.os.Parcelable>);
+ method public void putPersistableBundle(java.lang.String, android.os.PersistableBundle);
method public void putSerializable(java.lang.String, java.io.Serializable);
method public void putShort(java.lang.String, short);
method public void putShortArray(java.lang.String, short[]);
@@ -19361,6 +19380,9 @@
method public abstract void onCancel();
}
+ abstract class CommonBundle implements java.lang.Cloneable android.os.Parcelable {
+ }
+
public class ConditionVariable {
ctor public ConditionVariable();
ctor public ConditionVariable(boolean);
@@ -19787,6 +19809,8 @@
method public final void readMap(java.util.Map, java.lang.ClassLoader);
method public final T readParcelable(java.lang.ClassLoader);
method public final android.os.Parcelable[] readParcelableArray(java.lang.ClassLoader);
+ method public final android.os.PersistableBundle readPersistableBundle();
+ method public final android.os.PersistableBundle readPersistableBundle(java.lang.ClassLoader);
method public final java.io.Serializable readSerializable();
method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
method public final android.util.SparseBooleanArray readSparseBooleanArray();
@@ -19827,6 +19851,7 @@
method public final void writeNoException();
method public final void writeParcelable(android.os.Parcelable, int);
method public final void writeParcelableArray(T[], int);
+ method public final void writePersistableBundle(android.os.PersistableBundle);
method public final void writeSerializable(java.io.Serializable);
method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
@@ -19937,6 +19962,51 @@
field public static final int PATTERN_SIMPLE_GLOB = 2; // 0x2
}
+ public final class PersistableBundle extends android.os.CommonBundle {
+ ctor public PersistableBundle();
+ ctor public PersistableBundle(java.lang.ClassLoader);
+ ctor public PersistableBundle(int);
+ ctor public PersistableBundle(android.os.PersistableBundle);
+ method public void clear();
+ method public java.lang.Object clone();
+ method public boolean containsKey(java.lang.String);
+ method public int describeContents();
+ method public java.lang.Object get(java.lang.String);
+ method public java.lang.ClassLoader getClassLoader();
+ method public double getDouble(java.lang.String);
+ method public double getDouble(java.lang.String, double);
+ method public double[] getDoubleArray(java.lang.String);
+ method public int getInt(java.lang.String);
+ method public int getInt(java.lang.String, int);
+ method public int[] getIntArray(java.lang.String);
+ method public long getLong(java.lang.String);
+ method public long getLong(java.lang.String, long);
+ method public long[] getLongArray(java.lang.String);
+ method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
+ method public java.lang.String getString(java.lang.String);
+ method public java.lang.String getString(java.lang.String, java.lang.String);
+ method public java.lang.String[] getStringArray(java.lang.String);
+ method public boolean isEmpty();
+ method public java.util.Set<java.lang.String> keySet();
+ method public void putAll(android.os.PersistableBundle);
+ method public void putDouble(java.lang.String, double);
+ method public void putDoubleArray(java.lang.String, double[]);
+ method public void putInt(java.lang.String, int);
+ method public void putIntArray(java.lang.String, int[]);
+ method public void putLong(java.lang.String, long);
+ method public void putLongArray(java.lang.String, long[]);
+ method public void putPersistableBundle(java.lang.String, android.os.PersistableBundle);
+ method public void putString(java.lang.String, java.lang.String);
+ method public void putStringArray(java.lang.String, java.lang.String[]);
+ method public void readFromParcel(android.os.Parcel);
+ method public void remove(java.lang.String);
+ method public void setClassLoader(java.lang.ClassLoader);
+ method public int size();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final android.os.PersistableBundle EMPTY;
+ }
+
public final class PowerManager {
method public void goToSleep(long);
method public boolean isInteractive();
@@ -22480,16 +22550,21 @@
public final class DocumentsContract {
method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String);
+ method public static android.net.Uri buildChildDocumentsViaUri(android.net.Uri, java.lang.String);
method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
+ method public static android.net.Uri buildDocumentViaUri(android.net.Uri, java.lang.String);
method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String);
method public static android.net.Uri buildRootUri(java.lang.String, java.lang.String);
method public static android.net.Uri buildRootsUri(java.lang.String);
method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
+ method public static android.net.Uri buildViaUri(java.lang.String, java.lang.String);
+ method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String);
method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri);
method public static java.lang.String getDocumentId(android.net.Uri);
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal);
method public static java.lang.String getRootId(android.net.Uri);
method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
+ method public static java.lang.String getViaDocumentId(android.net.Uri);
method public static boolean isDocumentUri(android.content.Context, android.net.Uri);
field public static final java.lang.String EXTRA_ERROR = "error";
field public static final java.lang.String EXTRA_INFO = "info";
@@ -22526,6 +22601,7 @@
field public static final java.lang.String COLUMN_TITLE = "title";
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+ field public static final int FLAG_SUPPORTS_DIR_SELECTION = 16; // 0x10
field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
}
@@ -22538,6 +22614,9 @@
method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
method public final java.lang.String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+ method public boolean isChildDocument(java.lang.String, java.lang.String);
+ method public final android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
+ method public final android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -22550,6 +22629,7 @@
method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
+ method public final void revokeDocumentPermission(java.lang.String);
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 92cb52c..7df55a5 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -240,6 +240,7 @@
" (to embed a comma into a string escape it using \"\\,\")\n" +
" [-n <COMPONENT>] [-f <FLAGS>]\n" +
" [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
+ " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]\n" +
" [--debug-log-resolution] [--exclude-stopped-packages]\n" +
" [--include-stopped-packages]\n" +
" [--activity-brought-to-front] [--activity-clear-top]\n" +
@@ -455,6 +456,10 @@
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else if (opt.equals("--grant-write-uri-permission")) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ } else if (opt.equals("--grant-persistable-uri-permission")) {
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ } else if (opt.equals("--grant-prefix-uri-permission")) {
+ intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
} else if (opt.equals("--exclude-stopped-packages")) {
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
} else if (opt.equals("--include-stopped-packages")) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f1ce54a..fe532bf 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1865,17 +1865,26 @@
}
private String uriModeFlagToString(int uriModeFlags) {
- switch (uriModeFlags) {
- case Intent.FLAG_GRANT_READ_URI_PERMISSION |
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
- return "read and write";
- case Intent.FLAG_GRANT_READ_URI_PERMISSION:
- return "read";
- case Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
- return "write";
+ StringBuilder builder = new StringBuilder();
+ if ((uriModeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ builder.append("read and ");
}
- throw new IllegalArgumentException(
- "Unknown permission mode flags: " + uriModeFlags);
+ if ((uriModeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ builder.append("write and ");
+ }
+ if ((uriModeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) {
+ builder.append("persistable and ");
+ }
+ if ((uriModeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) {
+ builder.append("prefix and ");
+ }
+
+ if (builder.length() > 5) {
+ builder.setLength(builder.length() - 5);
+ return builder.toString();
+ } else {
+ throw new IllegalArgumentException("Unknown permission mode flags: " + uriModeFlags);
+ }
}
private void enforceForUri(
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a6a04d1..5cf61a8 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -41,8 +41,6 @@
@Deprecated
public static final int DISABLE_NOTIFICATION_TICKER
= View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
- public static final int DISABLE_PRIVATE_NOTIFICATIONS
- = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO;
public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME;
public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index f3c803d..5b41394 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1641,7 +1641,7 @@
*
* @see #getPersistedUriPermissions()
*/
- public void takePersistableUriPermission(Uri uri, int modeFlags) {
+ public void takePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
try {
ActivityManagerNative.getDefault().takePersistableUriPermission(uri, modeFlags);
} catch (RemoteException e) {
@@ -1656,7 +1656,7 @@
*
* @see #getPersistedUriPermissions()
*/
- public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+ public void releasePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
try {
ActivityManagerNative.getDefault().releasePersistableUriPermission(uri, modeFlags);
} catch (RemoteException e) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 906484a..cbb6cf5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2791,9 +2791,13 @@
* @param uri The Uri you would like to grant access to.
* @param modeFlags The desired access modes. Any combination of
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
- * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION},
* {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
- * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION},
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, or
+ * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
+ * Intent.FLAG_GRANT_PREFIX_URI_PERMISSION}.
*
* @see #revokeUriPermission
*/
@@ -2806,7 +2810,8 @@
* Uri will match all previously granted Uris that are the same or a
* sub-path of the given Uri. That is, revoking "content://foo/target" will
* revoke both "content://foo/target" and "content://foo/target/sub", but not
- * "content://foo".
+ * "content://foo". It will not remove any prefix grants that exist at a
+ * higher level.
*
* @param uri The Uri you would like to revoke access to.
* @param modeFlags The desired access modes. Any combination of
@@ -2817,7 +2822,7 @@
*
* @see #grantUriPermission
*/
- public abstract void revokeUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags);
+ public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
* Determine whether a particular process and user ID has been granted
@@ -2841,7 +2846,7 @@
* @see #checkCallingUriPermission
*/
public abstract int checkUriPermission(Uri uri, int pid, int uid,
- @Intent.GrantUriMode int modeFlags);
+ @Intent.AccessUriMode int modeFlags);
/**
* Determine whether the calling process and user ID has been
@@ -2864,7 +2869,7 @@
*
* @see #checkUriPermission(Uri, int, int, int)
*/
- public abstract int checkCallingUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags);
+ public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
* Determine whether the calling process of an IPC <em>or you</em> has been granted
@@ -2884,7 +2889,7 @@
* @see #checkCallingUriPermission
*/
public abstract int checkCallingOrSelfUriPermission(Uri uri,
- @Intent.GrantUriMode int modeFlags);
+ @Intent.AccessUriMode int modeFlags);
/**
* Check both a Uri and normal permission. This allows you to perform
@@ -2910,7 +2915,7 @@
*/
public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
@Nullable String writePermission, int pid, int uid,
- @Intent.GrantUriMode int modeFlags);
+ @Intent.AccessUriMode int modeFlags);
/**
* If a particular process and user ID has not been granted
@@ -2932,7 +2937,7 @@
* @see #checkUriPermission(Uri, int, int, int)
*/
public abstract void enforceUriPermission(
- Uri uri, int pid, int uid, @Intent.GrantUriMode int modeFlags, String message);
+ Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message);
/**
* If the calling process and user ID has not been granted
@@ -2954,7 +2959,7 @@
* @see #checkCallingUriPermission(Uri, int)
*/
public abstract void enforceCallingUriPermission(
- Uri uri, @Intent.GrantUriMode int modeFlags, String message);
+ Uri uri, @Intent.AccessUriMode int modeFlags, String message);
/**
* If the calling process of an IPC <em>or you</em> has not been
@@ -2973,7 +2978,7 @@
* @see #checkCallingOrSelfUriPermission(Uri, int)
*/
public abstract void enforceCallingOrSelfUriPermission(
- Uri uri, @Intent.GrantUriMode int modeFlags, String message);
+ Uri uri, @Intent.AccessUriMode int modeFlags, String message);
/**
* Enforce both a Uri and normal permission. This allows you to perform
@@ -2998,7 +3003,7 @@
*/
public abstract void enforceUriPermission(
@Nullable Uri uri, @Nullable String readPermission,
- @Nullable String writePermission, int pid, int uid, @Intent.GrantUriMode int modeFlags,
+ @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags,
@Nullable String message);
/** @hide */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 421956b..c0f04af 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@
import android.content.pm.ApplicationInfo;
import android.util.ArraySet;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -864,8 +865,9 @@
}
// Migrate any clip data and flags from target.
- int permFlags = target.getFlags()
- & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
+ int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION);
if (permFlags != 0) {
ClipData targetClipData = target.getClipData();
if (targetClipData == null && target.getData() != null) {
@@ -2698,9 +2700,11 @@
* take the persistable permissions using
* {@link ContentResolver#takePersistableUriPermission(Uri, int)}.
* <p>
- * Callers can restrict document selection to a specific kind of data, such
- * as photos, by setting one or more MIME types in
- * {@link #EXTRA_MIME_TYPES}.
+ * Callers must indicate the acceptable document MIME types through
+ * {@link #setType(String)}. For example, to select photos, use
+ * {@code image/*}. If multiple disjoint MIME types are acceptable, define
+ * them in {@link #EXTRA_MIME_TYPES} and {@link #setType(String)} to
+ * {@literal *}/*.
* <p>
* If the caller can handle multiple returned items (the user performing
* multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
@@ -2710,9 +2714,10 @@
* returned URIs can be opened with
* {@link ContentResolver#openFileDescriptor(Uri, String)}.
* <p>
- * Output: The URI of the item that was picked. This must be a
- * {@code content://} URI so that any receiver can access it. If multiple
- * documents were selected, they are returned in {@link #getClipData()}.
+ * Output: The URI of the item that was picked, returned in
+ * {@link #getData()}. This must be a {@code content://} URI so that any
+ * receiver can access it. If multiple documents were selected, they are
+ * returned in {@link #getClipData()}.
*
* @see DocumentsContract
* @see #ACTION_CREATE_DOCUMENT
@@ -2754,6 +2759,24 @@
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+ /**
+ * Activity Action: Allow the user to pick a directory. When invoked, the
+ * system will display the various {@link DocumentsProvider} instances
+ * installed on the device, letting the user navigate through them. Apps can
+ * fully manage documents within the returned directory.
+ * <p>
+ * To gain access to descendant (child, grandchild, etc) documents, use
+ * {@link DocumentsContract#buildDocumentViaUri(Uri, String)} and
+ * {@link DocumentsContract#buildChildDocumentsViaUri(Uri, String)} using
+ * the returned directory URI.
+ * <p>
+ * Output: The URI representing the selected directory.
+ *
+ * @see DocumentsContract
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -3332,6 +3355,7 @@
* @see #ACTION_GET_CONTENT
* @see #ACTION_OPEN_DOCUMENT
* @see #ACTION_CREATE_DOCUMENT
+ * @see #ACTION_PICK_DIRECTORY
*/
public static final String EXTRA_LOCAL_ONLY =
"android.intent.extra.LOCAL_ONLY";
@@ -3425,11 +3449,29 @@
// Intent flags (see mFlags variable).
/** @hide */
- @IntDef(flag = true,
- value = {FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION})
+ @IntDef(flag = true, value = {
+ FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION,
+ FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_PREFIX_URI_PERMISSION })
@Retention(RetentionPolicy.SOURCE)
public @interface GrantUriMode {}
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AccessUriMode {}
+
+ /**
+ * Test if given mode flags specify an access mode, which must be at least
+ * read and/or write.
+ *
+ * @hide
+ */
+ public static boolean isAccessUriMode(int modeFlags) {
+ return (modeFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) != 0;
+ }
+
/**
* If set, the recipient of this Intent will be granted permission to
* perform read operations on the URI in the Intent's data and any URIs
@@ -3491,6 +3533,17 @@
public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040;
/**
+ * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant
+ * applies to any URI that is a prefix match against the original granted
+ * URI. (Without this flag, the URI must match exactly for access to be
+ * granted.) Another URI is considered a prefix match only when scheme,
+ * authority, and all path segments defined by the prefix are an exact
+ * match.
+ */
+ public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 0x00000080;
+
+ /**
* If set, the new activity is not kept in the history stack. As soon as
* the user navigates away from it, the activity is finished. This may also
* be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
@@ -3810,9 +3863,9 @@
/**
* @hide Flags that can't be changed with PendingIntent.
*/
- public static final int IMMUTABLE_FLAGS =
- FLAG_GRANT_READ_URI_PERMISSION
- | FLAG_GRANT_WRITE_URI_PERMISSION;
+ public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION;
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
@@ -6350,6 +6403,8 @@
*
* @see #FLAG_GRANT_READ_URI_PERMISSION
* @see #FLAG_GRANT_WRITE_URI_PERMISSION
+ * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * @see #FLAG_GRANT_PREFIX_URI_PERMISSION
* @see #FLAG_DEBUG_LOG_RESOLUTION
* @see #FLAG_FROM_BACKGROUND
* @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
@@ -7381,9 +7436,10 @@
// Since we migrated in child, we need to promote ClipData
// and flags to ourselves to grant.
setClipData(target.getClipData());
- addFlags(target.getFlags()
- & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION
- | FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+ addFlags(target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION
+ | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION));
return true;
} else {
return false;
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index a7a8a0a..ce70455 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import android.os.StrictMode;
import android.util.Log;
+
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -32,8 +33,10 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.RandomAccess;
import java.util.Set;
+
import libcore.net.UriCodec;
/**
@@ -2338,4 +2341,29 @@
StrictMode.onFileUriExposed(location);
}
}
+
+ /**
+ * Test if this is a path prefix match against the given Uri. Verifies that
+ * scheme, authority, and atomic path segments match.
+ *
+ * @hide
+ */
+ public boolean isPathPrefixMatch(Uri prefix) {
+ if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
+ if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
+
+ List<String> seg = getPathSegments();
+ List<String> prefixSeg = prefix.getPathSegments();
+
+ final int prefixSize = prefixSeg.size();
+ if (seg.size() < prefixSize) return false;
+
+ for (int i = 0; i < prefixSize; i++) {
+ if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index b8a5ba7..ae9796b 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,7 @@
package android.nfc;
import android.content.ComponentName;
+import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.os.RemoteCallback;
@@ -29,5 +30,8 @@
boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+ boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+ AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
+ boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
List<ApduServiceInfo> getServices(int userHandle, in String category);
}
diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/core/java/android/nfc/cardemulation/AidGroup.aidl
new file mode 100644
index 0000000..56d6fa5
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/AidGroup.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+parcelable AidGroup;
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
new file mode 100644
index 0000000..2820f40
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -0,0 +1,165 @@
+package android.nfc.cardemulation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * The AidGroup class represents a group of ISO/IEC 7816-4
+ * Application Identifiers (AIDs) for a specific application
+ * category, along with a description resource describing
+ * the group.
+ */
+public final class AidGroup implements Parcelable {
+ /**
+ * The maximum number of AIDs that can be present in any one group.
+ */
+ public static final int MAX_NUM_AIDS = 256;
+
+ static final String TAG = "AidGroup";
+
+ final ArrayList<String> aids;
+ final String category;
+ final String description;
+
+ /**
+ * Creates a new AidGroup object.
+ *
+ * @param aids The list of AIDs present in the group
+ * @param category The category of this group
+ */
+ public AidGroup(ArrayList<String> aids, String category) {
+ if (aids == null || aids.size() == 0) {
+ throw new IllegalArgumentException("No AIDS in AID group.");
+ }
+ if (aids.size() > MAX_NUM_AIDS) {
+ throw new IllegalArgumentException("Too many AIDs in AID group.");
+ }
+ if (!isValidCategory(category)) {
+ throw new IllegalArgumentException("Category specified is not valid.");
+ }
+ this.aids = aids;
+ this.category = category;
+ this.description = null;
+ }
+
+ AidGroup(String category, String description) {
+ this.aids = new ArrayList<String>();
+ this.category = category;
+ this.description = description;
+ }
+
+ /**
+ * @return the category of this AID group
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * @return the list of AIDs in this group
+ */
+ public ArrayList<String> getAids() {
+ return aids;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder("Category: " + category +
+ ", AIDs:");
+ for (String aid : aids) {
+ out.append(aid);
+ out.append(", ");
+ }
+ return out.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(category);
+ dest.writeInt(aids.size());
+ if (aids.size() > 0) {
+ dest.writeStringList(aids);
+ }
+ }
+
+ public static final Parcelable.Creator<AidGroup> CREATOR =
+ new Parcelable.Creator<AidGroup>() {
+
+ @Override
+ public AidGroup createFromParcel(Parcel source) {
+ String category = source.readString();
+ int listSize = source.readInt();
+ ArrayList<String> aidList = new ArrayList<String>();
+ if (listSize > 0) {
+ source.readStringList(aidList);
+ }
+ return new AidGroup(aidList, category);
+ }
+
+ @Override
+ public AidGroup[] newArray(int size) {
+ return new AidGroup[size];
+ }
+ };
+
+ /**
+ * @hide
+ * Note: description is not serialized, since it's not localized
+ * and resource identifiers don't make sense to persist.
+ */
+ static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String category = parser.getAttributeValue(null, "category");
+ ArrayList<String> aids = new ArrayList<String>();
+ int eventType = parser.getEventType();
+ int minDepth = parser.getDepth();
+ while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if (tagName.equals("aid")) {
+ String aid = parser.getAttributeValue(null, "value");
+ if (aid != null) {
+ aids.add(aid);
+ }
+ } else {
+ Log.d(TAG, "Ignorning unexpected tag: " + tagName);
+ }
+ }
+ eventType = parser.next();
+ }
+ if (category != null && aids.size() > 0) {
+ return new AidGroup(aids, category);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void writeAsXml(XmlSerializer out) throws IOException {
+ out.attribute(null, "category", category);
+ for (String aid : aids) {
+ out.startTag(null, "aid");
+ out.attribute(null, "value", aid);
+ out.endTag(null, "aid");
+ }
+ }
+
+ boolean isValidCategory(String category) {
+ return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
+ CardEmulation.CATEGORY_OTHER.equals(category);
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index d7ef4bc..94f35ed 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -35,9 +35,12 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
/**
* @hide
@@ -56,24 +59,19 @@
final String mDescription;
/**
- * Convenience AID list
- */
- final ArrayList<String> mAids;
-
- /**
* Whether this service represents AIDs running on the host CPU
*/
final boolean mOnHost;
/**
- * All AID groups this service handles
+ * Mapping from category to static AID group
*/
- final ArrayList<AidGroup> mAidGroups;
+ final HashMap<String, AidGroup> mStaticAidGroups;
/**
- * Convenience hashmap
+ * Mapping from category to dynamic AID group
*/
- final HashMap<String, AidGroup> mCategoryToGroup;
+ final HashMap<String, AidGroup> mDynamicAidGroups;
/**
* Whether this service should only be started when the device is unlocked.
@@ -86,26 +84,33 @@
final int mBannerResourceId;
/**
+ * The uid of the package the service belongs to
+ */
+ final int mUid;
+ /**
* @hide
*/
public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
- ArrayList<AidGroup> aidGroups, boolean requiresUnlock, int bannerResource) {
+ ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, int bannerResource, int uid) {
this.mService = info;
this.mDescription = description;
- this.mAidGroups = aidGroups;
- this.mAids = new ArrayList<String>();
- this.mCategoryToGroup = new HashMap<String, AidGroup>();
+ this.mStaticAidGroups = new HashMap<String, AidGroup>();
+ this.mDynamicAidGroups = new HashMap<String, AidGroup>();
this.mOnHost = onHost;
this.mRequiresDeviceUnlock = requiresUnlock;
- for (AidGroup aidGroup : aidGroups) {
- this.mCategoryToGroup.put(aidGroup.category, aidGroup);
- this.mAids.addAll(aidGroup.aids);
+ for (AidGroup aidGroup : staticAidGroups) {
+ this.mStaticAidGroups.put(aidGroup.category, aidGroup);
+ }
+ for (AidGroup aidGroup : dynamicAidGroups) {
+ this.mDynamicAidGroups.put(aidGroup.category, aidGroup);
}
this.mBannerResourceId = bannerResource;
+ this.mUid = uid;
}
- public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost)
- throws XmlPullParserException, IOException {
+ public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws
+ XmlPullParserException, IOException {
ServiceInfo si = info.serviceInfo;
XmlResourceParser parser = null;
try {
@@ -163,10 +168,10 @@
sa.recycle();
}
- mAidGroups = new ArrayList<AidGroup>();
- mCategoryToGroup = new HashMap<String, AidGroup>();
- mAids = new ArrayList<String>();
+ mStaticAidGroups = new HashMap<String, AidGroup>();
+ mDynamicAidGroups = new HashMap<String, AidGroup>();
mOnHost = onHost;
+
final int depth = parser.getDepth();
AidGroup currentGroup = null;
@@ -179,14 +184,14 @@
final TypedArray groupAttrs = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AidGroup);
// Get category of AID group
- String groupDescription = groupAttrs.getString(
- com.android.internal.R.styleable.AidGroup_description);
String groupCategory = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_category);
+ String groupDescription = groupAttrs.getString(
+ com.android.internal.R.styleable.AidGroup_description);
if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
groupCategory = CardEmulation.CATEGORY_OTHER;
}
- currentGroup = mCategoryToGroup.get(groupCategory);
+ currentGroup = mStaticAidGroups.get(groupCategory);
if (currentGroup != null) {
if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
Log.e(TAG, "Not allowing multiple aid-groups in the " +
@@ -200,9 +205,8 @@
} else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
currentGroup != null) {
if (currentGroup.aids.size() > 0) {
- if (!mCategoryToGroup.containsKey(currentGroup.category)) {
- mAidGroups.add(currentGroup);
- mCategoryToGroup.put(currentGroup.category, currentGroup);
+ if (!mStaticAidGroups.containsKey(currentGroup.category)) {
+ mStaticAidGroups.put(currentGroup.category, currentGroup);
}
} else {
Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
@@ -216,7 +220,6 @@
toUpperCase();
if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
currentGroup.aids.add(aid);
- mAids.add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
}
@@ -228,6 +231,8 @@
} finally {
if (parser != null) parser.close();
}
+ // Set uid
+ mUid = si.applicationInfo.uid;
}
public ComponentName getComponent() {
@@ -235,16 +240,58 @@
mService.serviceInfo.name);
}
+ /**
+ * Returns a consolidated list of AIDs from the AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AIDs will be returned
+ * for that category.
+ * @return List of AIDs registered by the service
+ */
public ArrayList<String> getAids() {
- return mAids;
+ final ArrayList<String> aids = new ArrayList<String>();
+ for (AidGroup group : getAidGroups()) {
+ aids.addAll(group.aids);
+ }
+ return aids;
}
+ /**
+ * Returns the registered AID group for this category.
+ */
+ public AidGroup getDynamicAidGroupForCategory(String category) {
+ return mDynamicAidGroups.get(category);
+ }
+
+ public boolean removeDynamicAidGroupForCategory(String category) {
+ return (mDynamicAidGroups.remove(category) != null);
+ }
+
+ /**
+ * Returns a consolidated list of AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AID group will be returned
+ * for that category.
+ * @return List of AIDs registered by the service
+ */
public ArrayList<AidGroup> getAidGroups() {
- return mAidGroups;
+ final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
+ for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
+ groups.add(entry.getValue());
+ }
+ for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
+ if (!mDynamicAidGroups.containsKey(entry.getKey())) {
+ // Consolidate AID groups - don't return static ones
+ // if a dynamic group exists for the category.
+ groups.add(entry.getValue());
+ }
+ }
+ return groups;
}
public boolean hasCategory(String category) {
- return mCategoryToGroup.containsKey(category);
+ return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
}
public boolean isOnHost() {
@@ -259,6 +306,14 @@
return mDescription;
}
+ public int getUid() {
+ return mUid;
+ }
+
+ public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
+ mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+ }
+
public CharSequence loadLabel(PackageManager pm) {
return mService.loadLabel(pm);
}
@@ -304,8 +359,12 @@
StringBuilder out = new StringBuilder("ApduService: ");
out.append(getComponent());
out.append(", description: " + mDescription);
- out.append(", AID Groups: ");
- for (AidGroup aidGroup : mAidGroups) {
+ out.append(", Static AID Groups: ");
+ for (AidGroup aidGroup : mStaticAidGroups.values()) {
+ out.append(aidGroup.toString());
+ }
+ out.append(", Dynamic AID Groups: ");
+ for (AidGroup aidGroup : mDynamicAidGroups.values()) {
out.append(aidGroup.toString());
}
return out.toString();
@@ -336,12 +395,17 @@
mService.writeToParcel(dest, flags);
dest.writeString(mDescription);
dest.writeInt(mOnHost ? 1 : 0);
- dest.writeInt(mAidGroups.size());
- if (mAidGroups.size() > 0) {
- dest.writeTypedList(mAidGroups);
+ dest.writeInt(mStaticAidGroups.size());
+ if (mStaticAidGroups.size() > 0) {
+ dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
+ }
+ dest.writeInt(mDynamicAidGroups.size());
+ if (mDynamicAidGroups.size() > 0) {
+ dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
}
dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
dest.writeInt(mBannerResourceId);
+ dest.writeInt(mUid);
};
public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
@@ -351,14 +415,21 @@
ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
String description = source.readString();
boolean onHost = (source.readInt() != 0) ? true : false;
- ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
- int numGroups = source.readInt();
- if (numGroups > 0) {
- source.readTypedList(aidGroups, AidGroup.CREATOR);
+ ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
+ int numStaticGroups = source.readInt();
+ if (numStaticGroups > 0) {
+ source.readTypedList(staticAidGroups, AidGroup.CREATOR);
+ }
+ ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
+ int numDynamicGroups = source.readInt();
+ if (numDynamicGroups > 0) {
+ source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
}
boolean requiresUnlock = (source.readInt() != 0) ? true : false;
int bannerResource = source.readInt();
- return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource);
+ int uid = source.readInt();
+ return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+ dynamicAidGroups, requiresUnlock, bannerResource, uid);
}
@Override
@@ -367,76 +438,22 @@
}
};
- public static class AidGroup implements Parcelable {
- final ArrayList<String> aids;
- final String category;
- final String description;
-
- AidGroup(ArrayList<String> aids, String category, String description) {
- this.aids = aids;
- this.category = category;
- this.description = description;
- }
-
- AidGroup(String category, String description) {
- this.aids = new ArrayList<String>();
- this.category = category;
- this.description = description;
- }
-
- public String getCategory() {
- return category;
- }
-
- public ArrayList<String> getAids() {
- return aids;
- }
-
- @Override
- public String toString() {
- StringBuilder out = new StringBuilder("Category: " + category +
- ", description: " + description + ", AIDs:");
- for (String aid : aids) {
- out.append(aid);
- out.append(", ");
- }
- return out.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(category);
- dest.writeString(description);
- dest.writeInt(aids.size());
- if (aids.size() > 0) {
- dest.writeStringList(aids);
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" " + getComponent() +
+ " (Description: " + getDescription() + ")");
+ pw.println(" Static AID groups:");
+ for (AidGroup group : mStaticAidGroups.values()) {
+ pw.println(" Category: " + group.category);
+ for (String aid : group.aids) {
+ pw.println(" AID: " + aid);
}
}
-
- public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR =
- new Parcelable.Creator<ApduServiceInfo.AidGroup>() {
-
- @Override
- public AidGroup createFromParcel(Parcel source) {
- String category = source.readString();
- String description = source.readString();
- int listSize = source.readInt();
- ArrayList<String> aidList = new ArrayList<String>();
- if (listSize > 0) {
- source.readStringList(aidList);
- }
- return new AidGroup(aidList, category, description);
+ pw.println(" Dynamic AID groups:");
+ for (AidGroup group : mDynamicAidGroups.values()) {
+ pw.println(" Category: " + group.category);
+ for (String aid : group.aids) {
+ pw.println(" AID: " + aid);
}
-
- @Override
- public AidGroup[] newArray(int size) {
- return new AidGroup[size];
- }
- };
+ }
}
}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 58d9616..41f039c 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -168,6 +168,10 @@
if (manager == null) {
// Get card emu service
INfcCardEmulation service = adapter.getCardEmulationService();
+ if (service == null) {
+ Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
+ throw new UnsupportedOperationException();
+ }
manager = new CardEmulation(context, service);
sCardEmus.put(context, manager);
}
@@ -271,6 +275,109 @@
}
/**
+ * Registers a group of AIDs for the specified service.
+ *
+ * <p>If an AID group for that category was previously
+ * registered for this service (either statically
+ * through the manifest, or dynamically by using this API),
+ * that AID group will be replaced with this one.
+ *
+ * <p>Note that you can only register AIDs for a service that
+ * is running under the same UID as you are. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * @param service The component name of the service
+ * @param aidGroup The group of AIDs to be registered
+ * @return whether the registration was successful.
+ */
+ public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
+ try {
+ return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerAidGroupForService(UserHandle.myUserId(), service,
+ aidGroup);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the currently registered AID group for the specified
+ * category for a service.
+ *
+ * <p>Note that this will only return AID groups that were dynamically
+ * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)}
+ * method. It will *not* return AID groups that were statically registered
+ * in the manifest.
+ *
+ * @param service The component name of the service
+ * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT}
+ * @return The AID group, or null if it couldn't be found
+ */
+ public AidGroup getAidGroupForService(ComponentName service, String category) {
+ try {
+ return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Removes a registered AID group for the specified category for the
+ * service provided.
+ *
+ * <p>Note that this will only remove AID groups that were dynamically
+ * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)}
+ * method. It will *not* remove AID groups that were statically registered in
+ * the manifest. If a dynamically registered AID group is removed using
+ * this method, and a statically registered AID group for the same category
+ * exists in the manifest, that AID group will become active again.
+ *
+ * @param service The component name of the service
+ * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT}
+ * @return whether the group was successfully removed.
+ */
+ public boolean removeAidGroupForService(ComponentName service, String category) {
+ try {
+ return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
* @hide
*/
public boolean setDefaultServiceForCategory(ComponentName service, String category) {
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index af57507..c85e418 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -17,7 +17,6 @@
package android.os;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.SparseArray;
import java.io.Serializable;
@@ -29,47 +28,25 @@
* A mapping from String values to various Parcelable types.
*
*/
-public final class Bundle implements Parcelable, Cloneable {
- private static final String TAG = "Bundle";
- static final boolean DEBUG = false;
+public final class Bundle extends CommonBundle {
public static final Bundle EMPTY;
-
- static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
static final Parcel EMPTY_PARCEL;
static {
EMPTY = new Bundle();
EMPTY.mMap = ArrayMap.EMPTY;
- EMPTY_PARCEL = Parcel.obtain();
+ EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
}
- // Invariant - exactly one of mMap / mParcelledData will be null
- // (except inside a call to unparcel)
-
- /* package */ ArrayMap<String, Object> mMap = null;
-
- /*
- * If mParcelledData is non-null, then mMap will be null and the
- * data are stored as a Parcel containing a Bundle. When the data
- * are unparcelled, mParcelledData willbe set to null.
- */
- /* package */ Parcel mParcelledData = null;
-
private boolean mHasFds = false;
private boolean mFdsKnown = true;
private boolean mAllowFds = true;
/**
- * The ClassLoader used when unparcelling data from mParcelledData.
- */
- private ClassLoader mClassLoader;
-
- /**
* Constructs a new, empty Bundle.
*/
public Bundle() {
- mMap = new ArrayMap<String, Object>();
- mClassLoader = getClass().getClassLoader();
+ super();
}
/**
@@ -79,11 +56,17 @@
* @param parcelledData a Parcel containing a Bundle
*/
Bundle(Parcel parcelledData) {
- readFromParcel(parcelledData);
+ super(parcelledData);
+
+ mHasFds = mParcelledData.hasFileDescriptors();
+ mFdsKnown = true;
}
/* package */ Bundle(Parcel parcelledData, int length) {
- readFromParcelInner(parcelledData, length);
+ super(parcelledData, length);
+
+ mHasFds = mParcelledData.hasFileDescriptors();
+ mFdsKnown = true;
}
/**
@@ -94,8 +77,7 @@
* inside of the Bundle.
*/
public Bundle(ClassLoader loader) {
- mMap = new ArrayMap<String, Object>();
- mClassLoader = loader;
+ super(loader);
}
/**
@@ -105,8 +87,7 @@
* @param capacity the initial capacity of the Bundle
*/
public Bundle(int capacity) {
- mMap = new ArrayMap<String, Object>(capacity);
- mClassLoader = getClass().getClassLoader();
+ super(capacity);
}
/**
@@ -116,27 +97,20 @@
* @param b a Bundle to be copied.
*/
public Bundle(Bundle b) {
- if (b.mParcelledData != null) {
- if (b.mParcelledData == EMPTY_PARCEL) {
- mParcelledData = EMPTY_PARCEL;
- } else {
- mParcelledData = Parcel.obtain();
- mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
- mParcelledData.setDataPosition(0);
- }
- } else {
- mParcelledData = null;
- }
-
- if (b.mMap != null) {
- mMap = new ArrayMap<String, Object>(b.mMap);
- } else {
- mMap = null;
- }
+ super(b);
mHasFds = b.mHasFds;
mFdsKnown = b.mFdsKnown;
- mClassLoader = b.mClassLoader;
+ }
+
+ /**
+ * Constructs a Bundle containing a copy of the mappings from the given
+ * PersistableBundle.
+ *
+ * @param b a Bundle to be copied.
+ */
+ public Bundle(PersistableBundle b) {
+ super(b);
}
/**
@@ -145,37 +119,17 @@
* @hide
*/
public static Bundle forPair(String key, String value) {
- // TODO: optimize this case.
Bundle b = new Bundle(1);
b.putString(key, value);
return b;
}
/**
- * TODO: optimize this later (getting just the value part of a Bundle
- * with a single pair) once Bundle.forPair() above is implemented
- * with a special single-value Map implementation/serialization.
- *
- * Note: value in single-pair Bundle may be null.
- *
* @hide
*/
+ @Override
public String getPairValue() {
- unparcel();
- int size = mMap.size();
- if (size > 1) {
- Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
- }
- if (size == 0) {
- return null;
- }
- Object o = mMap.valueAt(0);
- try {
- return (String) o;
- } catch (ClassCastException e) {
- typeWarning("getPairValue()", o, "String", e);
- return null;
- }
+ return super.getPairValue();
}
/**
@@ -184,15 +138,17 @@
* @param loader An explicit ClassLoader to use when instantiating objects
* inside of the Bundle.
*/
+ @Override
public void setClassLoader(ClassLoader loader) {
- mClassLoader = loader;
+ super.setClassLoader(loader);
}
/**
* Return the ClassLoader currently associated with this Bundle.
*/
+ @Override
public ClassLoader getClassLoader() {
- return mClassLoader;
+ return super.getClassLoader();
}
/** @hide */
@@ -212,52 +168,11 @@
}
/**
- * If the underlying data are stored as a Parcel, unparcel them
- * using the currently assigned class loader.
- */
- /* package */ synchronized void unparcel() {
- if (mParcelledData == null) {
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": no parcelled data");
- return;
- }
-
- if (mParcelledData == EMPTY_PARCEL) {
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": empty");
- if (mMap == null) {
- mMap = new ArrayMap<String, Object>(1);
- } else {
- mMap.erase();
- }
- mParcelledData = null;
- return;
- }
-
- int N = mParcelledData.readInt();
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": reading " + N + " maps");
- if (N < 0) {
- return;
- }
- if (mMap == null) {
- mMap = new ArrayMap<String, Object>(N);
- } else {
- mMap.erase();
- mMap.ensureCapacity(N);
- }
- mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
- mParcelledData.recycle();
- mParcelledData = null;
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + " final map: " + mMap);
- }
-
- /**
* @hide
*/
+ @Override
public boolean isParcelled() {
- return mParcelledData != null;
+ return super.isParcelled();
}
/**
@@ -265,25 +180,26 @@
*
* @return the number of mappings as an int.
*/
+ @Override
public int size() {
- unparcel();
- return mMap.size();
+ return super.size();
}
/**
* Returns true if the mapping of this Bundle is empty, false otherwise.
*/
+ @Override
public boolean isEmpty() {
- unparcel();
- return mMap.isEmpty();
+ return super.isEmpty();
}
/**
* Removes all elements from the mapping of this Bundle.
*/
+ @Override
public void clear() {
- unparcel();
- mMap.clear();
+ super.clear();
+
mHasFds = false;
mFdsKnown = true;
}
@@ -295,9 +211,9 @@
* @param key a String key
* @return true if the key is part of the mapping, false otherwise
*/
+ @Override
public boolean containsKey(String key) {
- unparcel();
- return mMap.containsKey(key);
+ return super.containsKey(key);
}
/**
@@ -306,9 +222,9 @@
* @param key a String key
* @return an Object, or null
*/
+ @Override
public Object get(String key) {
- unparcel();
- return mMap.get(key);
+ return super.get(key);
}
/**
@@ -316,24 +232,33 @@
*
* @param key a String key
*/
+ @Override
public void remove(String key) {
- unparcel();
- mMap.remove(key);
+ super.remove(key);
}
/**
* Inserts all mappings from the given Bundle into this Bundle.
*
- * @param map a Bundle
+ * @param bundle a Bundle
*/
- public void putAll(Bundle map) {
+ public void putAll(Bundle bundle) {
unparcel();
- map.unparcel();
- mMap.putAll(map.mMap);
+ bundle.unparcel();
+ mMap.putAll(bundle.mMap);
// fd state is now known if and only if both bundles already knew
- mHasFds |= map.mHasFds;
- mFdsKnown = mFdsKnown && map.mFdsKnown;
+ mHasFds |= bundle.mHasFds;
+ mFdsKnown = mFdsKnown && bundle.mFdsKnown;
+ }
+
+ /**
+ * Inserts all mappings from the given PersistableBundle into this Bundle.
+ *
+ * @param bundle a PersistableBundle
+ */
+ public void putAll(PersistableBundle bundle) {
+ super.putAll(bundle);
}
/**
@@ -341,9 +266,9 @@
*
* @return a Set of String keys
*/
+ @Override
public Set<String> keySet() {
- unparcel();
- return mMap.keySet();
+ return super.keySet();
}
/**
@@ -352,7 +277,7 @@
public boolean hasFileDescriptors() {
if (!mFdsKnown) {
boolean fdFound = false; // keep going until we find one or run out of data
-
+
if (mParcelledData != null) {
if (mParcelledData.hasFileDescriptors()) {
fdFound = true;
@@ -390,8 +315,7 @@
ArrayList array = (ArrayList) obj;
// an ArrayList here might contain either Strings or
// Parcelables; only look inside for Parcelables
- if ((array.size() > 0)
- && (array.get(0) instanceof Parcelable)) {
+ if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) {
for (int n = array.size() - 1; n >= 0; n--) {
Parcelable p = (Parcelable) array.get(n);
if (p != null && ((p.describeContents()
@@ -410,7 +334,7 @@
}
return mHasFds;
}
-
+
/**
* Inserts a Boolean value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
@@ -418,9 +342,9 @@
* @param key a String, or null
* @param value a Boolean, or null
*/
+ @Override
public void putBoolean(String key, boolean value) {
- unparcel();
- mMap.put(key, value);
+ super.putBoolean(key, value);
}
/**
@@ -430,9 +354,9 @@
* @param key a String, or null
* @param value a byte
*/
+ @Override
public void putByte(String key, byte value) {
- unparcel();
- mMap.put(key, value);
+ super.putByte(key, value);
}
/**
@@ -442,9 +366,9 @@
* @param key a String, or null
* @param value a char, or null
*/
+ @Override
public void putChar(String key, char value) {
- unparcel();
- mMap.put(key, value);
+ super.putChar(key, value);
}
/**
@@ -454,9 +378,9 @@
* @param key a String, or null
* @param value a short
*/
+ @Override
public void putShort(String key, short value) {
- unparcel();
- mMap.put(key, value);
+ super.putShort(key, value);
}
/**
@@ -466,9 +390,9 @@
* @param key a String, or null
* @param value an int, or null
*/
+ @Override
public void putInt(String key, int value) {
- unparcel();
- mMap.put(key, value);
+ super.putInt(key, value);
}
/**
@@ -478,9 +402,9 @@
* @param key a String, or null
* @param value a long
*/
+ @Override
public void putLong(String key, long value) {
- unparcel();
- mMap.put(key, value);
+ super.putLong(key, value);
}
/**
@@ -490,9 +414,9 @@
* @param key a String, or null
* @param value a float
*/
+ @Override
public void putFloat(String key, float value) {
- unparcel();
- mMap.put(key, value);
+ super.putFloat(key, value);
}
/**
@@ -502,9 +426,9 @@
* @param key a String, or null
* @param value a double
*/
+ @Override
public void putDouble(String key, double value) {
- unparcel();
- mMap.put(key, value);
+ super.putDouble(key, value);
}
/**
@@ -514,9 +438,9 @@
* @param key a String, or null
* @param value a String, or null
*/
+ @Override
public void putString(String key, String value) {
- unparcel();
- mMap.put(key, value);
+ super.putString(key, value);
}
/**
@@ -526,9 +450,9 @@
* @param key a String, or null
* @param value a CharSequence, or null
*/
+ @Override
public void putCharSequence(String key, CharSequence value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharSequence(key, value);
}
/**
@@ -567,7 +491,7 @@
* @param value an ArrayList of Parcelable objects, or null
*/
public void putParcelableArrayList(String key,
- ArrayList<? extends Parcelable> value) {
+ ArrayList<? extends Parcelable> value) {
unparcel();
mMap.put(key, value);
mFdsKnown = false;
@@ -602,9 +526,9 @@
* @param key a String, or null
* @param value an ArrayList<Integer> object, or null
*/
+ @Override
public void putIntegerArrayList(String key, ArrayList<Integer> value) {
- unparcel();
- mMap.put(key, value);
+ super.putIntegerArrayList(key, value);
}
/**
@@ -614,9 +538,9 @@
* @param key a String, or null
* @param value an ArrayList<String> object, or null
*/
+ @Override
public void putStringArrayList(String key, ArrayList<String> value) {
- unparcel();
- mMap.put(key, value);
+ super.putStringArrayList(key, value);
}
/**
@@ -626,9 +550,9 @@
* @param key a String, or null
* @param value an ArrayList<CharSequence> object, or null
*/
+ @Override
public void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharSequenceArrayList(key, value);
}
/**
@@ -638,9 +562,9 @@
* @param key a String, or null
* @param value a Serializable object, or null
*/
+ @Override
public void putSerializable(String key, Serializable value) {
- unparcel();
- mMap.put(key, value);
+ super.putSerializable(key, value);
}
/**
@@ -650,9 +574,9 @@
* @param key a String, or null
* @param value a boolean array object, or null
*/
+ @Override
public void putBooleanArray(String key, boolean[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putBooleanArray(key, value);
}
/**
@@ -662,9 +586,9 @@
* @param key a String, or null
* @param value a byte array object, or null
*/
+ @Override
public void putByteArray(String key, byte[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putByteArray(key, value);
}
/**
@@ -674,9 +598,9 @@
* @param key a String, or null
* @param value a short array object, or null
*/
+ @Override
public void putShortArray(String key, short[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putShortArray(key, value);
}
/**
@@ -686,9 +610,9 @@
* @param key a String, or null
* @param value a char array object, or null
*/
+ @Override
public void putCharArray(String key, char[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharArray(key, value);
}
/**
@@ -698,9 +622,9 @@
* @param key a String, or null
* @param value an int array object, or null
*/
+ @Override
public void putIntArray(String key, int[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putIntArray(key, value);
}
/**
@@ -710,9 +634,9 @@
* @param key a String, or null
* @param value a long array object, or null
*/
+ @Override
public void putLongArray(String key, long[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putLongArray(key, value);
}
/**
@@ -722,9 +646,9 @@
* @param key a String, or null
* @param value a float array object, or null
*/
+ @Override
public void putFloatArray(String key, float[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putFloatArray(key, value);
}
/**
@@ -734,9 +658,9 @@
* @param key a String, or null
* @param value a double array object, or null
*/
+ @Override
public void putDoubleArray(String key, double[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putDoubleArray(key, value);
}
/**
@@ -746,9 +670,9 @@
* @param key a String, or null
* @param value a String array object, or null
*/
+ @Override
public void putStringArray(String key, String[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putStringArray(key, value);
}
/**
@@ -758,9 +682,9 @@
* @param key a String, or null
* @param value a CharSequence array object, or null
*/
+ @Override
public void putCharSequenceArray(String key, CharSequence[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharSequenceArray(key, value);
}
/**
@@ -776,6 +700,17 @@
}
/**
+ * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ public void putPersistableBundle(String key, PersistableBundle value) {
+ super.putPersistableBundle(key, value);
+ }
+
+ /**
* Inserts an {@link IBinder} value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -817,33 +752,9 @@
* @param key a String
* @return a boolean value
*/
+ @Override
public boolean getBoolean(String key) {
- unparcel();
- if (DEBUG) Log.d(TAG, "Getting boolean in "
- + Integer.toHexString(System.identityHashCode(this)));
- return getBoolean(key, false);
- }
-
- // Log a message if the value was non-null but not of the expected type
- private void typeWarning(String key, Object value, String className,
- Object defaultValue, ClassCastException e) {
- StringBuilder sb = new StringBuilder();
- sb.append("Key ");
- sb.append(key);
- sb.append(" expected ");
- sb.append(className);
- sb.append(" but value was a ");
- sb.append(value.getClass().getName());
- sb.append(". The default value ");
- sb.append(defaultValue);
- sb.append(" was returned.");
- Log.w(TAG, sb.toString());
- Log.w(TAG, "Attempt to cast generated internal exception:", e);
- }
-
- private void typeWarning(String key, Object value, String className,
- ClassCastException e) {
- typeWarning(key, value, className, "<null>", e);
+ return super.getBoolean(key);
}
/**
@@ -854,18 +765,9 @@
* @param defaultValue Value to return if key does not exist
* @return a boolean value
*/
+ @Override
public boolean getBoolean(String key, boolean defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Boolean) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Boolean", defaultValue, e);
- return defaultValue;
- }
+ return super.getBoolean(key, defaultValue);
}
/**
@@ -875,9 +777,9 @@
* @param key a String
* @return a byte value
*/
+ @Override
public byte getByte(String key) {
- unparcel();
- return getByte(key, (byte) 0);
+ return super.getByte(key);
}
/**
@@ -888,18 +790,9 @@
* @param defaultValue Value to return if key does not exist
* @return a byte value
*/
+ @Override
public Byte getByte(String key, byte defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Byte) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Byte", defaultValue, e);
- return defaultValue;
- }
+ return super.getByte(key, defaultValue);
}
/**
@@ -909,9 +802,9 @@
* @param key a String
* @return a char value
*/
+ @Override
public char getChar(String key) {
- unparcel();
- return getChar(key, (char) 0);
+ return super.getChar(key);
}
/**
@@ -922,18 +815,9 @@
* @param defaultValue Value to return if key does not exist
* @return a char value
*/
+ @Override
public char getChar(String key, char defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Character) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Character", defaultValue, e);
- return defaultValue;
- }
+ return super.getChar(key, defaultValue);
}
/**
@@ -943,9 +827,9 @@
* @param key a String
* @return a short value
*/
+ @Override
public short getShort(String key) {
- unparcel();
- return getShort(key, (short) 0);
+ return super.getShort(key);
}
/**
@@ -956,18 +840,9 @@
* @param defaultValue Value to return if key does not exist
* @return a short value
*/
+ @Override
public short getShort(String key, short defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Short) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Short", defaultValue, e);
- return defaultValue;
- }
+ return super.getShort(key, defaultValue);
}
/**
@@ -977,9 +852,9 @@
* @param key a String
* @return an int value
*/
+ @Override
public int getInt(String key) {
- unparcel();
- return getInt(key, 0);
+ return super.getInt(key);
}
/**
@@ -990,18 +865,9 @@
* @param defaultValue Value to return if key does not exist
* @return an int value
*/
+ @Override
public int getInt(String key, int defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Integer) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Integer", defaultValue, e);
- return defaultValue;
- }
+ return super.getInt(key, defaultValue);
}
/**
@@ -1011,9 +877,9 @@
* @param key a String
* @return a long value
*/
+ @Override
public long getLong(String key) {
- unparcel();
- return getLong(key, 0L);
+ return super.getLong(key);
}
/**
@@ -1024,18 +890,9 @@
* @param defaultValue Value to return if key does not exist
* @return a long value
*/
+ @Override
public long getLong(String key, long defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Long) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Long", defaultValue, e);
- return defaultValue;
- }
+ return super.getLong(key, defaultValue);
}
/**
@@ -1045,9 +902,9 @@
* @param key a String
* @return a float value
*/
+ @Override
public float getFloat(String key) {
- unparcel();
- return getFloat(key, 0.0f);
+ return super.getFloat(key);
}
/**
@@ -1058,18 +915,9 @@
* @param defaultValue Value to return if key does not exist
* @return a float value
*/
+ @Override
public float getFloat(String key, float defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Float) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Float", defaultValue, e);
- return defaultValue;
- }
+ return super.getFloat(key, defaultValue);
}
/**
@@ -1079,9 +927,9 @@
* @param key a String
* @return a double value
*/
+ @Override
public double getDouble(String key) {
- unparcel();
- return getDouble(key, 0.0);
+ return super.getDouble(key);
}
/**
@@ -1092,18 +940,9 @@
* @param defaultValue Value to return if key does not exist
* @return a double value
*/
+ @Override
public double getDouble(String key, double defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Double) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Double", defaultValue, e);
- return defaultValue;
- }
+ return super.getDouble(key, defaultValue);
}
/**
@@ -1114,15 +953,9 @@
* @param key a String, or null
* @return a String value, or null
*/
+ @Override
public String getString(String key) {
- unparcel();
- final Object o = mMap.get(key);
- try {
- return (String) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "String", e);
- return null;
- }
+ return super.getString(key);
}
/**
@@ -1134,9 +967,9 @@
* @return the String value associated with the given key, or defaultValue
* if no valid String object is currently mapped to that key.
*/
+ @Override
public String getString(String key, String defaultValue) {
- final String s = getString(key);
- return (s == null) ? defaultValue : s;
+ return super.getString(key, defaultValue);
}
/**
@@ -1147,15 +980,9 @@
* @param key a String, or null
* @return a CharSequence value, or null
*/
+ @Override
public CharSequence getCharSequence(String key) {
- unparcel();
- final Object o = mMap.get(key);
- try {
- return (CharSequence) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "CharSequence", e);
- return null;
- }
+ return super.getCharSequence(key);
}
/**
@@ -1167,9 +994,9 @@
* @return the CharSequence value associated with the given key, or defaultValue
* if no valid CharSequence object is currently mapped to that key.
*/
+ @Override
public CharSequence getCharSequence(String key, CharSequence defaultValue) {
- final CharSequence cs = getCharSequence(key);
- return (cs == null) ? defaultValue : cs;
+ return super.getCharSequence(key, defaultValue);
}
/**
@@ -1200,6 +1027,18 @@
* value is explicitly associated with the key.
*
* @param key a String, or null
+ * @return a PersistableBundle value, or null
+ */
+ public PersistableBundle getPersistableBundle(String key) {
+ return super.getPersistableBundle(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
* @return a Parcelable value, or null
*/
public <T extends Parcelable> T getParcelable(String key) {
@@ -1291,18 +1130,9 @@
* @param key a String, or null
* @return a Serializable value, or null
*/
+ @Override
public Serializable getSerializable(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (Serializable) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Serializable", e);
- return null;
- }
+ return super.getSerializable(key);
}
/**
@@ -1313,18 +1143,9 @@
* @param key a String, or null
* @return an ArrayList<String> value, or null
*/
+ @Override
public ArrayList<Integer> getIntegerArrayList(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<Integer>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<Integer>", e);
- return null;
- }
+ return super.getIntegerArrayList(key);
}
/**
@@ -1335,18 +1156,9 @@
* @param key a String, or null
* @return an ArrayList<String> value, or null
*/
+ @Override
public ArrayList<String> getStringArrayList(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<String>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<String>", e);
- return null;
- }
+ return super.getStringArrayList(key);
}
/**
@@ -1357,18 +1169,9 @@
* @param key a String, or null
* @return an ArrayList<CharSequence> value, or null
*/
+ @Override
public ArrayList<CharSequence> getCharSequenceArrayList(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<CharSequence>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<CharSequence>", e);
- return null;
- }
+ return super.getCharSequenceArrayList(key);
}
/**
@@ -1379,18 +1182,9 @@
* @param key a String, or null
* @return a boolean[] value, or null
*/
+ @Override
public boolean[] getBooleanArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (boolean[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "byte[]", e);
- return null;
- }
+ return super.getBooleanArray(key);
}
/**
@@ -1401,18 +1195,9 @@
* @param key a String, or null
* @return a byte[] value, or null
*/
+ @Override
public byte[] getByteArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (byte[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "byte[]", e);
- return null;
- }
+ return super.getByteArray(key);
}
/**
@@ -1423,18 +1208,9 @@
* @param key a String, or null
* @return a short[] value, or null
*/
+ @Override
public short[] getShortArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (short[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "short[]", e);
- return null;
- }
+ return super.getShortArray(key);
}
/**
@@ -1445,18 +1221,9 @@
* @param key a String, or null
* @return a char[] value, or null
*/
+ @Override
public char[] getCharArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (char[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "char[]", e);
- return null;
- }
+ return super.getCharArray(key);
}
/**
@@ -1467,18 +1234,9 @@
* @param key a String, or null
* @return an int[] value, or null
*/
+ @Override
public int[] getIntArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (int[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "int[]", e);
- return null;
- }
+ return super.getIntArray(key);
}
/**
@@ -1489,18 +1247,9 @@
* @param key a String, or null
* @return a long[] value, or null
*/
+ @Override
public long[] getLongArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (long[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "long[]", e);
- return null;
- }
+ return super.getLongArray(key);
}
/**
@@ -1511,18 +1260,9 @@
* @param key a String, or null
* @return a float[] value, or null
*/
+ @Override
public float[] getFloatArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (float[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "float[]", e);
- return null;
- }
+ return super.getFloatArray(key);
}
/**
@@ -1533,18 +1273,9 @@
* @param key a String, or null
* @return a double[] value, or null
*/
+ @Override
public double[] getDoubleArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (double[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "double[]", e);
- return null;
- }
+ return super.getDoubleArray(key);
}
/**
@@ -1555,18 +1286,9 @@
* @param key a String, or null
* @return a String[] value, or null
*/
+ @Override
public String[] getStringArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (String[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "String[]", e);
- return null;
- }
+ return super.getStringArray(key);
}
/**
@@ -1577,18 +1299,9 @@
* @param key a String, or null
* @return a CharSequence[] value, or null
*/
+ @Override
public CharSequence[] getCharSequenceArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (CharSequence[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "CharSequence[]", e);
- return null;
- }
+ return super.getCharSequenceArray(key);
}
/**
@@ -1641,10 +1354,12 @@
public static final Parcelable.Creator<Bundle> CREATOR =
new Parcelable.Creator<Bundle>() {
+ @Override
public Bundle createFromParcel(Parcel in) {
return in.readBundle();
}
+ @Override
public Bundle[] newArray(int size) {
return new Bundle[size];
}
@@ -1653,6 +1368,7 @@
/**
* Report the nature of this Parcelable's contents
*/
+ @Override
public int describeContents() {
int mask = 0;
if (hasFileDescriptors()) {
@@ -1660,44 +1376,17 @@
}
return mask;
}
-
+
/**
* Writes the Bundle contents to a Parcel, typically in order for
* it to be passed through an IBinder connection.
* @param parcel The parcel to copy this bundle to.
*/
+ @Override
public void writeToParcel(Parcel parcel, int flags) {
final boolean oldAllowFds = parcel.pushAllowFds(mAllowFds);
try {
- if (mParcelledData != null) {
- if (mParcelledData == EMPTY_PARCEL) {
- parcel.writeInt(0);
- } else {
- int length = mParcelledData.dataSize();
- parcel.writeInt(length);
- parcel.writeInt(BUNDLE_MAGIC);
- parcel.appendFrom(mParcelledData, 0, length);
- }
- } else {
- // Special case for empty bundles.
- if (mMap == null || mMap.size() <= 0) {
- parcel.writeInt(0);
- return;
- }
- int lengthPos = parcel.dataPosition();
- parcel.writeInt(-1); // dummy, will hold length
- parcel.writeInt(BUNDLE_MAGIC);
-
- int startPos = parcel.dataPosition();
- parcel.writeArrayMapInternal(mMap);
- int endPos = parcel.dataPosition();
-
- // Backpatch length
- parcel.setDataPosition(lengthPos);
- int length = endPos - startPos;
- parcel.writeInt(length);
- parcel.setDataPosition(endPos);
- }
+ super.writeToParcelInner(parcel, flags);
} finally {
parcel.restoreAllowFds(oldAllowFds);
}
@@ -1709,41 +1398,8 @@
* @param parcel The parcel to overwrite this bundle from.
*/
public void readFromParcel(Parcel parcel) {
- int length = parcel.readInt();
- if (length < 0) {
- throw new RuntimeException("Bad length in parcel: " + length);
- }
- readFromParcelInner(parcel, length);
- }
-
- void readFromParcelInner(Parcel parcel, int length) {
- if (length == 0) {
- // Empty Bundle or end of data.
- mParcelledData = EMPTY_PARCEL;
- mHasFds = false;
- mFdsKnown = true;
- return;
- }
- int magic = parcel.readInt();
- if (magic != BUNDLE_MAGIC) {
- //noinspection ThrowableInstanceNeverThrown
- throw new IllegalStateException("Bad magic number for Bundle: 0x"
- + Integer.toHexString(magic));
- }
-
- // Advance within this Parcel
- int offset = parcel.dataPosition();
- parcel.setDataPosition(offset + length);
-
- Parcel p = Parcel.obtain();
- p.setDataPosition(0);
- p.appendFrom(parcel, offset, length);
- if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this))
- + ": " + length + " bundle bytes starting at " + offset);
- p.setDataPosition(0);
-
- mParcelledData = p;
- mHasFds = p.hasFileDescriptors();
+ super.readFromParcelInner(parcel);
+ mHasFds = mParcelledData.hasFileDescriptors();
mFdsKnown = true;
}
@@ -1759,4 +1415,5 @@
}
return "Bundle[" + mMap.toString() + "]";
}
+
}
diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/CommonBundle.java
new file mode 100644
index 0000000..e11f170
--- /dev/null
+++ b/core/java/android/os/CommonBundle.java
@@ -0,0 +1,1384 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A mapping from String values to various types.
+ */
+abstract class CommonBundle implements Parcelable, Cloneable {
+ private static final String TAG = "Bundle";
+ static final boolean DEBUG = false;
+
+ static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
+ static final Parcel EMPTY_PARCEL;
+
+ static {
+ EMPTY_PARCEL = Parcel.obtain();
+ }
+
+ // Invariant - exactly one of mMap / mParcelledData will be null
+ // (except inside a call to unparcel)
+
+ ArrayMap<String, Object> mMap = null;
+
+ /*
+ * If mParcelledData is non-null, then mMap will be null and the
+ * data are stored as a Parcel containing a Bundle. When the data
+ * are unparcelled, mParcelledData willbe set to null.
+ */
+ Parcel mParcelledData = null;
+
+ /**
+ * The ClassLoader used when unparcelling data from mParcelledData.
+ */
+ private ClassLoader mClassLoader;
+
+ /**
+ * Constructs a new, empty Bundle that uses a specific ClassLoader for
+ * instantiating Parcelable and Serializable objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the Bundle.
+ * @param capacity Initial size of the ArrayMap.
+ */
+ CommonBundle(ClassLoader loader, int capacity) {
+ mMap = capacity > 0 ?
+ new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
+ mClassLoader = loader == null ? getClass().getClassLoader() : loader;
+ }
+
+ /**
+ * Constructs a new, empty Bundle.
+ */
+ CommonBundle() {
+ this((ClassLoader) null, 0);
+ }
+
+ /**
+ * Constructs a Bundle whose data is stored as a Parcel. The data
+ * will be unparcelled on first contact, using the assigned ClassLoader.
+ *
+ * @param parcelledData a Parcel containing a Bundle
+ */
+ CommonBundle(Parcel parcelledData) {
+ readFromParcelInner(parcelledData);
+ }
+
+ CommonBundle(Parcel parcelledData, int length) {
+ readFromParcelInner(parcelledData, length);
+ }
+
+ /**
+ * Constructs a new, empty Bundle that uses a specific ClassLoader for
+ * instantiating Parcelable and Serializable objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the Bundle.
+ */
+ CommonBundle(ClassLoader loader) {
+ this(loader, 0);
+ }
+
+ /**
+ * Constructs a new, empty Bundle sized to hold the given number of
+ * elements. The Bundle will grow as needed.
+ *
+ * @param capacity the initial capacity of the Bundle
+ */
+ CommonBundle(int capacity) {
+ this((ClassLoader) null, capacity);
+ }
+
+ /**
+ * Constructs a Bundle containing a copy of the mappings from the given
+ * Bundle.
+ *
+ * @param b a Bundle to be copied.
+ */
+ CommonBundle(CommonBundle b) {
+ if (b.mParcelledData != null) {
+ if (b.mParcelledData == EMPTY_PARCEL) {
+ mParcelledData = EMPTY_PARCEL;
+ } else {
+ mParcelledData = Parcel.obtain();
+ mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
+ mParcelledData.setDataPosition(0);
+ }
+ } else {
+ mParcelledData = null;
+ }
+
+ if (b.mMap != null) {
+ mMap = new ArrayMap<String, Object>(b.mMap);
+ } else {
+ mMap = null;
+ }
+
+ mClassLoader = b.mClassLoader;
+ }
+
+ /**
+ * TODO: optimize this later (getting just the value part of a Bundle
+ * with a single pair) once Bundle.forPair() above is implemented
+ * with a special single-value Map implementation/serialization.
+ *
+ * Note: value in single-pair Bundle may be null.
+ *
+ * @hide
+ */
+ String getPairValue() {
+ unparcel();
+ int size = mMap.size();
+ if (size > 1) {
+ Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
+ }
+ if (size == 0) {
+ return null;
+ }
+ Object o = mMap.valueAt(0);
+ try {
+ return (String) o;
+ } catch (ClassCastException e) {
+ typeWarning("getPairValue()", o, "String", e);
+ return null;
+ }
+ }
+
+ /**
+ * Changes the ClassLoader this Bundle uses when instantiating objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the Bundle.
+ */
+ void setClassLoader(ClassLoader loader) {
+ mClassLoader = loader;
+ }
+
+ /**
+ * Return the ClassLoader currently associated with this Bundle.
+ */
+ ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * If the underlying data are stored as a Parcel, unparcel them
+ * using the currently assigned class loader.
+ */
+ /* package */ synchronized void unparcel() {
+ if (mParcelledData == null) {
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": no parcelled data");
+ return;
+ }
+
+ if (mParcelledData == EMPTY_PARCEL) {
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": empty");
+ if (mMap == null) {
+ mMap = new ArrayMap<String, Object>(1);
+ } else {
+ mMap.erase();
+ }
+ mParcelledData = null;
+ return;
+ }
+
+ int N = mParcelledData.readInt();
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": reading " + N + " maps");
+ if (N < 0) {
+ return;
+ }
+ if (mMap == null) {
+ mMap = new ArrayMap<String, Object>(N);
+ } else {
+ mMap.erase();
+ mMap.ensureCapacity(N);
+ }
+ mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
+ mParcelledData.recycle();
+ mParcelledData = null;
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + " final map: " + mMap);
+ }
+
+ /**
+ * @hide
+ */
+ boolean isParcelled() {
+ return mParcelledData != null;
+ }
+
+ /**
+ * Returns the number of mappings contained in this Bundle.
+ *
+ * @return the number of mappings as an int.
+ */
+ int size() {
+ unparcel();
+ return mMap.size();
+ }
+
+ /**
+ * Returns true if the mapping of this Bundle is empty, false otherwise.
+ */
+ boolean isEmpty() {
+ unparcel();
+ return mMap.isEmpty();
+ }
+
+ /**
+ * Removes all elements from the mapping of this Bundle.
+ */
+ void clear() {
+ unparcel();
+ mMap.clear();
+ }
+
+ /**
+ * Returns true if the given key is contained in the mapping
+ * of this Bundle.
+ *
+ * @param key a String key
+ * @return true if the key is part of the mapping, false otherwise
+ */
+ boolean containsKey(String key) {
+ unparcel();
+ return mMap.containsKey(key);
+ }
+
+ /**
+ * Returns the entry with the given key as an object.
+ *
+ * @param key a String key
+ * @return an Object, or null
+ */
+ Object get(String key) {
+ unparcel();
+ return mMap.get(key);
+ }
+
+ /**
+ * Removes any entry with the given key from the mapping of this Bundle.
+ *
+ * @param key a String key
+ */
+ void remove(String key) {
+ unparcel();
+ mMap.remove(key);
+ }
+
+ /**
+ * Inserts all mappings from the given PersistableBundle into this CommonBundle.
+ *
+ * @param bundle a PersistableBundle
+ */
+ void putAll(PersistableBundle bundle) {
+ unparcel();
+ bundle.unparcel();
+ mMap.putAll(bundle.mMap);
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this Bundle.
+ *
+ * @return a Set of String keys
+ */
+ Set<String> keySet() {
+ unparcel();
+ return mMap.keySet();
+ }
+
+ /**
+ * Inserts a Boolean value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Boolean, or null
+ */
+ void putBoolean(String key, boolean value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a byte value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a byte
+ */
+ void putByte(String key, byte value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a char value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a char, or null
+ */
+ void putChar(String key, char value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a short value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a short
+ */
+ void putShort(String key, short value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an int value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value an int, or null
+ */
+ void putInt(String key, int value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a long value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a long
+ */
+ void putLong(String key, long value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a float value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a float
+ */
+ void putFloat(String key, float value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a double value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a double
+ */
+ void putDouble(String key, double value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a String value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String, or null
+ */
+ void putString(String key, String value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a CharSequence value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence, or null
+ */
+ void putCharSequence(String key, CharSequence value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<Integer> object, or null
+ */
+ void putIntegerArrayList(String key, ArrayList<Integer> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<String> object, or null
+ */
+ void putStringArrayList(String key, ArrayList<String> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<CharSequence> object, or null
+ */
+ void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a Serializable value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Serializable object, or null
+ */
+ void putSerializable(String key, Serializable value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a boolean array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a boolean array object, or null
+ */
+ void putBooleanArray(String key, boolean[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a byte array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a byte array object, or null
+ */
+ void putByteArray(String key, byte[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a short array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a short array object, or null
+ */
+ void putShortArray(String key, short[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a char array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a char array object, or null
+ */
+ void putCharArray(String key, char[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an int array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an int array object, or null
+ */
+ void putIntArray(String key, int[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a long array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a long array object, or null
+ */
+ void putLongArray(String key, long[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a float array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a float array object, or null
+ */
+ void putFloatArray(String key, float[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a double array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a double array object, or null
+ */
+ void putDoubleArray(String key, double[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a String array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String array object, or null
+ */
+ void putStringArray(String key, String[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence array object, or null
+ */
+ void putCharSequenceArray(String key, CharSequence[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ void putPersistableBundle(String key, PersistableBundle value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Returns the value associated with the given key, or false if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a boolean value
+ */
+ boolean getBoolean(String key) {
+ unparcel();
+ if (DEBUG) Log.d(TAG, "Getting boolean in "
+ + Integer.toHexString(System.identityHashCode(this)));
+ return getBoolean(key, false);
+ }
+
+ // Log a message if the value was non-null but not of the expected type
+ void typeWarning(String key, Object value, String className,
+ Object defaultValue, ClassCastException e) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Key ");
+ sb.append(key);
+ sb.append(" expected ");
+ sb.append(className);
+ sb.append(" but value was a ");
+ sb.append(value.getClass().getName());
+ sb.append(". The default value ");
+ sb.append(defaultValue);
+ sb.append(" was returned.");
+ Log.w(TAG, sb.toString());
+ Log.w(TAG, "Attempt to cast generated internal exception:", e);
+ }
+
+ void typeWarning(String key, Object value, String className,
+ ClassCastException e) {
+ typeWarning(key, value, className, "<null>", e);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a boolean value
+ */
+ boolean getBoolean(String key, boolean defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Boolean) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Boolean", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or (byte) 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a byte value
+ */
+ byte getByte(String key) {
+ unparcel();
+ return getByte(key, (byte) 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a byte value
+ */
+ Byte getByte(String key, byte defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Byte) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Byte", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or (char) 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a char value
+ */
+ char getChar(String key) {
+ unparcel();
+ return getChar(key, (char) 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a char value
+ */
+ char getChar(String key, char defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Character) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Character", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or (short) 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a short value
+ */
+ short getShort(String key) {
+ unparcel();
+ return getShort(key, (short) 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a short value
+ */
+ short getShort(String key, short defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Short) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Short", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return an int value
+ */
+ int getInt(String key) {
+ unparcel();
+ return getInt(key, 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return an int value
+ */
+ int getInt(String key, int defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Integer) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Integer", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a long value
+ */
+ long getLong(String key) {
+ unparcel();
+ return getLong(key, 0L);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a long value
+ */
+ long getLong(String key, long defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Long) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Long", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0.0f if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a float value
+ */
+ float getFloat(String key) {
+ unparcel();
+ return getFloat(key, 0.0f);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a float value
+ */
+ float getFloat(String key, float defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Float) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Float", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0.0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a double value
+ */
+ double getDouble(String key) {
+ unparcel();
+ return getDouble(key, 0.0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a double value
+ */
+ double getDouble(String key, double defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Double) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Double", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String value, or null
+ */
+ String getString(String key) {
+ unparcel();
+ final Object o = mMap.get(key);
+ try {
+ return (String) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "String", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String, or null
+ * @param defaultValue Value to return if key does not exist
+ * @return the String value associated with the given key, or defaultValue
+ * if no valid String object is currently mapped to that key.
+ */
+ String getString(String key, String defaultValue) {
+ final String s = getString(key);
+ return (s == null) ? defaultValue : s;
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a CharSequence value, or null
+ */
+ CharSequence getCharSequence(String key) {
+ unparcel();
+ final Object o = mMap.get(key);
+ try {
+ return (CharSequence) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "CharSequence", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String, or null
+ * @param defaultValue Value to return if key does not exist
+ * @return the CharSequence value associated with the given key, or defaultValue
+ * if no valid CharSequence object is currently mapped to that key.
+ */
+ CharSequence getCharSequence(String key, CharSequence defaultValue) {
+ final CharSequence cs = getCharSequence(key);
+ return (cs == null) ? defaultValue : cs;
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a Bundle value, or null
+ */
+ PersistableBundle getPersistableBundle(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (PersistableBundle) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Bundle", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a Serializable value, or null
+ */
+ Serializable getSerializable(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (Serializable) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Serializable", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an ArrayList<String> value, or null
+ */
+ ArrayList<Integer> getIntegerArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<Integer>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<Integer>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an ArrayList<String> value, or null
+ */
+ ArrayList<String> getStringArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<String>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<String>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an ArrayList<CharSequence> value, or null
+ */
+ ArrayList<CharSequence> getCharSequenceArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<CharSequence>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<CharSequence>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a boolean[] value, or null
+ */
+ boolean[] getBooleanArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (boolean[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "byte[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a byte[] value, or null
+ */
+ byte[] getByteArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (byte[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "byte[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a short[] value, or null
+ */
+ short[] getShortArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (short[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "short[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a char[] value, or null
+ */
+ char[] getCharArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (char[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "char[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an int[] value, or null
+ */
+ int[] getIntArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (int[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "int[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a long[] value, or null
+ */
+ long[] getLongArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (long[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "long[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a float[] value, or null
+ */
+ float[] getFloatArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (float[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "float[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a double[] value, or null
+ */
+ double[] getDoubleArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (double[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "double[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String[] value, or null
+ */
+ String[] getStringArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (String[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "String[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a CharSequence[] value, or null
+ */
+ CharSequence[] getCharSequenceArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (CharSequence[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "CharSequence[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Writes the Bundle contents to a Parcel, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to copy this bundle to.
+ */
+ void writeToParcelInner(Parcel parcel, int flags) {
+ if (mParcelledData != null) {
+ if (mParcelledData == EMPTY_PARCEL) {
+ parcel.writeInt(0);
+ } else {
+ int length = mParcelledData.dataSize();
+ parcel.writeInt(length);
+ parcel.writeInt(BUNDLE_MAGIC);
+ parcel.appendFrom(mParcelledData, 0, length);
+ }
+ } else {
+ // Special case for empty bundles.
+ if (mMap == null || mMap.size() <= 0) {
+ parcel.writeInt(0);
+ return;
+ }
+ int lengthPos = parcel.dataPosition();
+ parcel.writeInt(-1); // dummy, will hold length
+ parcel.writeInt(BUNDLE_MAGIC);
+
+ int startPos = parcel.dataPosition();
+ parcel.writeArrayMapInternal(mMap);
+ int endPos = parcel.dataPosition();
+
+ // Backpatch length
+ parcel.setDataPosition(lengthPos);
+ int length = endPos - startPos;
+ parcel.writeInt(length);
+ parcel.setDataPosition(endPos);
+ }
+ }
+
+ /**
+ * Reads the Parcel contents into this Bundle, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to overwrite this bundle from.
+ */
+ void readFromParcelInner(Parcel parcel) {
+ int length = parcel.readInt();
+ if (length < 0) {
+ throw new RuntimeException("Bad length in parcel: " + length);
+ }
+ readFromParcelInner(parcel, length);
+ }
+
+ private void readFromParcelInner(Parcel parcel, int length) {
+ if (length == 0) {
+ // Empty Bundle or end of data.
+ mParcelledData = EMPTY_PARCEL;
+ return;
+ }
+ int magic = parcel.readInt();
+ if (magic != BUNDLE_MAGIC) {
+ //noinspection ThrowableInstanceNeverThrown
+ throw new IllegalStateException("Bad magic number for Bundle: 0x"
+ + Integer.toHexString(magic));
+ }
+
+ // Advance within this Parcel
+ int offset = parcel.dataPosition();
+ parcel.setDataPosition(offset + length);
+
+ Parcel p = Parcel.obtain();
+ p.setDataPosition(0);
+ p.appendFrom(parcel, offset, length);
+ if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this))
+ + ": " + length + " bundle bytes starting at " + offset);
+ p.setDataPosition(0);
+
+ mParcelledData = p;
+ }
+}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index dc18dee..1089f27 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -370,8 +370,8 @@
* attacks.
*/
public static boolean contains(File dir, File file) {
- String dirPath = dir.getPath();
- String filePath = file.getPath();
+ String dirPath = dir.getAbsolutePath();
+ String filePath = file.getAbsolutePath();
if (dirPath.equals(filePath)) {
return true;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 8e0ff08..95cb9f3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -223,6 +223,7 @@
private static final int VAL_SPARSEBOOLEANARRAY = 22;
private static final int VAL_BOOLEANARRAY = 23;
private static final int VAL_CHARSEQUENCEARRAY = 24;
+ private static final int VAL_PERSISTABLEBUNDLE = 25;
// The initial int32 in a Binder call's reply Parcel header:
private static final int EX_SECURITY = -1;
@@ -638,6 +639,19 @@
}
/**
+ * Flatten a PersistableBundle into the parcel at the current dataPosition(),
+ * growing dataCapacity() if needed.
+ */
+ public final void writePersistableBundle(PersistableBundle val) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+
+ val.writeToParcel(this, 0);
+ }
+
+ /**
* Flatten a List into the parcel at the current dataPosition(), growing
* dataCapacity() if needed. The List values are written using
* {@link #writeValue} and must follow the specification there.
@@ -1256,6 +1270,9 @@
} else if (v instanceof Byte) {
writeInt(VAL_BYTE);
writeInt((Byte) v);
+ } else if (v instanceof PersistableBundle) {
+ writeInt(VAL_PERSISTABLEBUNDLE);
+ writePersistableBundle((PersistableBundle) v);
} else {
Class<?> clazz = v.getClass();
if (clazz.isArray() && clazz.getComponentType() == Object.class) {
@@ -1633,6 +1650,35 @@
}
/**
+ * Read and return a new Bundle object from the parcel at the current
+ * dataPosition(). Returns null if the previously written Bundle object was
+ * null.
+ */
+ public final PersistableBundle readPersistableBundle() {
+ return readPersistableBundle(null);
+ }
+
+ /**
+ * Read and return a new Bundle object from the parcel at the current
+ * dataPosition(), using the given class loader to initialize the class
+ * loader of the Bundle for later retrieval of Parcelable objects.
+ * Returns null if the previously written Bundle object was null.
+ */
+ public final PersistableBundle readPersistableBundle(ClassLoader loader) {
+ int length = readInt();
+ if (length < 0) {
+ if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
+ return null;
+ }
+
+ final PersistableBundle bundle = new PersistableBundle(this, length);
+ if (loader != null) {
+ bundle.setClassLoader(loader);
+ }
+ return bundle;
+ }
+
+ /**
* Read and return a byte[] object from the parcel.
*/
public final byte[] createByteArray() {
@@ -2082,6 +2128,9 @@
case VAL_BUNDLE:
return readBundle(loader); // loading will be deferred
+ case VAL_PERSISTABLEBUNDLE:
+ return readPersistableBundle(loader);
+
default:
int off = dataPosition() - 4;
throw new RuntimeException(
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 86dc8b4..24bf05e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -42,6 +42,7 @@
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.nio.ByteOrder;
@@ -698,6 +699,9 @@
} catch (ErrnoException e) {
// Reporting status is best-effort
Log.w(TAG, "Failed to report status: " + e);
+ } catch (InterruptedIOException e) {
+ // Reporting status is best-effort
+ Log.w(TAG, "Failed to report status: " + e);
}
} finally {
@@ -728,6 +732,9 @@
Log.d(TAG, "Failed to read status; assuming dead: " + e);
return new Status(Status.DEAD);
}
+ } catch (InterruptedIOException e) {
+ Log.d(TAG, "Failed to read status; assuming dead: " + e);
+ return new Status(Status.DEAD);
}
}
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
new file mode 100644
index 0000000..c2cd3be
--- /dev/null
+++ b/core/java/android/os/PersistableBundle.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.ArrayMap;
+
+import java.util.Set;
+
+/**
+ * A mapping from String values to various types that can be saved to persistent and later
+ * restored.
+ *
+ */
+public final class PersistableBundle extends CommonBundle {
+ public static final PersistableBundle EMPTY;
+ static final Parcel EMPTY_PARCEL;
+
+ static {
+ EMPTY = new PersistableBundle();
+ EMPTY.mMap = ArrayMap.EMPTY;
+ EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+ }
+
+ /**
+ * Constructs a new, empty PersistableBundle.
+ */
+ public PersistableBundle() {
+ super();
+ }
+
+ /**
+ * Constructs a PersistableBundle whose data is stored as a Parcel. The data
+ * will be unparcelled on first contact, using the assigned ClassLoader.
+ *
+ * @param parcelledData a Parcel containing a PersistableBundle
+ */
+ PersistableBundle(Parcel parcelledData) {
+ super(parcelledData);
+ }
+
+ /* package */ PersistableBundle(Parcel parcelledData, int length) {
+ super(parcelledData, length);
+ }
+
+ /**
+ * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for
+ * instantiating Parcelable and Serializable objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the PersistableBundle.
+ */
+ public PersistableBundle(ClassLoader loader) {
+ super(loader);
+ }
+
+ /**
+ * Constructs a new, empty PersistableBundle sized to hold the given number of
+ * elements. The PersistableBundle will grow as needed.
+ *
+ * @param capacity the initial capacity of the PersistableBundle
+ */
+ public PersistableBundle(int capacity) {
+ super(capacity);
+ }
+
+ /**
+ * Constructs a PersistableBundle containing a copy of the mappings from the given
+ * PersistableBundle.
+ *
+ * @param b a PersistableBundle to be copied.
+ */
+ public PersistableBundle(PersistableBundle b) {
+ super(b);
+ }
+
+ /**
+ * Make a PersistableBundle for a single key/value pair.
+ *
+ * @hide
+ */
+ public static PersistableBundle forPair(String key, String value) {
+ PersistableBundle b = new PersistableBundle(1);
+ b.putString(key, value);
+ return b;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public String getPairValue() {
+ return super.getPairValue();
+ }
+
+ /**
+ * Changes the ClassLoader this PersistableBundle uses when instantiating objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the PersistableBundle.
+ */
+ @Override
+ public void setClassLoader(ClassLoader loader) {
+ super.setClassLoader(loader);
+ }
+
+ /**
+ * Return the ClassLoader currently associated with this PersistableBundle.
+ */
+ @Override
+ public ClassLoader getClassLoader() {
+ return super.getClassLoader();
+ }
+
+ /**
+ * Clones the current PersistableBundle. The internal map is cloned, but the keys and
+ * values to which it refers are copied by reference.
+ */
+ @Override
+ public Object clone() {
+ return new PersistableBundle(this);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isParcelled() {
+ return super.isParcelled();
+ }
+
+ /**
+ * Returns the number of mappings contained in this PersistableBundle.
+ *
+ * @return the number of mappings as an int.
+ */
+ @Override
+ public int size() {
+ return super.size();
+ }
+
+ /**
+ * Returns true if the mapping of this PersistableBundle is empty, false otherwise.
+ */
+ @Override
+ public boolean isEmpty() {
+ return super.isEmpty();
+ }
+
+ /**
+ * Removes all elements from the mapping of this PersistableBundle.
+ */
+ @Override
+ public void clear() {
+ super.clear();
+ }
+
+ /**
+ * Returns true if the given key is contained in the mapping
+ * of this PersistableBundle.
+ *
+ * @param key a String key
+ * @return true if the key is part of the mapping, false otherwise
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return super.containsKey(key);
+ }
+
+ /**
+ * Returns the entry with the given key as an object.
+ *
+ * @param key a String key
+ * @return an Object, or null
+ */
+ @Override
+ public Object get(String key) {
+ return super.get(key);
+ }
+
+ /**
+ * Removes any entry with the given key from the mapping of this PersistableBundle.
+ *
+ * @param key a String key
+ */
+ @Override
+ public void remove(String key) {
+ super.remove(key);
+ }
+
+ /**
+ * Inserts all mappings from the given PersistableBundle into this Bundle.
+ *
+ * @param bundle a PersistableBundle
+ */
+ public void putAll(PersistableBundle bundle) {
+ super.putAll(bundle);
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this PersistableBundle.
+ *
+ * @return a Set of String keys
+ */
+ @Override
+ public Set<String> keySet() {
+ return super.keySet();
+ }
+
+ /**
+ * Inserts an int value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value an int, or null
+ */
+ @Override
+ public void putInt(String key, int value) {
+ super.putInt(key, value);
+ }
+
+ /**
+ * Inserts a long value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a long
+ */
+ @Override
+ public void putLong(String key, long value) {
+ super.putLong(key, value);
+ }
+
+ /**
+ * Inserts a double value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a double
+ */
+ @Override
+ public void putDouble(String key, double value) {
+ super.putDouble(key, value);
+ }
+
+ /**
+ * Inserts a String value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String, or null
+ */
+ @Override
+ public void putString(String key, String value) {
+ super.putString(key, value);
+ }
+
+ /**
+ * Inserts an int array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an int array object, or null
+ */
+ @Override
+ public void putIntArray(String key, int[] value) {
+ super.putIntArray(key, value);
+ }
+
+ /**
+ * Inserts a long array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a long array object, or null
+ */
+ @Override
+ public void putLongArray(String key, long[] value) {
+ super.putLongArray(key, value);
+ }
+
+ /**
+ * Inserts a double array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a double array object, or null
+ */
+ @Override
+ public void putDoubleArray(String key, double[] value) {
+ super.putDoubleArray(key, value);
+ }
+
+ /**
+ * Inserts a String array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String array object, or null
+ */
+ @Override
+ public void putStringArray(String key, String[] value) {
+ super.putStringArray(key, value);
+ }
+
+ /**
+ * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ public void putPersistableBundle(String key, PersistableBundle value) {
+ super.putPersistableBundle(key, value);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return an int value
+ */
+ @Override
+ public int getInt(String key) {
+ return super.getInt(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return an int value
+ */
+ @Override
+ public int getInt(String key, int defaultValue) {
+ return super.getInt(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a long value
+ */
+ @Override
+ public long getLong(String key) {
+ return super.getLong(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a long value
+ */
+ @Override
+ public long getLong(String key, long defaultValue) {
+ return super.getLong(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0.0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a double value
+ */
+ @Override
+ public double getDouble(String key) {
+ return super.getDouble(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a double value
+ */
+ @Override
+ public double getDouble(String key, double defaultValue) {
+ return super.getDouble(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String value, or null
+ */
+ @Override
+ public String getString(String key) {
+ return super.getString(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String, or null
+ * @param defaultValue Value to return if key does not exist
+ * @return the String value associated with the given key, or defaultValue
+ * if no valid String object is currently mapped to that key.
+ */
+ @Override
+ public String getString(String key, String defaultValue) {
+ return super.getString(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a Bundle value, or null
+ */
+ @Override
+ public PersistableBundle getPersistableBundle(String key) {
+ return super.getPersistableBundle(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an int[] value, or null
+ */
+ @Override
+ public int[] getIntArray(String key) {
+ return super.getIntArray(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a long[] value, or null
+ */
+ @Override
+ public long[] getLongArray(String key) {
+ return super.getLongArray(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a double[] value, or null
+ */
+ @Override
+ public double[] getDoubleArray(String key) {
+ return super.getDoubleArray(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String[] value, or null
+ */
+ @Override
+ public String[] getStringArray(String key) {
+ return super.getStringArray(key);
+ }
+
+ public static final Parcelable.Creator<PersistableBundle> CREATOR =
+ new Parcelable.Creator<PersistableBundle>() {
+ @Override
+ public PersistableBundle createFromParcel(Parcel in) {
+ return in.readPersistableBundle();
+ }
+
+ @Override
+ public PersistableBundle[] newArray(int size) {
+ return new PersistableBundle[size];
+ }
+ };
+
+ /**
+ * Report the nature of this Parcelable's contents
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes the PersistableBundle contents to a Parcel, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to copy this bundle to.
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ final boolean oldAllowFds = parcel.pushAllowFds(false);
+ try {
+ super.writeToParcelInner(parcel, flags);
+ } finally {
+ parcel.restoreAllowFds(oldAllowFds);
+ }
+ }
+
+ /**
+ * Reads the Parcel contents into this PersistableBundle, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to overwrite this bundle from.
+ */
+ public void readFromParcel(Parcel parcel) {
+ super.readFromParcelInner(parcel);
+ }
+
+ @Override
+ synchronized public String toString() {
+ if (mParcelledData != null) {
+ if (mParcelledData == EMPTY_PARCEL) {
+ return "PersistableBundle[EMPTY_PARCEL]";
+ } else {
+ return "PersistableBundle[mParcelledData.dataSize=" +
+ mParcelledData.dataSize() + "]";
+ }
+ }
+ return "PersistableBundle[" + mMap.toString() + "]";
+ }
+
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 8aef9bd..7bac3af 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,7 +300,8 @@
/**
* Sets all the user-wide restrictions for this user.
- * Requires the MANAGE_USERS permission.
+ * Requires the MANAGE_USERS permission or profile/device owner
+ * privileges.
* @param restrictions the Bundle containing all the restrictions.
*/
public void setUserRestrictions(Bundle restrictions) {
@@ -309,7 +310,8 @@
/**
* Sets all the user-wide restrictions for the specified user.
- * Requires the MANAGE_USERS permission.
+ * Requires the MANAGE_USERS permission or profile/device owner
+ * privileges.
* @param restrictions the Bundle containing all the restrictions.
* @param userHandle the UserHandle of the user for whom to set the restrictions.
*/
@@ -323,7 +325,8 @@
/**
* Sets the value of a specific restriction.
- * Requires the MANAGE_USERS permission.
+ * Requires the MANAGE_USERS permission or profile/device owner
+ * privileges.
* @param key the key of the restriction
* @param value the value for the restriction
*/
@@ -336,7 +339,8 @@
/**
* @hide
* Sets the value of a specific restriction on a specific user.
- * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission or profile/device owner
+ * privileges.
* @param key the key of the restriction
* @param value the value for the restriction
* @param userHandle the user whose restriction is to be changed.
@@ -548,7 +552,7 @@
/**
* Returns information for all users on this device. Requires
* {@link android.Manifest.permission#MANAGE_USERS} permission.
- *
+ *
* @param excludeDying specify if the list should exclude users being
* removed.
* @return the list of users that were created.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index f0520b5..9a768e0 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -57,6 +57,10 @@
* <p>
* To create a document provider, extend {@link DocumentsProvider}, which
* provides a foundational implementation of this contract.
+ * <p>
+ * All client apps must hold a valid URI permission grant to access documents,
+ * typically issued when a user makes a selection through
+ * {@link Intent#ACTION_OPEN_DOCUMENT} or {@link Intent#ACTION_CREATE_DOCUMENT}.
*
* @see DocumentsProvider
*/
@@ -69,6 +73,8 @@
// content://com.example/root/sdcard/search/?query=pony
// content://com.example/document/12/
// content://com.example/document/12/children/
+ // content://com.example/via/12/document/24/
+ // content://com.example/via/12/document/24/children/
private DocumentsContract() {
}
@@ -425,6 +431,14 @@
public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
/**
+ * Flag indicating that this root supports directory selection.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsProvider#isChildDocument(String, String)
+ */
+ public static final int FLAG_SUPPORTS_DIR_SELECTION = 1 << 4;
+
+ /**
* Flag indicating that this root is currently empty. This may be used
* to hide the root when opening documents, but the root will still be
* shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
@@ -484,12 +498,15 @@
/** {@hide} */
public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+ /** {@hide} */
+ public static final String EXTRA_URI = "uri";
private static final String PATH_ROOT = "root";
private static final String PATH_RECENT = "recent";
private static final String PATH_DOCUMENT = "document";
private static final String PATH_CHILDREN = "children";
private static final String PATH_SEARCH = "search";
+ private static final String PATH_VIA = "via";
private static final String PARAM_QUERY = "query";
private static final String PARAM_MANAGE = "manage";
@@ -532,6 +549,17 @@
}
/**
+ * Build URI representing access to descendant documents of the given
+ * {@link Document#COLUMN_DOCUMENT_ID}.
+ *
+ * @see #getViaDocumentId(Uri)
+ */
+ public static Uri buildViaUri(String authority, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_VIA).appendPath(documentId).build();
+ }
+
+ /**
* Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
* document provider. When queried, a provider will return a single row with
* columns defined by {@link Document}.
@@ -545,6 +573,41 @@
}
/**
+ * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+ * document provider. Instead of directly accessing the target document,
+ * gain access via another document. The target document must be a
+ * descendant (child, grandchild, etc) of the via document.
+ * <p>
+ * This is typically used to access documents under a user-selected
+ * directory, since it doesn't require the user to separately confirm each
+ * new document access.
+ *
+ * @param viaUri a related document (directory) that the caller is
+ * leveraging to gain access to the target document. The target
+ * document must be a descendant of this directory.
+ * @param documentId the target document, which the caller may not have
+ * direct access to.
+ * @see Intent#ACTION_PICK_DIRECTORY
+ * @see DocumentsProvider#isChildDocument(String, String)
+ * @see #buildDocumentUri(String, String)
+ */
+ public static Uri buildDocumentViaUri(Uri viaUri, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(viaUri.getAuthority()).appendPath(PATH_VIA)
+ .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
+ .appendPath(documentId).build();
+ }
+
+ /** {@hide} */
+ public static Uri buildDocumentMaybeViaUri(Uri baseUri, String documentId) {
+ if (isViaUri(baseUri)) {
+ return buildDocumentViaUri(baseUri, documentId);
+ } else {
+ return buildDocumentUri(baseUri.getAuthority(), documentId);
+ }
+ }
+
+ /**
* Build URI representing the children of the given directory in a document
* provider. When queried, a provider will return zero or more rows with
* columns defined by {@link Document}.
@@ -562,6 +625,32 @@
}
/**
+ * Build URI representing the children of the given directory in a document
+ * provider. Instead of directly accessing the target document, gain access
+ * via another document. The target document must be a descendant (child,
+ * grandchild, etc) of the via document.
+ * <p>
+ * This is typically used to access documents under a user-selected
+ * directory, since it doesn't require the user to separately confirm each
+ * new document access.
+ *
+ * @param viaUri a related document (directory) that the caller is
+ * leveraging to gain access to the target document. The target
+ * document must be a descendant of this directory.
+ * @param parentDocumentId the target document, which the caller may not
+ * have direct access to.
+ * @see Intent#ACTION_PICK_DIRECTORY
+ * @see DocumentsProvider#isChildDocument(String, String)
+ * @see #buildChildDocumentsUri(String, String)
+ */
+ public static Uri buildChildDocumentsViaUri(Uri viaUri, String parentDocumentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(viaUri.getAuthority()).appendPath(PATH_VIA)
+ .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
+ .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
+ }
+
+ /**
* Build URI representing a search for matching documents under a specific
* root in a document provider. When queried, a provider will return zero or
* more rows with columns defined by {@link Document}.
@@ -580,21 +669,31 @@
/**
* Test if the given URI represents a {@link Document} backed by a
* {@link DocumentsProvider}.
+ *
+ * @see #buildDocumentUri(String, String)
+ * @see #buildDocumentViaUri(Uri, String)
*/
public static boolean isDocumentUri(Context context, Uri uri) {
final List<String> paths = uri.getPathSegments();
- if (paths.size() < 2) {
- return false;
+ if (paths.size() >= 2
+ && (PATH_DOCUMENT.equals(paths.get(0)) || PATH_VIA.equals(paths.get(0)))) {
+ return isDocumentsProvider(context, uri.getAuthority());
}
- if (!PATH_DOCUMENT.equals(paths.get(0))) {
- return false;
- }
+ return false;
+ }
+ /** {@hide} */
+ public static boolean isViaUri(Uri uri) {
+ final List<String> paths = uri.getPathSegments();
+ return (paths.size() >= 2 && PATH_VIA.equals(paths.get(0)));
+ }
+
+ private static boolean isDocumentsProvider(Context context, String authority) {
final Intent intent = new Intent(PROVIDER_INTERFACE);
final List<ResolveInfo> infos = context.getPackageManager()
.queryIntentContentProviders(intent, 0);
for (ResolveInfo info : infos) {
- if (uri.getAuthority().equals(info.providerInfo.authority)) {
+ if (authority.equals(info.providerInfo.authority)) {
return true;
}
}
@@ -606,27 +705,40 @@
*/
public static String getRootId(Uri rootUri) {
final List<String> paths = rootUri.getPathSegments();
- if (paths.size() < 2) {
- throw new IllegalArgumentException("Not a root: " + rootUri);
+ if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
+ return paths.get(1);
}
- if (!PATH_ROOT.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a root: " + rootUri);
- }
- return paths.get(1);
+ throw new IllegalArgumentException("Invalid URI: " + rootUri);
}
/**
* Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
+ *
+ * @see #isDocumentUri(Context, Uri)
*/
public static String getDocumentId(Uri documentUri) {
final List<String> paths = documentUri.getPathSegments();
- if (paths.size() < 2) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
+ if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
+ return paths.get(1);
}
- if (!PATH_DOCUMENT.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
+ if (paths.size() >= 4 && PATH_VIA.equals(paths.get(0))
+ && PATH_DOCUMENT.equals(paths.get(2))) {
+ return paths.get(3);
}
- return paths.get(1);
+ throw new IllegalArgumentException("Invalid URI: " + documentUri);
+ }
+
+ /**
+ * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
+ *
+ * @see #isViaUri(Uri)
+ */
+ public static String getViaDocumentId(Uri documentUri) {
+ final List<String> paths = documentUri.getPathSegments();
+ if (paths.size() >= 2 && PATH_VIA.equals(paths.get(0))) {
+ return paths.get(1);
+ }
+ throw new IllegalArgumentException("Invalid URI: " + documentUri);
}
/**
@@ -758,7 +870,6 @@
* @param mimeType MIME type of new document
* @param displayName name of new document
* @return newly created document, or {@code null} if failed
- * @hide
*/
public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
String mimeType, String displayName) {
@@ -778,13 +889,12 @@
public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
String mimeType, String displayName) throws RemoteException {
final Bundle in = new Bundle();
- in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+ in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
in.putString(Document.COLUMN_MIME_TYPE, mimeType);
in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
- return buildDocumentUri(
- parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
+ return out.getParcelable(DocumentsContract.EXTRA_URI);
}
/**
@@ -811,7 +921,7 @@
public static void deleteDocument(ContentProviderClient client, Uri documentUri)
throws RemoteException {
final Bundle in = new Bundle();
- in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
+ in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
client.call(METHOD_DELETE_DOCUMENT, null, in);
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 49816f8..1a7a00f2 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -46,6 +46,7 @@
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
+import java.util.Objects;
/**
* Base class for a document provider. A document provider offers read and write
@@ -125,6 +126,8 @@
private static final int MATCH_SEARCH = 4;
private static final int MATCH_DOCUMENT = 5;
private static final int MATCH_CHILDREN = 6;
+ private static final int MATCH_DOCUMENT_VIA = 7;
+ private static final int MATCH_CHILDREN_VIA = 8;
private String mAuthority;
@@ -144,6 +147,8 @@
mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+ mMatcher.addURI(mAuthority, "via/*/document/*", MATCH_DOCUMENT_VIA);
+ mMatcher.addURI(mAuthority, "via/*/document/*/children", MATCH_CHILDREN_VIA);
// Sanity check our setup
if (!info.exported) {
@@ -161,6 +166,35 @@
}
/**
+ * Test if a document is descendant (child, grandchild, etc) from the given
+ * parent. Providers must override this to support directory selection. You
+ * should avoid making network requests to keep this request fast.
+ *
+ * @param parentDocumentId parent to verify against.
+ * @param documentId child to verify.
+ * @return if given document is a descendant of the given parent.
+ * @see DocumentsContract.Root#FLAG_SUPPORTS_DIR_SELECTION
+ */
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ return false;
+ }
+
+ /** {@hide} */
+ private void enforceVia(Uri documentUri) {
+ if (DocumentsContract.isViaUri(documentUri)) {
+ final String parent = DocumentsContract.getViaDocumentId(documentUri);
+ final String child = DocumentsContract.getDocumentId(documentUri);
+ if (Objects.equals(parent, child)) {
+ return;
+ }
+ if (!isChildDocument(parent, child)) {
+ throw new SecurityException(
+ "Document " + child + " is not a descendant of " + parent);
+ }
+ }
+ }
+
+ /**
* Create a new document and return its newly generated
* {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
* {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
@@ -182,9 +216,10 @@
/**
* Delete the requested document. Upon returning, any URI permission grants
- * for the requested document will be revoked. If additional documents were
- * deleted as a side effect of this call, such as documents inside a
- * directory, the implementor is responsible for revoking those permissions.
+ * for the given document will be revoked. If additional documents were
+ * deleted as a side effect of this call (such as documents inside a
+ * directory) the implementor is responsible for revoking those permissions
+ * using {@link #revokeDocumentPermission(String)}.
*
* @param documentId the document to delete.
*/
@@ -420,8 +455,12 @@
return querySearchDocuments(
getRootId(uri), getSearchDocumentsQuery(uri), projection);
case MATCH_DOCUMENT:
+ case MATCH_DOCUMENT_VIA:
+ enforceVia(uri);
return queryDocument(getDocumentId(uri), projection);
case MATCH_CHILDREN:
+ case MATCH_CHILDREN_VIA:
+ enforceVia(uri);
if (DocumentsContract.isManageMode(uri)) {
return queryChildDocumentsForManage(
getDocumentId(uri), projection, sortOrder);
@@ -449,6 +488,8 @@
case MATCH_ROOT:
return DocumentsContract.Root.MIME_TYPE_ITEM;
case MATCH_DOCUMENT:
+ case MATCH_DOCUMENT_VIA:
+ enforceVia(uri);
return getDocumentType(getDocumentId(uri));
default:
return null;
@@ -460,6 +501,49 @@
}
/**
+ * Implementation is provided by the parent class. Can be overridden to
+ * provide additional functionality, but subclasses <em>must</em> always
+ * call the superclass. If the superclass returns {@code null}, the subclass
+ * may implement custom behavior.
+ * <p>
+ * This is typically used to resolve a "via" URI into a concrete document
+ * reference, issuing a narrower single-document URI permission grant along
+ * the way.
+ *
+ * @see DocumentsContract#buildDocumentViaUri(Uri, String)
+ */
+ @Override
+ public Uri canonicalize(Uri uri) {
+ final Context context = getContext();
+ switch (mMatcher.match(uri)) {
+ case MATCH_DOCUMENT_VIA:
+ enforceVia(uri);
+
+ final Uri narrowUri = DocumentsContract.buildDocumentUri(uri.getAuthority(),
+ DocumentsContract.getDocumentId(uri));
+
+ // Caller may only have prefix grant, so extend them a grant to
+ // the narrow Uri. Caller already holds read grant to get here,
+ // so check for any other modes we should extend.
+ int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ if (context.checkCallingOrSelfUriPermission(uri,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+ }
+ if (context.checkCallingOrSelfUriPermission(uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
+ }
+ context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
+ return narrowUri;
+ }
+ return null;
+ }
+
+ /**
* Implementation is provided by the parent class. Throws by default, and
* cannot be overriden.
*
@@ -496,54 +580,47 @@
* provide additional functionality, but subclasses <em>must</em> always
* call the superclass. If the superclass returns {@code null}, the subclass
* may implement custom behavior.
- *
- * @see #openDocument(String, String, CancellationSignal)
- * @see #deleteDocument(String)
*/
@Override
public Bundle call(String method, String arg, Bundle extras) {
- final Context context = getContext();
-
if (!method.startsWith("android:")) {
- // Let non-platform methods pass through
+ // Ignore non-platform methods
return super.call(method, arg, extras);
}
- final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
- final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+ final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+ final String authority = documentUri.getAuthority();
+ final String documentId = DocumentsContract.getDocumentId(documentUri);
- // Require that caller can manage requested document
- final boolean callerHasManage =
- context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS)
- == PackageManager.PERMISSION_GRANTED;
- enforceWritePermissionInner(documentUri);
+ if (!mAuthority.equals(authority)) {
+ throw new SecurityException(
+ "Requested authority " + authority + " doesn't match provider " + mAuthority);
+ }
+ enforceVia(documentUri);
final Bundle out = new Bundle();
try {
if (METHOD_CREATE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri);
+
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
final String newDocumentId = createDocument(documentId, mimeType, displayName);
- out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
- // Extend permission grant towards caller if needed
- if (!callerHasManage) {
- final Uri newDocumentUri = DocumentsContract.buildDocumentUri(
- mAuthority, newDocumentId);
- context.grantUriPermission(getCallingPackage(), newDocumentUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
- }
+ // No need to issue new grants here, since caller either has
+ // manage permission or a prefix grant. We might generate a
+ // "via" style URI if that's how they called us.
+ final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(documentUri,
+ newDocumentId);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri);
deleteDocument(documentId);
// Document no longer exists, clean up any grants
- context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ revokeDocumentPermission(documentId);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
@@ -555,12 +632,25 @@
}
/**
+ * Revoke any active permission grants for the given
+ * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
+ * becomes invalid. Follows the same semantics as
+ * {@link Context#revokeUriPermission(Uri, int)}.
+ */
+ public final void revokeDocumentPermission(String documentId) {
+ final Context context = getContext();
+ context.revokeUriPermission(DocumentsContract.buildDocumentUri(mAuthority, documentId), ~0);
+ context.revokeUriPermission(DocumentsContract.buildViaUri(mAuthority, documentId), ~0);
+ }
+
+ /**
* Implementation is provided by the parent class. Cannot be overriden.
*
* @see #openDocument(String, String, CancellationSignal)
*/
@Override
public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ enforceVia(uri);
return openDocument(getDocumentId(uri), mode, null);
}
@@ -572,17 +662,47 @@
@Override
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
throws FileNotFoundException {
+ enforceVia(uri);
return openDocument(getDocumentId(uri), mode, signal);
}
/**
* Implementation is provided by the parent class. Cannot be overriden.
*
+ * @see #openDocument(String, String, CancellationSignal)
+ */
+ @Override
+ @SuppressWarnings("resource")
+ public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ enforceVia(uri);
+ final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
+
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ */
+ @Override
+ @SuppressWarnings("resource")
+ public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ enforceVia(uri);
+ final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
+
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
* @see #openDocumentThumbnail(String, Point, CancellationSignal)
*/
@Override
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
throws FileNotFoundException {
+ enforceVia(uri);
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
@@ -600,6 +720,7 @@
public final AssetFileDescriptor openTypedAssetFile(
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
throws FileNotFoundException {
+ enforceVia(uri);
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2ce6210..7f9f862 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5970,6 +5970,12 @@
public static final String SHOW_PROCESSES = "show_processes";
/**
+ * If 1 low power mode is enabled.
+ * @hide
+ */
+ public static final String LOW_POWER_MODE = "low_power";
+
+ /**
* If 1, the activity manager will aggressively finish activities and
* processes as soon as they are no longer needed. If 0, the normal
* extended lifetime is used.
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 13cc88b..2cc91b9 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -130,7 +130,10 @@
private static final String IRI
= "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
- private static final String HOST_NAME = IRI + "(?:\\." + IRI + ")+";
+ private static final String GOOD_GTLD_CHAR =
+ "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+ private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
+ private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
public static final Pattern DOMAIN_NAME
= Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index a7ee12b..71296fa 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -161,6 +161,7 @@
} else if (tagName.equals(XML_MENU)) {
// A menu start tag denotes a submenu for an item
SubMenu subMenu = menuState.addSubMenuItem();
+ registerMenu(subMenu, attrs);
// Parse the submenu into returned SubMenu
parseMenu(parser, attrs, subMenu);
@@ -183,9 +184,9 @@
if (!menuState.hasAddedItem()) {
if (menuState.itemActionProvider != null &&
menuState.itemActionProvider.hasSubMenu()) {
- menuState.addSubMenuItem();
+ registerMenu(menuState.addSubMenuItem(), attrs);
} else {
- menuState.addItem();
+ registerMenu(menuState.addItem(), attrs);
}
}
} else if (tagName.equals(XML_MENU)) {
@@ -200,7 +201,30 @@
eventType = parser.next();
}
}
-
+
+ /**
+ * The method is a hook for layoutlib to do its magic.
+ * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+ * appears to do nothing.
+ */
+ private void registerMenu(@SuppressWarnings("unused") MenuItem item,
+ @SuppressWarnings("unused") AttributeSet set) {
+ }
+
+ /**
+ * The method is a hook for layoutlib to do its magic.
+ * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+ * appears to do nothing.
+ */
+ private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
+ @SuppressWarnings("unused") AttributeSet set) {
+ }
+
+ // Needed by layoutlib.
+ /*package*/ Context getContext() {
+ return mContext;
+ }
+
private static class InflatedOnMenuItemClickListener
implements MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
@@ -446,9 +470,11 @@
}
}
- public void addItem() {
+ public MenuItem addItem() {
itemAdded = true;
- setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
+ MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
+ setItem(item);
+ return item;
}
public SubMenu addSubMenuItem() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 097dff0..374787e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5821,6 +5821,13 @@
}
}
+ public void dispatchUnhandledInputEvent(InputEvent event) {
+ if (event instanceof KeyEvent) {
+ dispatchUnhandledKey((KeyEvent) event);
+ return;
+ }
+ }
+
public void dispatchAppVisibility(boolean visible) {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY);
msg.arg1 = visible ? 1 : 0;
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 438a9da..225cd6d 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -29,6 +29,8 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
public abstract class AbsSeekBar extends ProgressBar {
private Drawable mThumb;
private int mThumbOffset;
@@ -289,28 +291,39 @@
*/
private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
int available = w - mPaddingLeft - mPaddingRight;
- int thumbWidth = thumb.getIntrinsicWidth();
- int thumbHeight = thumb.getIntrinsicHeight();
+ final int thumbWidth = thumb.getIntrinsicWidth();
+ final int thumbHeight = thumb.getIntrinsicHeight();
available -= thumbWidth;
// The extra space for the thumb to move on the track
available += mThumbOffset * 2;
- int thumbPos = (int) (scale * available + 0.5f);
+ final int thumbPos = (int) (scale * available + 0.5f);
- int topBound, bottomBound;
+ final int top, bottom;
if (gap == Integer.MIN_VALUE) {
- Rect oldBounds = thumb.getBounds();
- topBound = oldBounds.top;
- bottomBound = oldBounds.bottom;
+ final Rect oldBounds = thumb.getBounds();
+ top = oldBounds.top;
+ bottom = oldBounds.bottom;
} else {
- topBound = gap;
- bottomBound = gap + thumbHeight;
+ top = gap;
+ bottom = gap + thumbHeight;
}
-
- // Canvas will be translated, so 0,0 is where we start drawing
+
final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
- thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
+ final int right = left + thumbWidth;
+
+ final Drawable background = getBackground();
+ if (background != null && background.supportsHotspots()) {
+ final Rect bounds = mThumb.getBounds();
+ final int offsetX = mPaddingLeft - mThumbOffset;
+ final int offsetY = mPaddingTop;
+ background.setHotspotBounds(left + offsetX, bounds.top + offsetY,
+ right + offsetX, bounds.bottom + offsetY);
+ }
+
+ // Canvas will be translated, so 0,0 is where we start drawing
+ thumb.setBounds(left, top, right, bottom);
}
/**
@@ -328,6 +341,7 @@
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
+
if (mThumb != null) {
canvas.save();
// Translate the padding. For the x, we need to allow the thumb to
@@ -424,10 +438,24 @@
return true;
}
+ private void setHotspot(int id, float x, float y) {
+ final Drawable bg = getBackground();
+ if (bg != null && bg.supportsHotspots()) {
+ bg.setHotspot(id, x, y);
+ }
+ }
+
+ private void clearHotspot(int id) {
+ final Drawable bg = getBackground();
+ if (bg != null && bg.supportsHotspots()) {
+ bg.removeHotspot(id);
+ }
+ }
+
private void trackTouchEvent(MotionEvent event) {
final int width = getWidth();
final int available = width - mPaddingLeft - mPaddingRight;
- int x = (int)event.getX();
+ final int x = (int) event.getX();
float scale;
float progress = 0;
if (isLayoutRtl() && mMirrorForRtl) {
@@ -451,7 +479,8 @@
}
final int max = getMax();
progress += scale * max;
-
+
+ setHotspot(R.attr.state_pressed, x, (int) event.getY());
setProgress((int) progress, true);
}
@@ -477,6 +506,7 @@
* canceled.
*/
void onStopTrackingTouch() {
+ clearHotspot(R.attr.state_pressed);
mIsDragging = false;
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 4298545..9e17cca 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -261,15 +261,13 @@
@Override
protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
final Drawable buttonDrawable = mButtonDrawable;
if (buttonDrawable != null) {
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int drawableHeight = buttonDrawable.getIntrinsicHeight();
final int drawableWidth = buttonDrawable.getIntrinsicWidth();
- int top = 0;
+ final int top;
switch (verticalGravity) {
case Gravity.BOTTOM:
top = getHeight() - drawableHeight;
@@ -277,12 +275,24 @@
case Gravity.CENTER_VERTICAL:
top = (getHeight() - drawableHeight) / 2;
break;
+ default:
+ top = 0;
}
- int bottom = top + drawableHeight;
- int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
- int right = isLayoutRtl() ? getWidth() : drawableWidth;
+ final int bottom = top + drawableHeight;
+ final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
+ final int right = isLayoutRtl() ? getWidth() : drawableWidth;
buttonDrawable.setBounds(left, top, right, bottom);
+
+ final Drawable background = getBackground();
+ if (background != null && background.supportsHotspots()) {
+ background.setHotspotBounds(left, top, right, bottom);
+ }
+ }
+
+ super.onDraw(canvas);
+
+ if (buttonDrawable != null) {
buttonDrawable.draw(canvas);
}
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 3d23e4d..08af4de 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -773,8 +773,6 @@
@Override
protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
final Rect tempRect = mTempRect;
final Drawable trackDrawable = mTrackDrawable;
final Drawable thumbDrawable = mThumbDrawable;
@@ -785,16 +783,12 @@
final int switchRight = mSwitchRight;
final int switchBottom = mSwitchBottom;
trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
- trackDrawable.draw(canvas);
-
- final int saveCount = canvas.save();
-
trackDrawable.getPadding(tempRect);
+
final int switchInnerLeft = switchLeft + tempRect.left;
final int switchInnerTop = switchTop + tempRect.top;
final int switchInnerRight = switchRight - tempRect.right;
final int switchInnerBottom = switchBottom - tempRect.bottom;
- canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
// Relies on mTempRect, MUST be called first!
final int thumbPos = getThumbOffset();
@@ -803,6 +797,18 @@
int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+
+ final Drawable background = getBackground();
+ if (background != null && background.supportsHotspots()) {
+ background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ }
+
+ super.onDraw(canvas);
+
+ trackDrawable.draw(canvas);
+
+ final int saveCount = canvas.save();
+ canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
thumbDrawable.draw(canvas);
final int drawableState[] = getDrawableState();
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index b776226..5d7d322 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -392,8 +392,8 @@
private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
final int ordering = getOrdering(categoryOrder);
- final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
- ordering, title, mDefaultShowAsAction);
+ final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title,
+ mDefaultShowAsAction);
if (mCurrentMenuInfo != null) {
// Pass along the current menu info
@@ -405,7 +405,14 @@
return item;
}
-
+
+ // Layoutlib overrides this method to return its custom implementation of MenuItemImpl
+ private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering,
+ CharSequence title, int defaultShowAsAction) {
+ return new MenuItemImpl(this, group, id, categoryOrder, ordering, title,
+ defaultShowAsAction);
+ }
+
public MenuItem add(CharSequence title) {
return addInternal(0, 0, 0, title);
}
diff --git a/core/res/res/drawable/btn_radio_quantum.xml b/core/res/res/drawable/btn_radio_quantum.xml
index 152e3d3..0f9ebce 100644
--- a/core/res/res/drawable/btn_radio_quantum.xml
+++ b/core/res/res/drawable/btn_radio_quantum.xml
@@ -15,18 +15,20 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_enabled="true" android:state_pressed="true">
- <bitmap android:src="@drawable/btn_radio_on_pressed_qntm_alpha"
- android:tint="?attr/colorControlActivated" />
+ <item android:state_enabled="false" android:state_checked="true">
+ <bitmap android:src="@drawable/btn_radio_on_qntm_alpha"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
+ </item>
+ <item android:state_enabled="false">
+ <bitmap android:src="@drawable/btn_radio_off_qntm_alpha"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
</item>
<item android:state_checked="true">
<bitmap android:src="@drawable/btn_radio_on_qntm_alpha"
android:tint="?attr/colorControlActivated" />
</item>
- <item android:state_enabled="true" android:state_pressed="true">
- <bitmap android:src="@drawable/btn_radio_off_pressed_qntm_alpha"
- android:tint="?attr/colorControlActivated" />
- </item>
<item>
<bitmap android:src="@drawable/btn_radio_off_qntm_alpha"
android:tint="?attr/colorControlNormal" />
diff --git a/core/res/res/drawable/scrubber_control_selector_quantum.xml b/core/res/res/drawable/scrubber_control_selector_quantum.xml
index e34f64a..ef3af0c 100644
--- a/core/res/res/drawable/scrubber_control_selector_quantum.xml
+++ b/core/res/res/drawable/scrubber_control_selector_quantum.xml
@@ -19,10 +19,6 @@
<bitmap android:src="@drawable/scrubber_control_off_qntm_alpha"
android:tint="?attr/colorControlNormal" />
</item>
- <item android:state_pressed="true">
- <bitmap android:src="@drawable/scrubber_control_on_pressed_qntm_alpha"
- android:tint="?attr/colorControlActivated" />
- </item>
<item>
<bitmap android:src="@drawable/scrubber_control_on_qntm_alpha"
android:tint="?attr/colorControlActivated" />
diff --git a/core/res/res/drawable/switch_inner_quantum.xml b/core/res/res/drawable/switch_inner_quantum.xml
index 915377e..856895e 100644
--- a/core/res/res/drawable/switch_inner_quantum.xml
+++ b/core/res/res/drawable/switch_inner_quantum.xml
@@ -15,18 +15,20 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:state_pressed="true">
+ <item android:state_enabled="false" android:state_checked="true">
<nine-patch android:src="@drawable/switch_on_qntm_alpha"
- android:tint="?attr/colorControlActivated" />
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
+ </item>
+ <item android:state_enabled="false">
+ <nine-patch android:src="@drawable/switch_off_qntm_alpha"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
</item>
<item android:state_checked="true">
<nine-patch android:src="@drawable/switch_on_qntm_alpha"
android:tint="?attr/colorControlActivated" />
</item>
- <item android:state_pressed="true">
- <nine-patch android:src="@drawable/switch_off_qntm_alpha"
- android:tint="?attr/colorControlActivated" />
- </item>
<item>
<nine-patch android:src="@drawable/switch_off_qntm_alpha"
android:tint="?attr/colorControlNormal" />
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index bdc7ad0..0cbb655 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -485,11 +485,18 @@
</style>
<style name="Widget.Quantum.CompoundButton" parent="Widget.CompoundButton"/>
- <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox"/>
- <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton"/>
+
+ <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
+ <item name="background">?attr/selectableItemBackground</item>
+ </style>
+
+ <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
+ <item name="background">?attr/selectableItemBackground</item>
+ </style>
<style name="Widget.Quantum.CompoundButton.Star" parent="Widget.CompoundButton.Star">
<item name="button">@drawable/btn_star_quantum</item>
+ <item name="background">?attr/selectableItemBackground</item>
</style>
<style name="Widget.Quantum.CompoundButton.Switch">
@@ -501,6 +508,7 @@
<item name="thumbTextPadding">12dip</item>
<item name="switchMinWidth">72dip</item>
<item name="switchPadding">16dip</item>
+ <item name="background">?attr/selectableItemBackground</item>
</style>
<style name="Widget.Quantum.EditText" parent="Widget.EditText"/>
@@ -619,6 +627,7 @@
<item name="paddingStart">16dip</item>
<item name="paddingEnd">16dip</item>
<item name="mirrorForRtl">true</item>
+ <item name="background">?attr/selectableItemBackground</item>
</style>
<style name="Widget.Quantum.RatingBar" parent="Widget.RatingBar">
@@ -846,7 +855,15 @@
<item name="popupBackground">?attr/colorBackground</item>
</style>
+ <style name="Widget.Quantum.Light.CompoundButton" parent="Widget.Quantum.CompoundButton"/>
<style name="Widget.Quantum.Light.CompoundButton.CheckBox" parent="Widget.Quantum.CompoundButton.CheckBox"/>
+ <style name="Widget.Quantum.Light.CompoundButton.RadioButton" parent="Widget.Quantum.CompoundButton.RadioButton"/>
+ <style name="Widget.Quantum.Light.CompoundButton.Star" parent="Widget.Quantum.CompoundButton.Star"/>
+
+ <style name="Widget.Quantum.Light.CompoundButton.Switch" parent="Widget.Quantum.CompoundButton.Switch">
+ <item name="switchTextAppearance">@style/TextAppearance.Quantum.Light.Widget.Switch</item>
+ </style>
+
<style name="Widget.Quantum.Light.ListView.DropDown" parent="Widget.Quantum.ListView.DropDown"/>
<style name="Widget.Quantum.Light.EditText" parent="Widget.Quantum.EditText"/>
<style name="Widget.Quantum.Light.ExpandableListView" parent="Widget.Quantum.ExpandableListView"/>
@@ -916,7 +933,6 @@
<item name="maxHeight">16dip</item>
</style>
- <style name="Widget.Quantum.Light.CompoundButton.RadioButton" parent="Widget.Quantum.CompoundButton.RadioButton"/>
<style name="Widget.Quantum.Light.ScrollView" parent="Widget.Quantum.ScrollView"/>
<style name="Widget.Quantum.Light.HorizontalScrollView" parent="Widget.Quantum.HorizontalScrollView"/>
@@ -932,7 +948,6 @@
<style name="Widget.Quantum.Light.Spinner.DropDown" parent="Widget.Quantum.Spinner.DropDown"/>
<style name="Widget.Quantum.Light.Spinner.DropDown.ActionBar" parent="Widget.Quantum.Spinner.DropDown.ActionBar"/>
- <style name="Widget.Quantum.Light.CompoundButton.Star" parent="Widget.Quantum.CompoundButton.Star"/>
<style name="Widget.Quantum.Light.TabWidget" parent="Widget.Quantum.TabWidget"/>
<style name="Widget.Quantum.Light.WebTextView" parent="Widget.Quantum.WebTextView"/>
<style name="Widget.Quantum.Light.WebView" parent="Widget.Quantum.WebView"/>
@@ -989,10 +1004,6 @@
<item name="backgroundSplit">?attr/colorPrimary</item>
</style>
- <style name="Widget.Quantum.Light.CompoundButton.Switch" parent="Widget.Quantum.CompoundButton.Switch">
- <item name="switchTextAppearance">@style/TextAppearance.Quantum.Light.Widget.Switch</item>
- </style>
-
<style name="Widget.Quantum.Light.FastScroll" parent="Widget.Quantum.FastScroll"/>
<style name="Widget.Quantum.Light.MediaRouteButton" parent="Widget.Quantum.MediaRouteButton">
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 6fb8946..cd45017 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -19,12 +19,14 @@
import android.content.ContentUris;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
import java.io.File;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import junit.framework.TestCase;
public class UriTest extends TestCase {
@@ -752,4 +754,54 @@
assertEquals("d e", Uri.parse("http://a/b?c=d%20e").getQueryParameter("c"));
assertEquals("d e", Uri.parse("http://a/b?c=d+e").getQueryParameter("c"));
}
+
+ public void testPathPrefixMatch() {
+ // Exact match
+ assertTrue(Uri.parse("content://com.example/path").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/")));
+ assertTrue(Uri.parse("content://com.example/path").isPathPrefixMatch(
+ Uri.parse("content://com.example/path")));
+ assertTrue(Uri.parse("content://com.example///path///").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/")));
+ assertTrue(Uri.parse("content://com.example/path").isPathPrefixMatch(
+ Uri.parse("content://com.example///path///")));
+
+ // Child match
+ assertTrue(Uri.parse("content://com.example/path/to/child").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/")));
+ assertTrue(Uri.parse("content://com.example/path/to/child").isPathPrefixMatch(
+ Uri.parse("content://com.example/path")));
+
+ // Extra parameters
+ assertTrue(Uri.parse("content://com.example/path#fragment").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/")));
+ assertTrue(Uri.parse("content://com.example/path?q=v").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/")));
+ assertTrue(Uri.parse("content://com.example/path/?q=v").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/")));
+
+ // Different path
+ assertFalse(Uri.parse("content://com.example/path").isPathPrefixMatch(
+ Uri.parse("content://com.example/path/deeper/")));
+ assertFalse(Uri.parse("content://com.example/path2").isPathPrefixMatch(
+ Uri.parse("content://com.example/path")));
+
+ // Top-level match
+ assertTrue(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+ Uri.parse("content://com.example/")));
+ assertTrue(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+ Uri.parse("content://com.example")));
+
+ // Different prefixes
+ assertFalse(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+ Uri.parse("file://com.example/path/")));
+ assertFalse(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+ Uri.parse("content://org.example/path/")));
+
+ // Escaping
+ assertTrue(Uri.parse("content://com.example/path path/").isPathPrefixMatch(
+ Uri.parse("content://com.example/path%20path/")));
+ assertFalse(Uri.parse("content://com.example/path/path").isPathPrefixMatch(
+ Uri.parse("content://com.example/path%2Fpath")));
+ }
}
diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd
index f169f5f..a61696a 100644
--- a/docs/html/guide/topics/ui/actionbar.jd
+++ b/docs/html/guide/topics/ui/actionbar.jd
@@ -1126,8 +1126,8 @@
uses a string array as the data source:</p>
<pre>
-SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list,
- android.R.layout.simple_spinner_dropdown_item);
+SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
+ R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
</pre>
<p>The {@link android.widget.ArrayAdapter#createFromResource createFromResource()} method takes
@@ -1179,10 +1179,13 @@
public boolean onNavigationItemSelected(int position, long itemId) {
// Create new fragment from our own Fragment class
ListContentFragment newFragment = new ListContentFragment();
- FragmentTransaction ft = openFragmentTransaction();
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
// Replace whatever is in the fragment container with this fragment
- // and give the fragment a tag name equal to the string at the position selected
+ // and give the fragment a tag name equal to the string at the position
+ // selected
ft.replace(R.id.fragment_container, newFragment, strings[position]);
+
// Apply changes
ft.commit();
return true;
@@ -1210,7 +1213,8 @@
@Override
public void onAttach(Activity activity) {
// This is the first callback received; here we can set the text for
- // the fragment as defined by the tag specified during the fragment transaction
+ // the fragment as defined by the tag specified during the fragment
+ // transaction
super.onAttach(activity);
mText = getTag();
}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 21cd5db..74d1219 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -518,9 +518,16 @@
public void removeHotspot(int key) {}
/**
- * Removes all hotspots from the drawable.
+ * Immediately removes all hotspots from the drawable.
*/
public void clearHotspots() {}
+
+ /**
+ * Sets the bounds to which hotspots are constrained.
+ *
+ * @hide until we finalize these APIs
+ */
+ public void setHotspotBounds(int left, int top, int right, int bottom) {}
/**
* Whether this drawable requests projection.
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index b366987..639d719 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -290,6 +290,26 @@
return false;
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isProjected() {
+ if (super.isProjected()) {
+ return true;
+ }
+
+ final ChildDrawable[] layers = mLayerState.mChildren;
+ final int N = mLayerState.mNum;
+ for (int i = 0; i < N; i++) {
+ if (layers[i].mDrawable.isProjected()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
/**
* Add a new layer to this drawable. The new layer is identified by an id.
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 796da50..e3f57e9 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -139,8 +139,8 @@
public boolean draw(Canvas c, Paint p) {
final Rect bounds = mBounds;
final Rect padding = mPadding;
- final float dX = Math.max(mX, bounds.right - mX);
- final float dY = Math.max(mY, bounds.bottom - mY);
+ final float dX = Math.max(mX - bounds.left, bounds.right - mX);
+ final float dY = Math.max(mY - bounds.top, bounds.bottom - mY);
final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY));
final float enterState = mEnterState;
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 813d755c..5101e35 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -25,12 +25,13 @@
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Ripple.RippleAnimator;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.drawable.Ripple.RippleAnimator;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.R;
@@ -45,6 +46,7 @@
* Documentation pending.
*/
public class TouchFeedbackDrawable extends LayerDrawable {
+ private static final String LOG_TAG = TouchFeedbackDrawable.class.getSimpleName();
private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
/** The maximum number of ripples supported. */
@@ -53,6 +55,9 @@
private final Rect mTempRect = new Rect();
private final Rect mPaddingRect = new Rect();
+ /** Current ripple effect bounds, used to constrain ripple effects. */
+ private final Rect mHotspotBounds = new Rect();
+
/** Current drawing bounds, used to compute dirty region. */
private final Rect mDrawingBounds = new Rect();
@@ -83,6 +88,9 @@
/** Whether the animation runnable has been posted. */
private boolean mAnimating;
+ /** Whether bounds are being overridden. */
+ private boolean mOverrideBounds;
+
TouchFeedbackDrawable() {
this(new TouchFeedbackState(null, null, null), null, null);
}
@@ -114,9 +122,22 @@
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
+
+ if (!mOverrideBounds) {
+ mHotspotBounds.set(bounds);
+ }
+ onHotspotBoundsChange();
+ }
+
+ private void onHotspotBoundsChange() {
+ final int x = mHotspotBounds.centerX();
+ final int y = mHotspotBounds.centerY();
final int N = mActiveRipplesCount;
for (int i = 0; i < N; i++) {
+ if (mState.mPinned) {
+ mActiveRipples[i].move(x, y);
+ }
mActiveRipples[i].onBoundsChanged();
}
}
@@ -289,13 +310,18 @@
mTouchedRipples = new SparseArray<Ripple>();
mActiveRipples = new Ripple[MAX_RIPPLES];
}
+
+ if (mActiveRipplesCount >= MAX_RIPPLES) {
+ Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
+ return;
+ }
final Ripple ripple = mTouchedRipples.get(id);
if (ripple == null) {
- final Rect bounds = getBounds();
final Rect padding = mPaddingRect;
getPadding(padding);
+ final Rect bounds = mHotspotBounds;
if (mState.mPinned) {
x = bounds.exactCenterX();
y = bounds.exactCenterY();
@@ -308,7 +334,12 @@
mActiveRipples[mActiveRipplesCount++] = newRipple;
mTouchedRipples.put(id, newRipple);
- } else if (!mState.mPinned) {
+ } else if (mState.mPinned) {
+ final Rect bounds = mHotspotBounds;
+ x = bounds.exactCenterX();
+ y = bounds.exactCenterY();
+ ripple.move(x, y);
+ } else {
ripple.move(x, y);
}
@@ -338,6 +369,7 @@
final int n = mTouchedRipples.size();
for (int i = 0; i < n; i++) {
+ // TODO: Use a fast exit, maybe just fade out?
mTouchedRipples.valueAt(i).animate().exit();
}
@@ -348,6 +380,16 @@
}
/**
+ * @hide
+ */
+ @Override
+ public void setHotspotBounds(int left, int top, int right, int bottom) {
+ mOverrideBounds = true;
+ mHotspotBounds.set(left, top, right, bottom);
+ onHotspotBoundsChange();
+ }
+
+ /**
* Schedules the next animation, if necessary.
*/
private void scheduleAnimation() {
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 5187f8f..d22cb8a 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -42,6 +42,7 @@
Font::Font(FontRenderer* state, const Font::FontDescription& desc) :
mState(state), mDescription(desc) {
+ mDeviceProperties = SkDeviceProperties::Make(SkDeviceProperties::Geometry::MakeDefault(), 1.0f);
}
Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) {
@@ -272,7 +273,7 @@
if (cachedGlyph) {
// Is the glyph still in texture cache?
if (!cachedGlyph->mIsValid) {
- SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+ SkAutoGlyphCache autoCache(*paint, &mDeviceProperties, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit);
updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching);
}
@@ -464,7 +465,7 @@
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+ SkAutoGlyphCache autoCache(*paint, &mDeviceProperties, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph);
newGlyph->mIsValid = false;
newGlyph->mGlyphIndex = skiaGlyph.fID;
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 02197bc..6ddd4f2 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -19,6 +19,8 @@
#include <utils/KeyedVector.h>
+#include <SkScalar.h>
+#include <SkDeviceProperties.h>
#include <SkGlyphCache.h>
#include <SkScalerContext.h>
#include <SkPaint.h>
@@ -146,6 +148,7 @@
DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
bool mIdentityTransform;
+ SkDeviceProperties mDeviceProperties;
};
inline int strictly_order_type(const Font::FontDescription& lhs,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index edfa36a..45df065 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -309,7 +309,7 @@
});
// rational (n) -- in particular rational x 9
- checkKeyGetAndSetArray("android.sensor.calibrationTransform", Rational[].class,
+ checkKeyGetAndSetArray("android.sensor.calibrationTransform1", Rational[].class,
new Rational[] {
new Rational(1, 2), new Rational(3, 4), new Rational(5, 6),
new Rational(7, 8), new Rational(9, 10), new Rational(10, 11),
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 6b77a7c..159ee66 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -9,18 +9,17 @@
android:label="@string/app_label"
android:supportsRtl="true">
- <!-- TODO: allow rotation when state saving is in better shape -->
<activity
android:name=".DocumentsActivity"
android:theme="@style/Theme"
android:icon="@drawable/ic_doc_text">
- <intent-filter android:priority="100">
+ <intent-filter>
<action android:name="android.intent.action.OPEN_DOCUMENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>
- <intent-filter android:priority="100">
+ <intent-filter>
<action android:name="android.intent.action.CREATE_DOCUMENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
@@ -33,6 +32,10 @@
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
+ <action android:name="android.intent.action.PICK_DIRECTORY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
<action android:name="android.provider.action.MANAGE_ROOT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.document/root" />
@@ -57,14 +60,5 @@
<data android:scheme="package" />
</intent-filter>
</receiver>
-
- <!-- TODO: remove when we have real clients -->
- <activity android:name=".TestActivity" android:enabled="false">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
</application>
</manifest>
diff --git a/packages/DocumentsUI/res/layout/fragment_pick.xml b/packages/DocumentsUI/res/layout/fragment_pick.xml
new file mode 100644
index 0000000..4a2fd03
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_pick.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- Le sigh, this really should be an asset -->
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#ccc" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:gravity="center_vertical"
+ android:background="#ddd"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall">
+
+ <Button
+ android:id="@android:id/button1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:attr/selectableItemBackground"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAllCaps="false"
+ android:padding="8dp" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 92c30ba..c1a9d72 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -44,6 +44,8 @@
<string name="menu_share">Share</string>
<!-- Menu item title that deletes the selected documents [CHAR LIMIT=24] -->
<string name="menu_delete">Delete</string>
+ <!-- Menu item title that selects the current directory [CHAR LIMIT=48] -->
+ <string name="menu_select">Select \"<xliff:g id="directory" example="My Directory">^1</xliff:g>\"</string>
<!-- Action mode title summarizing the number of documents selected [CHAR LIMIT=32] -->
<string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 4212e96..9f76991 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -24,6 +24,7 @@
import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_PICK_DIRECTORY;
import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
@@ -202,6 +203,8 @@
final String mimeType = getIntent().getType();
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
SaveFragment.show(getFragmentManager(), mimeType, title);
+ } else if (mState.action == ACTION_PICK_DIRECTORY) {
+ PickFragment.show(getFragmentManager());
}
if (mState.action == ACTION_GET_CONTENT) {
@@ -209,7 +212,8 @@
moreApps.setComponent(null);
moreApps.setPackage(null);
RootsFragment.show(getFragmentManager(), moreApps);
- } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) {
+ } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE
+ || mState.action == ACTION_PICK_DIRECTORY) {
RootsFragment.show(getFragmentManager(), null);
}
@@ -236,6 +240,8 @@
mState.action = ACTION_CREATE;
} else if (Intent.ACTION_GET_CONTENT.equals(action)) {
mState.action = ACTION_GET_CONTENT;
+ } else if (Intent.ACTION_PICK_DIRECTORY.equals(action)) {
+ mState.action = ACTION_PICK_DIRECTORY;
} else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) {
mState.action = ACTION_MANAGE;
}
@@ -434,7 +440,8 @@
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setIcon(new ColorDrawable());
- if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
+ if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT
+ || mState.action == ACTION_PICK_DIRECTORY) {
actionBar.setTitle(R.string.title_open);
} else if (mState.action == ACTION_CREATE) {
actionBar.setTitle(R.string.title_save);
@@ -576,7 +583,7 @@
sortSize.setVisible(mState.showSize);
final boolean searchVisible;
- if (mState.action == ACTION_CREATE) {
+ if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
createDir.setVisible(cwd != null && cwd.isCreateSupported());
searchVisible = false;
@@ -586,7 +593,9 @@
list.setVisible(false);
}
- SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
+ if (mState.action == ACTION_CREATE) {
+ SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
+ }
} else {
createDir.setVisible(false);
@@ -819,7 +828,7 @@
if (cwd == null) {
// No directory means recents
- if (mState.action == ACTION_CREATE) {
+ if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
RecentsCreateFragment.show(fm);
} else {
DirectoryFragment.showRecentsOpen(fm, anim);
@@ -848,6 +857,15 @@
}
}
+ if (mState.action == ACTION_PICK_DIRECTORY) {
+ final PickFragment pick = PickFragment.get(fm);
+ if (pick != null) {
+ final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
+ : cwd.displayName;
+ pick.setPickTarget(cwd, displayName);
+ }
+ }
+
final RootsFragment roots = RootsFragment.get(fm);
if (roots != null) {
roots.onCurrentRootChanged();
@@ -1002,12 +1020,18 @@
new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
}
+ public void onPickRequested(DocumentInfo pickTarget) {
+ final Uri viaUri = DocumentsContract.buildViaUri(pickTarget.authority,
+ pickTarget.documentId);
+ new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor());
+ }
+
private void saveStackBlocking() {
final ContentResolver resolver = getContentResolver();
final ContentValues values = new ContentValues();
final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
- if (mState.action == ACTION_CREATE) {
+ if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
// Remember stack for last create
values.clear();
values.put(RecentColumns.KEY, mState.stack.buildKey());
@@ -1040,6 +1064,11 @@
if (mState.action == ACTION_GET_CONTENT) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } else if (mState.action == ACTION_PICK_DIRECTORY) {
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -1121,6 +1150,25 @@
}
}
+ private class PickFinishTask extends AsyncTask<Void, Void, Void> {
+ private final Uri mUri;
+
+ public PickFinishTask(Uri uri) {
+ mUri = uri;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ saveStackBlocking();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ onFinished(mUri);
+ }
+ }
+
public static class State implements android.os.Parcelable {
public int action;
public String[] acceptMimes;
@@ -1154,7 +1202,8 @@
public static final int ACTION_OPEN = 1;
public static final int ACTION_CREATE = 2;
public static final int ACTION_GET_CONTENT = 3;
- public static final int ACTION_MANAGE = 4;
+ public static final int ACTION_PICK_DIRECTORY = 4;
+ public static final int ACTION_MANAGE = 5;
public static final int MODE_UNKNOWN = 0;
public static final int MODE_LIST = 1;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
new file mode 100644
index 0000000..a9e488a1
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.documentsui.model.DocumentInfo;
+
+import java.util.Locale;
+
+/**
+ * Display pick confirmation bar, usually for selecting a directory.
+ */
+public class PickFragment extends Fragment {
+ public static final String TAG = "PickFragment";
+
+ private DocumentInfo mPickTarget;
+
+ private View mContainer;
+ private Button mPick;
+
+ public static void show(FragmentManager fm) {
+ final PickFragment fragment = new PickFragment();
+
+ final FragmentTransaction ft = fm.beginTransaction();
+ ft.replace(R.id.container_save, fragment, TAG);
+ ft.commitAllowingStateLoss();
+ }
+
+ public static PickFragment get(FragmentManager fm) {
+ return (PickFragment) fm.findFragmentByTag(TAG);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mContainer = inflater.inflate(R.layout.fragment_pick, container, false);
+
+ mPick = (Button) mContainer.findViewById(android.R.id.button1);
+ mPick.setOnClickListener(mPickListener);
+
+ setPickTarget(null, null);
+
+ return mContainer;
+ }
+
+ private View.OnClickListener mPickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final DocumentsActivity activity = DocumentsActivity.get(PickFragment.this);
+ activity.onPickRequested(mPickTarget);
+ }
+ };
+
+ public void setPickTarget(DocumentInfo pickTarget, CharSequence displayName) {
+ mPickTarget = pickTarget;
+
+ if (mPickTarget != null) {
+ mContainer.setVisibility(View.VISIBLE);
+ final Locale locale = getResources().getConfiguration().locale;
+ final String raw = getString(R.string.menu_select).toUpperCase(locale);
+ mPick.setText(TextUtils.expandTemplate(raw, displayName));
+ } else {
+ mContainer.setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index f1dca1d..933dbe0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -104,7 +104,8 @@
mRecentsRoot.authority = null;
mRecentsRoot.rootId = null;
mRecentsRoot.icon = R.drawable.ic_root_recent;
- mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
+ mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE
+ | Root.FLAG_SUPPORTS_DIR_SELECTION;
mRecentsRoot.title = mContext.getString(R.string.root_recent);
mRecentsRoot.availableBytes = -1;
@@ -349,12 +350,15 @@
final List<RootInfo> matching = Lists.newArrayList();
for (RootInfo root : roots) {
final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
+ final boolean supportsDir = (root.flags & Root.FLAG_SUPPORTS_DIR_SELECTION) != 0;
final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
final boolean empty = (root.flags & Root.FLAG_EMPTY) != 0;
// Exclude read-only devices when creating
if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
+ // Exclude roots that don't support directory picking
+ if (state.action == State.ACTION_PICK_DIRECTORY && !supportsDir) continue;
// Exclude advanced devices when not requested
if (!state.showAdvanced && advanced) continue;
// Exclude non-local devices when local only
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
deleted file mode 100644
index 1a47308..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class TestActivity extends Activity {
- private static final String TAG = "TestActivity";
-
- private static final int CODE_READ = 42;
- private static final int CODE_WRITE = 43;
-
- private TextView mResult;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Context context = this;
-
- final LinearLayout view = new LinearLayout(context);
- view.setOrientation(LinearLayout.VERTICAL);
-
- mResult = new TextView(context);
- view.addView(mResult);
-
- final CheckBox multiple = new CheckBox(context);
- multiple.setText("ALLOW_MULTIPLE");
- view.addView(multiple);
- final CheckBox localOnly = new CheckBox(context);
- localOnly.setText("LOCAL_ONLY");
- view.addView(localOnly);
-
- Button button;
- button = new Button(context);
- button.setText("OPEN_DOC */*");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- if (multiple.isChecked()) {
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
- }
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(intent, CODE_READ);
- }
- });
- view.addView(button);
-
- button = new Button(context);
- button.setText("OPEN_DOC image/*");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("image/*");
- if (multiple.isChecked()) {
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
- }
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(intent, CODE_READ);
- }
- });
- view.addView(button);
-
- button = new Button(context);
- button.setText("OPEN_DOC audio/ogg");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("audio/ogg");
- if (multiple.isChecked()) {
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
- }
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(intent, CODE_READ);
- }
- });
- view.addView(button);
-
- button = new Button(context);
- button.setText("OPEN_DOC text/plain, application/msword");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
- "text/plain", "application/msword" });
- if (multiple.isChecked()) {
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
- }
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(intent, CODE_READ);
- }
- });
- view.addView(button);
-
- button = new Button(context);
- button.setText("CREATE_DOC text/plain");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt");
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(intent, CODE_WRITE);
- }
- });
- view.addView(button);
-
- button = new Button(context);
- button.setText("CREATE_DOC image/png");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("image/png");
- intent.putExtra(Intent.EXTRA_TITLE, "mypicture.png");
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(intent, CODE_WRITE);
- }
- });
- view.addView(button);
-
- button = new Button(context);
- button.setText("GET_CONTENT */*");
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- if (multiple.isChecked()) {
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
- }
- if (localOnly.isChecked()) {
- intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
- }
- startActivityForResult(Intent.createChooser(intent, "Kittens!"), CODE_READ);
- }
- });
- view.addView(button);
-
- final ScrollView scroll = new ScrollView(context);
- scroll.addView(view);
-
- setContentView(scroll);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- mResult.setText(null);
- String result = "resultCode=" + resultCode + ", data=" + String.valueOf(data);
-
- if (requestCode == CODE_READ) {
- final Uri uri = data != null ? data.getData() : null;
- if (uri != null) {
- if (DocumentsContract.isDocumentUri(this, uri)) {
- result += "; DOC_ID";
- }
- try {
- getContentResolver().takePersistableUriPermission(
- uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- } catch (SecurityException e) {
- result += "; FAILED TO TAKE";
- Log.e(TAG, "Failed to take", e);
- }
- InputStream is = null;
- try {
- is = getContentResolver().openInputStream(uri);
- final int length = Streams.readFullyNoClose(is).length;
- result += "; read length=" + length;
- } catch (Exception e) {
- result += "; ERROR";
- Log.e(TAG, "Failed to read " + uri, e);
- } finally {
- IoUtils.closeQuietly(is);
- }
- } else {
- result += "no uri?";
- }
- } else if (requestCode == CODE_WRITE) {
- final Uri uri = data != null ? data.getData() : null;
- if (uri != null) {
- if (DocumentsContract.isDocumentUri(this, uri)) {
- result += "; DOC_ID";
- }
- try {
- getContentResolver().takePersistableUriPermission(
- uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- } catch (SecurityException e) {
- result += "; FAILED TO TAKE";
- Log.e(TAG, "Failed to take", e);
- }
- OutputStream os = null;
- try {
- os = getContentResolver().openOutputStream(uri);
- os.write("THE COMPLETE WORKS OF SHAKESPEARE".getBytes());
- } catch (Exception e) {
- result += "; ERROR";
- Log.e(TAG, "Failed to write " + uri, e);
- } finally {
- IoUtils.closeQuietly(os);
- }
- } else {
- result += "no uri?";
- }
- }
-
- Log.d(TAG, result);
- mResult.setText(result);
- }
-}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 559e052..16fc3e5 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -27,6 +27,7 @@
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.FileObserver;
+import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
@@ -143,7 +144,7 @@
final RootInfo root = new RootInfo();
root.rootId = rootId;
root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
- | Root.FLAG_SUPPORTS_SEARCH;
+ | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_DIR_SELECTION;
if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
root.title = getContext().getString(R.string.root_internal_storage);
} else {
@@ -240,8 +241,8 @@
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else {
flags |= Document.FLAG_SUPPORTS_WRITE;
+ flags |= Document.FLAG_SUPPORTS_DELETE;
}
- flags |= Document.FLAG_SUPPORTS_DELETE;
}
final String displayName = file.getName();
@@ -284,11 +285,26 @@
}
@Override
+ public boolean isChildDocument(String parentDocId, String docId) {
+ try {
+ final File parent = getFileForDocId(parentDocId).getCanonicalFile();
+ final File doc = getFileForDocId(docId).getCanonicalFile();
+ return FileUtils.contains(parent, doc);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to determine if " + docId + " is child of " + parentDocId + ": " + e);
+ }
+ }
+
+ @Override
public String createDocument(String docId, String mimeType, String displayName)
throws FileNotFoundException {
final File parent = getFileForDocId(docId);
- File file;
+ if (!parent.isDirectory()) {
+ throw new IllegalArgumentException("Parent document isn't a directory");
+ }
+ File file;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
file = new File(parent, displayName);
if (!file.mkdir()) {
@@ -317,6 +333,7 @@
@Override
public void deleteDocument(String docId) throws FileNotFoundException {
+ // TODO: extend to delete directories
final File file = getFileForDocId(docId);
if (!file.delete()) {
throw new IllegalStateException("Failed to delete " + file);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index cb624ad..85befff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -35,6 +35,9 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
}
+ protected void onBootCompleted() {
+ }
+
@SuppressWarnings("unchecked")
public <T> T getComponent(Class<T> interfaceType) {
return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 0f55683..103991a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -17,7 +17,12 @@
package com.android.systemui;
import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.os.SystemProperties;
import android.util.Log;
import java.util.HashMap;
@@ -49,6 +54,7 @@
*/
private final SystemUI[] mServices = new SystemUI[SERVICES.length];
private boolean mServicesStarted;
+ private boolean mBootCompleted;
private final Map<Class<?>, Object> mComponents = new HashMap<Class<?>, Object>();
@Override
@@ -58,6 +64,23 @@
// application theme in the manifest does only work for activities. Keep this in sync with
// the theme set there.
setTheme(R.style.systemui_theme);
+
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mBootCompleted) return;
+
+ if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
+ unregisterReceiver(this);
+ mBootCompleted = true;
+ if (mServicesStarted) {
+ final int N = mServices.length;
+ for (int i = 0; i < N; i++) {
+ mServices[i].onBootCompleted();
+ }
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
/**
@@ -71,6 +94,17 @@
if (mServicesStarted) {
return;
}
+
+ if (!mBootCompleted) {
+ // check to see if maybe it was already completed long before we began
+ // see ActivityManagerService.finishBooting()
+ if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
+ mBootCompleted = true;
+ if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
+ }
+ }
+
+ Log.v(TAG, "Starting SystemUI services.");
final int N = SERVICES.length;
for (int i=0; i<N; i++) {
Class<?> cl = SERVICES[i];
@@ -86,6 +120,10 @@
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
+
+ if (mBootCompleted) {
+ mServices[i].onBootCompleted();
+ }
}
mServicesStarted = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 081e8de..ffdb620 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -157,24 +157,6 @@
private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
/**
- * Allow the user to expand the status bar when the keyguard is engaged
- * (without a pattern or password).
- */
- private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true;
-
- /**
- * Allow the user to expand the status bar when a SECURE keyguard is engaged
- * and {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS} is set
- * (private notifications will be masked).
- */
- private static final boolean ENABLE_SECURE_STATUS_BAR_EXPAND = true;
-
- /**
- * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
- */
- private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
-
- /**
* Secure setting whether analytics are collected on the keyguard.
*/
private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics";
@@ -277,11 +259,6 @@
private int mLockSoundStreamId;
/**
- * Tracks value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
- */
- private boolean mAllowNotificationsWhenSecure;
-
- /**
* The volume applied to the lock/unlock sounds.
*/
private float mLockSoundVolume;
@@ -895,13 +872,6 @@
return;
}
- // note whether notification access should be allowed
- mAllowNotificationsWhenSecure = ENABLE_SECURE_STATUS_BAR_EXPAND
- && 0 != Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- ALLOW_NOTIFICATIONS_DEFAULT ? 1 : 0);
-
// if the keyguard is already showing, don't bother
if (mStatusBarKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
@@ -1271,11 +1241,6 @@
// (like recents). Temporary enable/disable (e.g. the "back" button) are
// done in KeyguardHostView.
flags |= StatusBarManager.DISABLE_RECENT;
- if (isSecure()) {
- // showing secure lockscreen; disable ticker and switch private notifications
- // to show their public versions, if available.
- flags |= StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS;
- }
if (!isAssistantAvailable()) {
flags |= StatusBarManager.DISABLE_SEARCH;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 10b6d49..21c2926 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -26,6 +26,7 @@
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.DisplayMetrics;
@@ -45,6 +46,7 @@
// Which recents to use
boolean mUseAlternateRecents;
AlternateRecentsComponent mAlternateRecents;
+ boolean mBootCompleted = false;
@Override
public void start() {
@@ -60,6 +62,11 @@
}
@Override
+ protected void onBootCompleted() {
+ mBootCompleted = true;
+ }
+
+ @Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
if (mUseAlternateRecents) {
// Launch the alternate recents if required
@@ -197,13 +204,11 @@
Intent intent =
new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);
intent.setPackage("com.android.systemui");
- mContext.sendBroadcastAsUser(intent,
- new UserHandle(UserHandle.USER_CURRENT));
+ sendBroadcastSafely(intent);
}
});
intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);
- mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
- UserHandle.USER_CURRENT));
+ startActivitySafely(intent, opts.toBundle());
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentAppsIntent", e);
@@ -225,7 +230,7 @@
Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsPreloadReceiver");
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ sendBroadcastSafely(intent);
RecentTasksLoader.getInstance(mContext).preloadFirstTask();
}
@@ -239,7 +244,7 @@
Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsPreloadReceiver");
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ sendBroadcastSafely(intent);
RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
}
@@ -252,9 +257,25 @@
} else {
Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
intent.setPackage("com.android.systemui");
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ sendBroadcastSafely(intent);
RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
}
}
+
+ /**
+ * Send broadcast only if BOOT_COMPLETED
+ */
+ private void sendBroadcastSafely(Intent intent) {
+ if (!mBootCompleted) return;
+ mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+
+ /**
+ * Start activity only if BOOT_COMPLETED
+ */
+ private void startActivitySafely(Intent intent, Bundle opts) {
+ if (!mBootCompleted) return;
+ mContext.startActivityAsUser(intent, opts, new UserHandle(UserHandle.USER_CURRENT));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 56f83df..bb481ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -172,7 +172,7 @@
int oldHeight = lp.height;
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
setLayoutParams(lp);
- measure(View.MeasureSpec.makeMeasureSpec(getMeasuredWidth(), View.MeasureSpec.EXACTLY),
+ measure(View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(mRowMaxHeight, View.MeasureSpec.AT_MOST));
lp.height = oldHeight;
setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index cf31b44..1ffb4ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -143,6 +143,10 @@
return false;
}
+ public boolean isSecure() {
+ return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
+ }
+
public boolean onMenuPressed() {
ensureView();
if (mKeyguardView.handleMenuKey()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2d96c5e..7f1ddaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -25,7 +25,6 @@
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
-import static com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -77,7 +76,6 @@
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewPropertyAnimator;
-import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
@@ -148,6 +146,11 @@
private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
+ /**
+ * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
+ */
+ private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
+
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
private static final long AUTOHIDE_TIMEOUT_MS = 3000;
@@ -781,10 +784,11 @@
if (!qsTap && !inButton) {
mSettingsTracker.computeCurrentVelocity(1000);
final float vy = mSettingsTracker.getYVelocity();
+ final boolean animate = true;
if (dy <= slop || vy <= 0) {
- flipToNotifications();
+ flipToNotifications(animate);
} else {
- flipToSettings();
+ flipToSettings(animate);
}
}
mSettingsTracker.recycle();
@@ -1452,8 +1456,6 @@
flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts");
flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) ? "PRIVATE" : "private");
- flagdbg.append(((diff & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info");
flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
@@ -1538,15 +1540,6 @@
.setDuration(175)
.start();
}
- } else if ((diff & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) {
- if ((state & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) {
- // we are outside a secure keyguard, so we need to switch to "public" mode
- setLockscreenPublicMode(true);
- } else {
- // user has authenticated the device; full notifications may be shown
- setLockscreenPublicMode(false);
- }
- updateNotificationIcons();
}
}
@@ -1669,7 +1662,7 @@
mStatusBarWindow.cancelExpandHelper();
mStatusBarView.collapseAllPanels(true);
if (isFlippedToSettings()) {
- flipToNotifications();
+ flipToNotifications(true /*animate*/);
}
}
}
@@ -1729,36 +1722,49 @@
mNotificationPanel.expand();
if (mStackScroller.getVisibility() != View.VISIBLE) {
- flipToNotifications();
+ flipToNotifications(true /*animate*/);
}
if (false) postStartTracing();
}
- public void flipToNotifications() {
- if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel();
- if (mScrollViewAnim != null) mScrollViewAnim.cancel();
+ private static void cancelAnim(Animator anim) {
+ if (anim != null) {
+ anim.cancel();
+ }
+ }
+
+ public void flipToNotifications(boolean animate) {
+ cancelAnim(mFlipSettingsViewAnim);
+ cancelAnim(mScrollViewAnim);
+ cancelAnim(mClearButtonAnim);
mHeaderFlipper.cancel();
mKeyguardFlipper.cancel();
- if (mClearButtonAnim != null) mClearButtonAnim.cancel();
mStackScroller.setVisibility(View.VISIBLE);
final int h = mNotificationPanel.getMeasuredHeight();
- final float settingsY = mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : 0;
- final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : h;
- mScrollViewAnim = start(
- interpolator(mDecelerateInterpolator,
- ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, 0)
- .setDuration(FLIP_DURATION)
- ));
- mFlipSettingsViewAnim = start(
- setVisibilityWhenDone(
- interpolator(mDecelerateInterpolator,
- ObjectAnimator.ofFloat(mFlipSettingsView, View.TRANSLATION_Y, settingsY, -h)
- )
- .setDuration(FLIP_DURATION),
- mFlipSettingsView, View.INVISIBLE));
- mHeaderFlipper.flipToNotifications();
- mKeyguardFlipper.flipToNotifications();
+ if (animate) {
+ final float settingsY =
+ mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : 0;
+ final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : h;
+ mScrollViewAnim = start(
+ interpolator(mDecelerateInterpolator,
+ ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, 0)
+ .setDuration(FLIP_DURATION)
+ ));
+ mFlipSettingsViewAnim = start(
+ setVisibilityWhenDone(
+ interpolator(mDecelerateInterpolator,
+ ObjectAnimator.ofFloat(
+ mFlipSettingsView, View.TRANSLATION_Y, settingsY, -h))
+ .setDuration(FLIP_DURATION),
+ mFlipSettingsView, View.INVISIBLE));
+ } else {
+ mStackScroller.setTranslationY(0);
+ mFlipSettingsView.setTranslationY(-h);
+ mFlipSettingsView.setVisibility(View.INVISIBLE);
+ }
+ mHeaderFlipper.flipToNotifications(animate);
+ mKeyguardFlipper.flipToNotifications(animate);
mClearButton.setVisibility(View.VISIBLE);
mClearButton.setAlpha(0f);
setAreThereNotifications(); // this will show/hide the button as necessary
@@ -1766,7 +1772,7 @@
public void run() {
updateCarrierLabelVisibility(false);
}
- }, FLIP_DURATION - 150);
+ }, animate ? FLIP_DURATION - 150 : 0);
if (mOnFlipRunnable != null) {
mOnFlipRunnable.run();
}
@@ -1785,7 +1791,7 @@
mNotificationPanel.expand();
if (mFlipSettingsView.getVisibility() != View.VISIBLE
|| mFlipSettingsView.getTranslationY() < 0) {
- flipToSettings();
+ flipToSettings(true /*animate*/);
}
if (false) postStartTracing();
@@ -1798,58 +1804,63 @@
return false;
}
- public void flipToSettings() {
+ public void flipToSettings(boolean animate) {
// Settings are not available in setup
if (!mUserSetup) return;
- if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel();
- if (mScrollViewAnim != null) mScrollViewAnim.cancel();
+ cancelAnim(mFlipSettingsViewAnim);
+ cancelAnim(mScrollViewAnim);
mHeaderFlipper.cancel();
mKeyguardFlipper.cancel();
- if (mClearButtonAnim != null) mClearButtonAnim.cancel();
+ cancelAnim(mClearButtonAnim);
mFlipSettingsView.setVisibility(View.VISIBLE);
final int h = mNotificationPanel.getMeasuredHeight();
- final float settingsY = mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : -h;
- final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : 0;
- mFlipSettingsViewAnim = start(
- startDelay(0,
+ if (animate) {
+ final float settingsY
+ = mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : -h;
+ final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : 0;
+ mFlipSettingsViewAnim = start(
+ startDelay(0,
+ interpolator(mDecelerateInterpolator,
+ ObjectAnimator.ofFloat(mFlipSettingsView, View.TRANSLATION_Y,
+ settingsY, 0f)
+ .setDuration(FLIP_DURATION)
+ )));
+ mScrollViewAnim = start(
+ setVisibilityWhenDone(
interpolator(mDecelerateInterpolator,
- ObjectAnimator.ofFloat(mFlipSettingsView, View.TRANSLATION_Y, settingsY, 0f)
- .setDuration(FLIP_DURATION)
- )));
- mScrollViewAnim = start(
- setVisibilityWhenDone(
- interpolator(mDecelerateInterpolator,
- ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, h)
- )
+ ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, h)
+ )
+ .setDuration(FLIP_DURATION),
+ mStackScroller, View.INVISIBLE));
+ } else {
+ mFlipSettingsView.setTranslationY(0);
+ mStackScroller.setTranslationY(h);
+ mStackScroller.setVisibility(View.INVISIBLE);
+ }
+ mHeaderFlipper.flipToSettings(animate);
+ mKeyguardFlipper.flipToSettings(animate);
+ if (animate) {
+ mClearButtonAnim = start(
+ setVisibilityWhenDone(
+ ObjectAnimator.ofFloat(mClearButton, View.ALPHA, 0f)
.setDuration(FLIP_DURATION),
- mStackScroller, View.INVISIBLE));
- mHeaderFlipper.flipToSettings();
- mKeyguardFlipper.flipToSettings();
- mClearButtonAnim = start(
- setVisibilityWhenDone(
- ObjectAnimator.ofFloat(mClearButton, View.ALPHA, 0f)
- .setDuration(FLIP_DURATION),
- mClearButton, View.INVISIBLE));
+ mClearButton, View.INVISIBLE));
+ } else {
+ mClearButton.setAlpha(0);
+ mClearButton.setVisibility(View.INVISIBLE);
+ }
mNotificationPanel.postDelayed(new Runnable() {
public void run() {
updateCarrierLabelVisibility(false);
}
- }, FLIP_DURATION - 150);
+ }, animate ? FLIP_DURATION - 150 : 0);
if (mOnFlipRunnable != null) {
mOnFlipRunnable.run();
}
}
- public void flipPanels() {
- if (mFlipSettingsView.getVisibility() != View.VISIBLE) {
- flipToSettings();
- } else {
- flipToNotifications();
- }
- }
-
public void animateCollapseQuickSettings() {
mStatusBarView.collapseAllPanels(true);
}
@@ -2910,31 +2921,44 @@
public void showKeyguard() {
mOnKeyguard = true;
+ updateKeyguardState();
instantExpandNotificationsPanel();
- if (isFlippedToSettings()) {
- flipToNotifications();
- }
- mKeyguardStatusView.setVisibility(View.VISIBLE);
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
- mNotificationPanelHeader.setVisibility(View.GONE);
-
- mKeyguardFlipper.setVisibility(View.VISIBLE);
- mSettingsContainer.setKeyguardShowing(true);
- updateRowStates();
- checkBarModes();
}
public void hideKeyguard() {
mOnKeyguard = false;
- mKeyguardStatusView.setVisibility(View.GONE);
- mKeyguardBottomArea.setVisibility(View.GONE);
- mNotificationPanelHeader.setVisibility(View.VISIBLE);
-
- mKeyguardFlipper.setVisibility(View.GONE);
- mSettingsContainer.setKeyguardShowing(false);
- updateRowStates();
+ updateKeyguardState();
instantCollapseNotificationPanel();
+ }
+
+ private void updatePublicMode() {
+ setLockscreenPublicMode(mOnKeyguard && mStatusBarKeyguardViewManager.isSecure());
+ }
+
+ private void updateKeyguardState() {
+ if (mOnKeyguard) {
+ if (isFlippedToSettings()) {
+ flipToNotifications(false /*animate*/);
+ }
+ mKeyguardStatusView.setVisibility(View.VISIBLE);
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ mNotificationPanelHeader.setVisibility(View.GONE);
+
+ mKeyguardFlipper.setVisibility(View.VISIBLE);
+ mSettingsContainer.setKeyguardShowing(true);
+ } else {
+ mKeyguardStatusView.setVisibility(View.GONE);
+ mKeyguardBottomArea.setVisibility(View.GONE);
+ mNotificationPanelHeader.setVisibility(View.VISIBLE);
+
+ mKeyguardFlipper.setVisibility(View.GONE);
+ mSettingsContainer.setKeyguardShowing(false);
+ }
+
+ updatePublicMode();
+ updateRowStates();
checkBarModes();
+ updateNotificationIcons();
}
public void userActivity() {
@@ -3075,36 +3099,48 @@
}
}
- public void flipToSettings() {
- mSettingsButtonAnim = start(
- setVisibilityWhenDone(
- ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 0f)
- .setDuration(FLIP_DURATION_OUT),
- mStackScroller, View.INVISIBLE));
+ public void flipToSettings(boolean animate) {
mNotificationButton.setVisibility(View.VISIBLE);
- mNotificationButtonAnim = start(
- startDelay(FLIP_DURATION_OUT,
- ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 1f)
- .setDuration(FLIP_DURATION_IN)));
+ if (animate) {
+ mSettingsButtonAnim = start(
+ setVisibilityWhenDone(
+ ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 0f)
+ .setDuration(FLIP_DURATION_OUT),
+ mStackScroller, View.INVISIBLE));
+ mNotificationButtonAnim = start(
+ startDelay(FLIP_DURATION_OUT,
+ ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 1f)
+ .setDuration(FLIP_DURATION_IN)));
+ } else {
+ mSettingsButton.setAlpha(0f);
+ mSettingsButton.setVisibility(View.INVISIBLE);
+ mNotificationButton.setAlpha(1f);
+ }
}
- public void flipToNotifications() {
- mNotificationButtonAnim = start(
- setVisibilityWhenDone(
- ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 0f)
- .setDuration(FLIP_DURATION_OUT),
- mNotificationButton, View.INVISIBLE));
-
+ public void flipToNotifications(boolean animate) {
mSettingsButton.setVisibility(View.VISIBLE);
- mSettingsButtonAnim = start(
- startDelay(FLIP_DURATION_OUT,
- ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 1f)
- .setDuration(FLIP_DURATION_IN)));
+ if (animate) {
+ mNotificationButtonAnim = start(
+ setVisibilityWhenDone(
+ ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 0f)
+ .setDuration(FLIP_DURATION_OUT),
+ mNotificationButton, View.INVISIBLE));
+
+ mSettingsButtonAnim = start(
+ startDelay(FLIP_DURATION_OUT,
+ ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 1f)
+ .setDuration(FLIP_DURATION_IN)));
+ } else {
+ mNotificationButton.setVisibility(View.INVISIBLE);
+ mNotificationButton.setAlpha(0f);
+ mSettingsButton.setAlpha(1f);
+ }
}
public void cancel() {
- if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel();
- if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel();
+ cancelAnim(mSettingsButtonAnim);
+ cancelAnim(mNotificationButtonAnim);
}
public void setVisibility(int vis) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index b3fba76..f3ebf1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -280,6 +280,7 @@
addUserTiles(mContainerView, inflater);
addSystemTiles(mContainerView, inflater);
addTemporaryTiles(mContainerView, inflater);
+ addAccessibilityTiles(mContainerView);
queryForUserInformation();
queryForSslCaCerts();
@@ -311,6 +312,34 @@
collapsePanels();
}
+ private void addAccessibilityTiles(ViewGroup parent) {
+ if (!DEBUG_GONE_TILES && !SHOW_ACCESSIBILITY_TILES) return;
+
+ // Color inversion tile
+ final SystemSettingTile inversionTile = new SystemSettingTile(mContext);
+ inversionTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ SystemSettingTile.TYPE_SECURE);
+ inversionTile.setFragment("Settings$AccessibilityInversionSettingsActivity");
+ mModel.addInversionTile(inversionTile, inversionTile.getRefreshCallback());
+ parent.addView(inversionTile);
+
+ // Contrast enhancement tile
+ final SystemSettingTile contrastTile = new SystemSettingTile(mContext);
+ contrastTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED,
+ SystemSettingTile.TYPE_SECURE);
+ contrastTile.setFragment("Settings$AccessibilityContrastSettingsActivity");
+ mModel.addContrastTile(contrastTile, contrastTile.getRefreshCallback());
+ parent.addView(contrastTile);
+
+ // Color space adjustment tile
+ final SystemSettingTile colorSpaceTile = new SystemSettingTile(mContext);
+ colorSpaceTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ SystemSettingTile.TYPE_SECURE);
+ colorSpaceTile.setFragment("Settings$AccessibilityDaltonizerSettingsActivity");
+ mModel.addColorSpaceTile(colorSpaceTile, colorSpaceTile.getRefreshCallback());
+ parent.addView(colorSpaceTile);
+ }
+
private void addUserTiles(final ViewGroup parent, final LayoutInflater inflater) {
QuickSettingsTileView userTile = (QuickSettingsTileView)
inflater.inflate(R.layout.quick_settings_tile, parent, false);
@@ -384,35 +413,6 @@
new QuickSettingsModel.BasicRefreshCallback(settingsTile));
parent.addView(settingsTile);
mDynamicSpannedTiles.add(settingsTile);
-
- if (SHOW_ACCESSIBILITY_TILES) {
- // Color inversion tile
- final SystemSettingTile inversionTile = new SystemSettingTile(mContext);
- inversionTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
- SystemSettingTile.TYPE_SECURE);
- inversionTile.setFragment("Settings$AccessibilityInversionSettingsActivity");
- mModel.addInversionTile(inversionTile, inversionTile.getRefreshCallback());
- parent.addView(inversionTile);
- mDynamicSpannedTiles.add(inversionTile);
-
- // Contrast enhancement tile
- final SystemSettingTile contrastTile = new SystemSettingTile(mContext);
- contrastTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED,
- SystemSettingTile.TYPE_SECURE);
- contrastTile.setFragment("Settings$AccessibilityContrastSettingsActivity");
- mModel.addContrastTile(contrastTile, contrastTile.getRefreshCallback());
- parent.addView(contrastTile);
- mDynamicSpannedTiles.add(contrastTile);
-
- // Color space adjustment tile
- final SystemSettingTile colorSpaceTile = new SystemSettingTile(mContext);
- colorSpaceTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
- SystemSettingTile.TYPE_SECURE);
- colorSpaceTile.setFragment("Settings$AccessibilityDaltonizerSettingsActivity");
- mModel.addColorSpaceTile(colorSpaceTile, colorSpaceTile.getRefreshCallback());
- parent.addView(colorSpaceTile);
- mDynamicSpannedTiles.add(colorSpaceTile);
- }
}
private void addSystemTiles(ViewGroup parent, LayoutInflater inflater) {
@@ -574,49 +574,6 @@
});
parent.addView(batteryTile);
- // Airplane Mode
- final QuickSettingsBasicTile airplaneTile
- = new QuickSettingsBasicTile(mContext);
- mModel.addAirplaneModeTile(airplaneTile, new QuickSettingsModel.RefreshCallback() {
- @Override
- public void refreshView(QuickSettingsTileView unused, State state) {
- airplaneTile.setImageResource(state.iconId);
-
- String airplaneState = mContext.getString(
- (state.enabled) ? R.string.accessibility_desc_on
- : R.string.accessibility_desc_off);
- airplaneTile.setContentDescription(
- mContext.getString(R.string.accessibility_quick_settings_airplane, airplaneState));
- airplaneTile.setText(state.label);
- }
- });
- parent.addView(airplaneTile);
-
- // Zen Mode
- final QuickSettingsBasicTile zenModeTile = new QuickSettingsBasicTile(mContext);
- zenModeTile.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showZenModeDialog();
- }
- });
- mModel.addZenModeTile(zenModeTile, new QuickSettingsModel.RefreshCallback() {
- @Override
- public void refreshView(QuickSettingsTileView unused, State state) {
- zenModeTile.setImageResource(state.iconId);
- // TODO cut new assets
- zenModeTile.getImageView().setAlpha(state.enabled ? 1 : .2f);
- zenModeTile.getImageView().setScaleX(1.5f);
- zenModeTile.getImageView().setScaleY(1.5f);
- // for landscape version
- zenModeTile.getTextView().setMaxLines(2);
- zenModeTile.getTextView().setEllipsize(TruncateAt.END);
- // TODO content description
- zenModeTile.setText(state.label);
- }
- });
- parent.addView(zenModeTile);
-
// Bluetooth
if (mModel.deviceSupportsBluetooth()
|| DEBUG_GONE_TILES) {
@@ -710,6 +667,50 @@
}
});
parent.addView(locationTile);
+
+ // Airplane Mode
+ final QuickSettingsBasicTile airplaneTile
+ = new QuickSettingsBasicTile(mContext);
+ mModel.addAirplaneModeTile(airplaneTile, new QuickSettingsModel.RefreshCallback() {
+ @Override
+ public void refreshView(QuickSettingsTileView unused, State state) {
+ airplaneTile.setImageResource(state.iconId);
+
+ String airplaneState = mContext.getString(
+ (state.enabled) ? R.string.accessibility_desc_on
+ : R.string.accessibility_desc_off);
+ airplaneTile.setContentDescription(
+ mContext.getString(R.string.accessibility_quick_settings_airplane,
+ airplaneState));
+ airplaneTile.setText(state.label);
+ }
+ });
+ parent.addView(airplaneTile);
+
+ // Zen Mode
+ final QuickSettingsBasicTile zenModeTile = new QuickSettingsBasicTile(mContext);
+ zenModeTile.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showZenModeDialog();
+ }
+ });
+ mModel.addZenModeTile(zenModeTile, new QuickSettingsModel.RefreshCallback() {
+ @Override
+ public void refreshView(QuickSettingsTileView unused, State state) {
+ zenModeTile.setImageResource(state.iconId);
+ // TODO cut new assets
+ zenModeTile.getImageView().setAlpha(state.enabled ? 1 : .2f);
+ zenModeTile.getImageView().setScaleX(1.5f);
+ zenModeTile.getImageView().setScaleY(1.5f);
+ // for landscape version
+ zenModeTile.getTextView().setMaxLines(2);
+ zenModeTile.getTextView().setEllipsize(TruncateAt.END);
+ // TODO content description
+ zenModeTile.setText(state.label);
+ }
+ });
+ parent.addView(zenModeTile);
}
private void addTemporaryTiles(final ViewGroup parent, final LayoutInflater inflater) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 501d3f2..c2595cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -179,6 +179,10 @@
}
}
+ public boolean isSecure() {
+ return mBouncer.isSecure();
+ }
+
/**
* @return Whether the keyguard is showing
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index a7363f4..d9e7f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -533,7 +533,23 @@
} else {
// We are expanding the shade, expand it to its full height.
- mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+ if (mFirstChildWhileExpanding.getWidth() == 0) {
+
+ // This child was not layouted yet, wait for a layout pass
+ mFirstChildWhileExpanding
+ .addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight,
+ int oldBottom) {
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(
+ mFirstChildWhileExpanding);
+ mFirstChildWhileExpanding.removeOnLayoutChangeListener(this);
+ }
+ });
+ } else {
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+ }
}
} else {
mFirstChildMaxHeight = 0;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8d1a24a..8fa076b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17,8 +17,10 @@
package com.android.server.am;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
@@ -724,6 +726,7 @@
private static final String ATTR_URI = "uri";
private static final String ATTR_MODE_FLAGS = "modeFlags";
private static final String ATTR_CREATED_TIME = "createdTime";
+ private static final String ATTR_PREFIX = "prefix";
/**
* Global set of specific {@link Uri} permissions that have been granted.
@@ -731,8 +734,41 @@
* to {@link UriPermission#uri} to {@link UriPermission}.
*/
@GuardedBy("this")
- private final SparseArray<ArrayMap<Uri, UriPermission>>
- mGrantedUriPermissions = new SparseArray<ArrayMap<Uri, UriPermission>>();
+ private final SparseArray<ArrayMap<GrantUri, UriPermission>>
+ mGrantedUriPermissions = new SparseArray<ArrayMap<GrantUri, UriPermission>>();
+
+ public static class GrantUri {
+ public final Uri uri;
+ public final boolean prefix;
+
+ public GrantUri(Uri uri, boolean prefix) {
+ this.uri = uri;
+ this.prefix = prefix;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GrantUri) {
+ GrantUri other = (GrantUri) o;
+ return uri.equals(other.uri) && prefix == other.prefix;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ if (prefix) {
+ return uri.toString() + " [prefix]";
+ } else {
+ return uri.toString();
+ }
+ }
+ }
CoreSettingsObserver mCoreSettingsObserver;
@@ -5839,7 +5875,7 @@
* in {@link ContentProvider}.
*/
private final boolean checkHoldingPermissionsLocked(
- IPackageManager pm, ProviderInfo pi, Uri uri, int uid, int modeFlags) {
+ IPackageManager pm, ProviderInfo pi, Uri uri, int uid, final int modeFlags) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid);
@@ -5935,18 +5971,17 @@
return pi;
}
- private UriPermission findUriPermissionLocked(int targetUid, Uri uri) {
- ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+ private UriPermission findUriPermissionLocked(int targetUid, GrantUri uri) {
+ final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris != null) {
return targetUris.get(uri);
- } else {
- return null;
}
+ return null;
}
- private UriPermission findOrCreateUriPermissionLocked(
- String sourcePkg, String targetPkg, int targetUid, Uri uri) {
- ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+ private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,
+ String targetPkg, int targetUid, GrantUri uri) {
+ ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris == null) {
targetUris = Maps.newArrayMap();
mGrantedUriPermissions.put(targetUid, targetUris);
@@ -5961,21 +5996,40 @@
return perm;
}
- private final boolean checkUriPermissionLocked(
- Uri uri, int uid, int modeFlags, int minStrength) {
+ private final boolean checkUriPermissionLocked(Uri uri, int uid, final int modeFlags) {
+ final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+ final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
+ : UriPermission.STRENGTH_OWNED;
+
// Root gets to do everything.
if (uid == 0) {
return true;
}
- ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
if (perms == null) return false;
- UriPermission perm = perms.get(uri);
- if (perm == null) return false;
- return perm.getStrength(modeFlags) >= minStrength;
+
+ // First look for exact match
+ final UriPermission exactPerm = perms.get(new GrantUri(uri, false));
+ if (exactPerm != null && exactPerm.getStrength(modeFlags) >= minStrength) {
+ return true;
+ }
+
+ // No exact match, look for prefixes
+ final int N = perms.size();
+ for (int i = 0; i < N; i++) {
+ final UriPermission perm = perms.valueAt(i);
+ if (perm.uri.prefix && uri.isPathPrefixMatch(perm.uri.uri)
+ && perm.getStrength(modeFlags) >= minStrength) {
+ return true;
+ }
+ }
+
+ return false;
}
@Override
- public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ public int checkUriPermission(Uri uri, int pid, int uid, final int modeFlags) {
enforceNotIsolatedCaller("checkUriPermission");
// Another redirected-binder-call permissions check as in
@@ -5990,8 +6044,8 @@
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
- synchronized(this) {
- return checkUriPermissionLocked(uri, uid, modeFlags, UriPermission.STRENGTH_OWNED)
+ synchronized (this) {
+ return checkUriPermissionLocked(uri, uid, modeFlags)
? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED;
}
@@ -6007,11 +6061,8 @@
* lastTargetUid else set that to -1.
*/
int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
- Uri uri, int modeFlags, int lastTargetUid) {
- final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
- modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- if (modeFlags == 0) {
+ Uri uri, final int modeFlags, int lastTargetUid) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
return -1;
}
@@ -6106,9 +6157,7 @@
if (callingUid != Process.myUid()) {
if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
// Require they hold a strong enough Uri permission
- final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
- : UriPermission.STRENGTH_OWNED;
- if (!checkUriPermissionLocked(uri, callingUid, modeFlags, minStrength)) {
+ if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
throw new SecurityException("Uid " + callingUid
+ " does not have permission to uri " + uri);
}
@@ -6120,19 +6169,16 @@
@Override
public int checkGrantUriPermission(int callingUid, String targetPkg,
- Uri uri, int modeFlags) {
+ Uri uri, final int modeFlags) {
enforceNotIsolatedCaller("checkGrantUriPermission");
synchronized(this) {
return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1);
}
}
- void grantUriPermissionUncheckedLocked(
- int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
- final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
- modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- if (modeFlags == 0) {
+ void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, Uri uri,
+ final int modeFlags, UriPermissionOwner owner) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
return;
}
@@ -6150,13 +6196,14 @@
return;
}
+ final boolean prefix = (modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0;
final UriPermission perm = findOrCreateUriPermissionLocked(
- pi.packageName, targetPkg, targetUid, uri);
- perm.grantModes(modeFlags, persistable, owner);
+ pi.packageName, targetPkg, targetUid, new GrantUri(uri, prefix));
+ perm.grantModes(modeFlags, owner);
}
void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri,
- int modeFlags, UriPermissionOwner owner) {
+ final int modeFlags, UriPermissionOwner owner) {
if (targetPkg == null) {
throw new NullPointerException("targetPkg");
}
@@ -6270,7 +6317,7 @@
@Override
public void grantUriPermission(IApplicationThread caller, String targetPkg,
- Uri uri, int modeFlags) {
+ Uri uri, final int modeFlags) {
enforceNotIsolatedCaller("grantUriPermission");
synchronized(this) {
final ProcessRecord r = getRecordForAppLocked(caller);
@@ -6286,32 +6333,32 @@
throw new IllegalArgumentException("null uri");
}
- // Persistable only supported through Intents
- Preconditions.checkFlagsArgument(modeFlags,
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ Preconditions.checkFlagsArgument(modeFlags, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags,
- null);
+ grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags, null);
}
}
void removeUriPermissionIfNeededLocked(UriPermission perm) {
- if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION
- |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) {
- ArrayMap<Uri, UriPermission> perms
- = mGrantedUriPermissions.get(perm.targetUid);
+ if (perm.modeFlags == 0) {
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
+ perm.targetUid);
if (perms != null) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"Removing " + perm.targetUid + " permission to " + perm.uri);
+
perms.remove(perm.uri);
- if (perms.size() == 0) {
+ if (perms.isEmpty()) {
mGrantedUriPermissions.remove(perm.targetUid);
}
}
}
}
- private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) {
+ private void revokeUriPermissionLocked(int callingUid, Uri uri, final int modeFlags) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri);
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -6335,46 +6382,29 @@
boolean persistChanged = false;
// Go through all of the permissions and remove any that match.
- final List<String> SEGMENTS = uri.getPathSegments();
- if (SEGMENTS != null) {
- final int NS = SEGMENTS.size();
- int N = mGrantedUriPermissions.size();
- for (int i=0; i<N; i++) {
- ArrayMap<Uri, UriPermission> perms
- = mGrantedUriPermissions.valueAt(i);
- Iterator<UriPermission> it = perms.values().iterator();
- toploop:
- while (it.hasNext()) {
- UriPermission perm = it.next();
- Uri targetUri = perm.uri;
- if (!authority.equals(targetUri.getAuthority())) {
- continue;
- }
- List<String> targetSegments = targetUri.getPathSegments();
- if (targetSegments == null) {
- continue;
- }
- if (targetSegments.size() < NS) {
- continue;
- }
- for (int j=0; j<NS; j++) {
- if (!SEGMENTS.get(j).equals(targetSegments.get(j))) {
- continue toploop;
- }
- }
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Revoking " + perm.targetUid + " permission to " + perm.uri);
- persistChanged |= perm.clearModes(modeFlags, true);
+ int N = mGrantedUriPermissions.size();
+ for (int i = 0; i < N; i++) {
+ final int targetUid = mGrantedUriPermissions.keyAt(i);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
+ for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
+ final UriPermission perm = it.next();
+ if (perm.uri.uri.isPathPrefixMatch(uri)) {
+ if (DEBUG_URI_PERMISSION)
+ Slog.v(TAG,
+ "Revoking " + perm.targetUid + " permission to " + perm.uri);
+ persistChanged |= perm.revokeModes(
+ modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
if (perm.modeFlags == 0) {
it.remove();
}
}
- if (perms.size() == 0) {
- mGrantedUriPermissions.remove(
- mGrantedUriPermissions.keyAt(i));
- N--;
- i--;
- }
+ }
+
+ if (perms.isEmpty()) {
+ mGrantedUriPermissions.remove(targetUid);
+ N--;
+ i--;
}
}
@@ -6385,7 +6415,7 @@
@Override
public void revokeUriPermission(IApplicationThread caller, Uri uri,
- int modeFlags) {
+ final int modeFlags) {
enforceNotIsolatedCaller("revokeUriPermission");
synchronized(this) {
final ProcessRecord r = getRecordForAppLocked(caller);
@@ -6399,9 +6429,7 @@
return;
}
- modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- if (modeFlags == 0) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
return;
}
@@ -6436,20 +6464,22 @@
boolean persistChanged = false;
- final int size = mGrantedUriPermissions.size();
- for (int i = 0; i < size; i++) {
+ int N = mGrantedUriPermissions.size();
+ for (int i = 0; i < N; i++) {
+ final int targetUid = mGrantedUriPermissions.keyAt(i);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
// Only inspect grants matching user
if (userHandle == UserHandle.USER_ALL
- || userHandle == UserHandle.getUserId(mGrantedUriPermissions.keyAt(i))) {
- final Iterator<UriPermission> it = mGrantedUriPermissions.valueAt(i)
- .values().iterator();
- while (it.hasNext()) {
+ || userHandle == UserHandle.getUserId(targetUid)) {
+ for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
final UriPermission perm = it.next();
// Only inspect grants matching package
if (packageName == null || perm.sourcePkg.equals(packageName)
|| perm.targetPkg.equals(packageName)) {
- persistChanged |= perm.clearModes(~0, persistable);
+ persistChanged |= perm.revokeModes(
+ persistable ? ~0 : ~Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
// Only remove when no modes remain; any persisted grants
// will keep this alive.
@@ -6458,6 +6488,12 @@
}
}
}
+
+ if (perms.isEmpty()) {
+ mGrantedUriPermissions.remove(targetUid);
+ N--;
+ i--;
+ }
}
}
@@ -6477,7 +6513,7 @@
@Override
public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg,
- Uri uri, int modeFlags) {
+ Uri uri, final int modeFlags) {
synchronized(this) {
UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
if (owner == null) {
@@ -6531,8 +6567,9 @@
ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
synchronized (this) {
final int size = mGrantedUriPermissions.size();
- for (int i = 0 ; i < size; i++) {
- for (UriPermission perm : mGrantedUriPermissions.valueAt(i).values()) {
+ for (int i = 0; i < size; i++) {
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+ for (UriPermission perm : perms.values()) {
if (perm.persistedModeFlags != 0) {
persist.add(perm.snapshot());
}
@@ -6553,7 +6590,8 @@
writeIntAttribute(out, ATTR_USER_HANDLE, perm.userHandle);
out.attribute(null, ATTR_SOURCE_PKG, perm.sourcePkg);
out.attribute(null, ATTR_TARGET_PKG, perm.targetPkg);
- out.attribute(null, ATTR_URI, String.valueOf(perm.uri));
+ out.attribute(null, ATTR_URI, String.valueOf(perm.uri.uri));
+ writeBooleanAttribute(out, ATTR_PREFIX, perm.uri.prefix);
writeIntAttribute(out, ATTR_MODE_FLAGS, perm.persistedModeFlags);
writeLongAttribute(out, ATTR_CREATED_TIME, perm.persistedCreateTime);
out.endTag(null, TAG_URI_GRANT);
@@ -6589,6 +6627,7 @@
final String sourcePkg = in.getAttributeValue(null, ATTR_SOURCE_PKG);
final String targetPkg = in.getAttributeValue(null, ATTR_TARGET_PKG);
final Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI));
+ final boolean prefix = readBooleanAttribute(in, ATTR_PREFIX);
final int modeFlags = readIntAttribute(in, ATTR_MODE_FLAGS);
final long createdTime = readLongAttribute(in, ATTR_CREATED_TIME, now);
@@ -6604,7 +6643,7 @@
}
if (targetUid != -1) {
final UriPermission perm = findOrCreateUriPermissionLocked(
- sourcePkg, targetPkg, targetUid, uri);
+ sourcePkg, targetPkg, targetUid, new GrantUri(uri, prefix));
perm.initPersistedModes(modeFlags, createdTime);
}
} else {
@@ -6626,7 +6665,7 @@
}
@Override
- public void takePersistableUriPermission(Uri uri, int modeFlags) {
+ public void takePersistableUriPermission(Uri uri, final int modeFlags) {
enforceNotIsolatedCaller("takePersistableUriPermission");
Preconditions.checkFlagsArgument(modeFlags,
@@ -6634,13 +6673,28 @@
synchronized (this) {
final int callingUid = Binder.getCallingUid();
- final UriPermission perm = findUriPermissionLocked(callingUid, uri);
- if (perm == null) {
- throw new SecurityException("No permission grant found for UID " + callingUid
- + " and Uri " + uri.toSafeString());
+ boolean persistChanged = false;
+
+ UriPermission exactPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, false));
+ UriPermission prefixPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, true));
+
+ final boolean exactValid = (exactPerm != null)
+ && ((modeFlags & exactPerm.persistableModeFlags) == modeFlags);
+ final boolean prefixValid = (prefixPerm != null)
+ && ((modeFlags & prefixPerm.persistableModeFlags) == modeFlags);
+
+ if (!(exactValid || prefixValid)) {
+ throw new SecurityException("No persistable permission grants found for UID "
+ + callingUid + " and Uri " + uri.toSafeString());
}
- boolean persistChanged = perm.takePersistableModes(modeFlags);
+ if (exactValid) {
+ persistChanged |= exactPerm.takePersistableModes(modeFlags);
+ }
+ if (prefixValid) {
+ persistChanged |= prefixPerm.takePersistableModes(modeFlags);
+ }
+
persistChanged |= maybePrunePersistedUriGrantsLocked(callingUid);
if (persistChanged) {
@@ -6650,7 +6704,7 @@
}
@Override
- public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+ public void releasePersistableUriPermission(Uri uri, final int modeFlags) {
enforceNotIsolatedCaller("releasePersistableUriPermission");
Preconditions.checkFlagsArgument(modeFlags,
@@ -6658,16 +6712,24 @@
synchronized (this) {
final int callingUid = Binder.getCallingUid();
+ boolean persistChanged = false;
- final UriPermission perm = findUriPermissionLocked(callingUid, uri);
- if (perm == null) {
- Slog.w(TAG, "No permission grant found for UID " + callingUid + " and Uri "
- + uri.toSafeString());
- return;
+ UriPermission exactPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, false));
+ UriPermission prefixPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, true));
+ if (exactPerm == null && prefixPerm == null) {
+ throw new SecurityException("No permission grants found for UID " + callingUid
+ + " and Uri " + uri.toSafeString());
}
- final boolean persistChanged = perm.releasePersistableModes(modeFlags);
- removeUriPermissionIfNeededLocked(perm);
+ if (exactPerm != null) {
+ persistChanged |= exactPerm.releasePersistableModes(modeFlags);
+ removeUriPermissionIfNeededLocked(exactPerm);
+ }
+ if (prefixPerm != null) {
+ persistChanged |= prefixPerm.releasePersistableModes(modeFlags);
+ removeUriPermissionIfNeededLocked(prefixPerm);
+ }
+
if (persistChanged) {
schedulePersistUriGrants();
}
@@ -6681,7 +6743,7 @@
* @return if any mutations occured that require persisting.
*/
private boolean maybePrunePersistedUriGrantsLocked(int uid) {
- final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
if (perms == null) return false;
if (perms.size() < MAX_PERSISTED_URI_GRANTS) return false;
@@ -6731,13 +6793,12 @@
final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
synchronized (this) {
if (incoming) {
- final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
+ callingUid);
if (perms == null) {
Slog.w(TAG, "No permission grants found for " + packageName);
} else {
- final int size = perms.size();
- for (int i = 0; i < size; i++) {
- final UriPermission perm = perms.valueAt(i);
+ for (UriPermission perm : perms.values()) {
if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) {
result.add(perm.buildPersistedPublicApiObject());
}
@@ -6746,10 +6807,9 @@
} else {
final int size = mGrantedUriPermissions.size();
for (int i = 0; i < size; i++) {
- final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
- final int permsSize = perms.size();
- for (int j = 0; j < permsSize; j++) {
- final UriPermission perm = perms.valueAt(j);
+ final ArrayMap<GrantUri, UriPermission> perms =
+ mGrantedUriPermissions.valueAt(i);
+ for (UriPermission perm : perms.values()) {
if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) {
result.add(perm.buildPersistedPublicApiObject());
}
@@ -7619,11 +7679,11 @@
}
}
}
-
- ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
if (perms != null) {
- for (Map.Entry<Uri, UriPermission> uri : perms.entrySet()) {
- if (uri.getKey().getAuthority().equals(cpi.authority)) {
+ for (GrantUri uri : perms.keySet()) {
+ if (uri.uri.getAuthority().equals(cpi.authority)) {
return null;
}
}
@@ -11590,8 +11650,7 @@
if (dumpUid >= -1 && UserHandle.getAppId(uid) != dumpUid) {
continue;
}
- ArrayMap<Uri, UriPermission> perms
- = mGrantedUriPermissions.valueAt(i);
+ final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
if (!printed) {
if (needSep) pw.println();
needSep = true;
@@ -11599,8 +11658,7 @@
printed = true;
printedAnything = true;
}
- pw.print(" * UID "); pw.print(uid);
- pw.println(" holds:");
+ pw.print(" * UID "); pw.print(uid); pw.println(" holds:");
for (UriPermission perm : perms.values()) {
pw.print(" "); pw.println(perm);
if (dumpAll) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 60adfb0..3e59def 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -215,14 +215,7 @@
pw.print(prefix); pw.print("pendingOptions="); pw.println(pendingOptions);
}
if (uriPermissions != null) {
- if (uriPermissions.readUriPermissions != null) {
- pw.print(prefix); pw.print("readUriPermissions=");
- pw.println(uriPermissions.readUriPermissions);
- }
- if (uriPermissions.writeUriPermissions != null) {
- pw.print(prefix); pw.print("writeUriPermissions=");
- pw.println(uriPermissions.writeUriPermissions);
- }
+ uriPermissions.dump(pw, prefix);
}
pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed);
pw.print(" launchCount="); pw.print(launchCount);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 111f010..afef4ff 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2999,7 +2999,7 @@
}
}
- class ActivityContainer extends IActivityContainer.Stub {
+ class ActivityContainer extends android.app.IActivityContainer.Stub {
final int mStackId;
IActivityContainerCallback mCallback = null;
final ActivityStack mStack;
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index b12843b..443218c 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -29,6 +29,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.net.InetUnixAddress;
@@ -178,7 +179,7 @@
}
static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
- throws ErrnoException {
+ throws ErrnoException, InterruptedIOException {
int totalRead = 0;
while (numBytes > 0) {
int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 363a9b7..e54c95e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -193,14 +193,7 @@
pw.println(si.neededGrants);
}
if (si.uriPermissions != null) {
- if (si.uriPermissions.readUriPermissions != null) {
- pw.print(prefix); pw.print(" readUriPermissions=");
- pw.println(si.uriPermissions.readUriPermissions);
- }
- if (si.uriPermissions.writeUriPermissions != null) {
- pw.print(prefix); pw.print(" writeUriPermissions=");
- pw.println(si.uriPermissions.writeUriPermissions);
- }
+ si.uriPermissions.dump(pw, prefix);
}
}
}
diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java
index 1f12b74..4970b8d 100644
--- a/services/core/java/com/android/server/am/UriPermission.java
+++ b/services/core/java/com/android/server/am/UriPermission.java
@@ -17,15 +17,16 @@
package com.android.server.am;
import android.content.Intent;
-import android.net.Uri;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
+import com.android.server.am.ActivityManagerService.GrantUri;
import com.google.android.collect.Sets;
import java.io.PrintWriter;
import java.util.Comparator;
-import java.util.HashSet;
/**
* Description of a permission granted to an app to access a particular URI.
@@ -50,7 +51,7 @@
/** Cached UID of {@link #targetPkg}; should not be persisted */
final int targetUid;
- final Uri uri;
+ final GrantUri uri;
/**
* Allowed modes. All permission enforcement should use this field. Must
@@ -61,12 +62,13 @@
*/
int modeFlags = 0;
- /** Allowed modes with explicit owner. */
+ /** Allowed modes with active owner. */
int ownedModeFlags = 0;
/** Allowed modes without explicit owner. */
int globalModeFlags = 0;
/** Allowed modes that have been offered for possible persisting. */
int persistableModeFlags = 0;
+
/** Allowed modes that should be persisted across device boots. */
int persistedModeFlags = 0;
@@ -78,12 +80,12 @@
private static final long INVALID_TIME = Long.MIN_VALUE;
- private HashSet<UriPermissionOwner> mReadOwners;
- private HashSet<UriPermissionOwner> mWriteOwners;
+ private ArraySet<UriPermissionOwner> mReadOwners;
+ private ArraySet<UriPermissionOwner> mWriteOwners;
private String stringName;
- UriPermission(String sourcePkg, String targetPkg, int targetUid, Uri uri) {
+ UriPermission(String sourcePkg, String targetPkg, int targetUid, GrantUri uri) {
this.userHandle = UserHandle.getUserId(targetUid);
this.sourcePkg = sourcePkg;
this.targetPkg = targetPkg;
@@ -100,6 +102,9 @@
* global or owner grants.
*/
void initPersistedModes(int modeFlags, long createdTime) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
persistableModeFlags = modeFlags;
persistedModeFlags = modeFlags;
persistedCreateTime = createdTime;
@@ -107,7 +112,11 @@
updateModeFlags();
}
- void grantModes(int modeFlags, boolean persistable, UriPermissionOwner owner) {
+ void grantModes(int modeFlags, UriPermissionOwner owner) {
+ final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
if (persistable) {
persistableModeFlags |= modeFlags;
}
@@ -130,10 +139,14 @@
* @return if mode changes should trigger persisting.
*/
boolean takePersistableModes(int modeFlags) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
if ((modeFlags & persistableModeFlags) != modeFlags) {
- throw new SecurityException("Requested flags 0x"
+ Slog.w(TAG, "Requested flags 0x"
+ Integer.toHexString(modeFlags) + ", but only 0x"
+ Integer.toHexString(persistableModeFlags) + " are allowed");
+ return false;
}
final int before = persistedModeFlags;
@@ -148,6 +161,9 @@
}
boolean releasePersistableModes(int modeFlags) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
final int before = persistedModeFlags;
persistableModeFlags &= ~modeFlags;
@@ -164,7 +180,11 @@
/**
* @return if mode changes should trigger persisting.
*/
- boolean clearModes(int modeFlags, boolean persistable) {
+ boolean revokeModes(int modeFlags) {
+ final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
final int before = persistedModeFlags;
if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
@@ -208,6 +228,8 @@
* Return strength of this permission grant for the given flags.
*/
public int getStrength(int modeFlags) {
+ modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if ((persistableModeFlags & modeFlags) == modeFlags) {
return STRENGTH_PERSISTABLE;
} else if ((globalModeFlags & modeFlags) == modeFlags) {
@@ -221,7 +243,7 @@
private void addReadOwner(UriPermissionOwner owner) {
if (mReadOwners == null) {
- mReadOwners = Sets.newHashSet();
+ mReadOwners = Sets.newArraySet();
ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
updateModeFlags();
}
@@ -246,7 +268,7 @@
private void addWriteOwner(UriPermissionOwner owner) {
if (mWriteOwners == null) {
- mWriteOwners = Sets.newHashSet();
+ mWriteOwners = Sets.newArraySet();
ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
updateModeFlags();
}
@@ -333,7 +355,7 @@
final int userHandle;
final String sourcePkg;
final String targetPkg;
- final Uri uri;
+ final GrantUri uri;
final int persistedModeFlags;
final long persistedCreateTime;
@@ -352,6 +374,6 @@
}
public android.content.UriPermission buildPersistedPublicApiObject() {
- return new android.content.UriPermission(uri, persistedModeFlags, persistedCreateTime);
+ return new android.content.UriPermission(uri.uri, persistedModeFlags, persistedCreateTime);
}
}
diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java
index 7bbd3bc..65d7047 100644
--- a/services/core/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/am/UriPermissionOwner.java
@@ -20,8 +20,11 @@
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
+import android.util.ArraySet;
-import java.util.HashSet;
+import com.google.android.collect.Sets;
+
+import java.io.PrintWriter;
import java.util.Iterator;
final class UriPermissionOwner {
@@ -30,8 +33,8 @@
Binder externalToken;
- HashSet<UriPermission> readUriPermissions; // special access to reading uris.
- HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+ private ArraySet<UriPermission> mReadPerms;
+ private ArraySet<UriPermission> mWritePerms;
class ExternalToken extends Binder {
UriPermissionOwner getOwner() {
@@ -39,9 +42,9 @@
}
}
- UriPermissionOwner(ActivityManagerService _service, Object _owner) {
- service = _service;
- owner = _owner;
+ UriPermissionOwner(ActivityManagerService service, Object owner) {
+ this.service = service;
+ this.owner = owner;
}
Binder getExternalTokenLocked() {
@@ -64,82 +67,76 @@
}
void removeUriPermissionsLocked(int mode) {
- if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
- && readUriPermissions != null) {
- for (UriPermission perm : readUriPermissions) {
- perm.removeReadOwner(this);
- service.removeUriPermissionIfNeededLocked(perm);
- }
- readUriPermissions = null;
- }
- if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
- && writeUriPermissions != null) {
- for (UriPermission perm : writeUriPermissions) {
- perm.removeWriteOwner(this);
- service.removeUriPermissionIfNeededLocked(perm);
- }
- writeUriPermissions = null;
- }
+ removeUriPermissionLocked(null, mode);
}
void removeUriPermissionLocked(Uri uri, int mode) {
- if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
- && readUriPermissions != null) {
- Iterator<UriPermission> it = readUriPermissions.iterator();
+ if ((mode & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+ && mReadPerms != null) {
+ Iterator<UriPermission> it = mReadPerms.iterator();
while (it.hasNext()) {
UriPermission perm = it.next();
- if (uri.equals(perm.uri)) {
+ if (uri == null || uri.equals(perm.uri)) {
perm.removeReadOwner(this);
service.removeUriPermissionIfNeededLocked(perm);
it.remove();
}
}
- if (readUriPermissions.size() == 0) {
- readUriPermissions = null;
+ if (mReadPerms.isEmpty()) {
+ mReadPerms = null;
}
}
- if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
- && writeUriPermissions != null) {
- Iterator<UriPermission> it = writeUriPermissions.iterator();
+ if ((mode & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+ && mWritePerms != null) {
+ Iterator<UriPermission> it = mWritePerms.iterator();
while (it.hasNext()) {
UriPermission perm = it.next();
- if (uri.equals(perm.uri)) {
+ if (uri == null || uri.equals(perm.uri)) {
perm.removeWriteOwner(this);
service.removeUriPermissionIfNeededLocked(perm);
it.remove();
}
}
- if (writeUriPermissions.size() == 0) {
- writeUriPermissions = null;
+ if (mWritePerms.isEmpty()) {
+ mWritePerms = null;
}
}
}
public void addReadPermission(UriPermission perm) {
- if (readUriPermissions == null) {
- readUriPermissions = new HashSet<UriPermission>();
+ if (mReadPerms == null) {
+ mReadPerms = Sets.newArraySet();
}
- readUriPermissions.add(perm);
+ mReadPerms.add(perm);
}
public void addWritePermission(UriPermission perm) {
- if (writeUriPermissions == null) {
- writeUriPermissions = new HashSet<UriPermission>();
+ if (mWritePerms == null) {
+ mWritePerms = Sets.newArraySet();
}
- writeUriPermissions.add(perm);
+ mWritePerms.add(perm);
}
public void removeReadPermission(UriPermission perm) {
- readUriPermissions.remove(perm);
- if (readUriPermissions.size() == 0) {
- readUriPermissions = null;
+ mReadPerms.remove(perm);
+ if (mReadPerms.isEmpty()) {
+ mReadPerms = null;
}
}
public void removeWritePermission(UriPermission perm) {
- writeUriPermissions.remove(perm);
- if (writeUriPermissions.size() == 0) {
- writeUriPermissions = null;
+ mWritePerms.remove(perm);
+ if (mWritePerms.isEmpty()) {
+ mWritePerms = null;
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ if (mReadPerms != null) {
+ pw.print(prefix); pw.print("readUriPermissions="); pw.println(mReadPerms);
+ }
+ if (mWritePerms != null) {
+ pw.print(prefix); pw.print("writeUriPermissions="); pw.println(mWritePerms);
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2c8f1b4..91afec7 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -95,6 +95,14 @@
}
}
+ static boolean shouldBlank(int state) {
+ return state == Display.STATE_OFF;
+ }
+
+ static boolean shouldUnblank(int state) {
+ return state == Display.STATE_ON || state == Display.STATE_DOZING;
+ }
+
private final class LocalDisplayDevice extends DisplayDevice {
private final int mBuiltInDisplayId;
private final SurfaceControl.PhysicalDisplayInfo mPhys;
@@ -180,9 +188,9 @@
@Override
public void requestDisplayStateLocked(int state) {
if (mState != state) {
- if (state == Display.STATE_OFF && mState != Display.STATE_OFF) {
+ if (shouldBlank(state) && !shouldBlank(mState)) {
SurfaceControl.blankDisplay(getDisplayTokenLocked());
- } else if (state != Display.STATE_OFF && mState == Display.STATE_OFF) {
+ } else if (shouldUnblank(state) && !shouldUnblank(mState)) {
SurfaceControl.unblankDisplay(getDisplayTokenLocked());
}
mState = state;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a6e83a7..53db9ef 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,6 +22,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
import android.app.IStopUserCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -437,7 +438,7 @@
@Override
public void setUserRestrictions(Bundle restrictions, int userId) {
- checkManageUsersPermission("setUserRestrictions");
+ checkProfileOwnerOrManageUsersPermission("setUserRestrictions");
if (restrictions == null) return;
synchronized (mPackagesLock) {
@@ -463,16 +464,53 @@
* @param message used as message if SecurityException is thrown
* @throws SecurityException if the caller is not system or root
*/
- private static final void checkManageUsersPermission(String message) {
+ private final void checkManageUsersPermission(String message) {
final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID && uid != 0
- && ActivityManager.checkComponentPermission(
- android.Manifest.permission.MANAGE_USERS,
- uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+
+ if (missingManageUsersPermission(uid)) {
throw new SecurityException("You need MANAGE_USERS permission to: " + message);
}
}
+ /**
+ * Enforces that only the system UID, root's UID, apps that have the
+ * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
+ * permission, the profile owner, or the device owner can make certain calls to the
+ * UserManager.
+ *
+ * @param message used as message if SecurityException is thrown
+ * @throws SecurityException if the caller is not system, root, or device
+ * owner
+ */
+ private final void checkProfileOwnerOrManageUsersPermission(String message) {
+ final int uid = Binder.getCallingUid();
+ boolean isProfileOwner = false;
+ if (mContext != null && mContext.getPackageManager() != null) {
+ String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
+ DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (dpm != null) {
+ for (String pkg : pkgs) {
+ if (dpm.isDeviceOwnerApp(pkg) || dpm.isProfileOwnerApp(pkg)) {
+ isProfileOwner = true;
+ }
+ }
+ }
+ }
+
+ if (missingManageUsersPermission(uid) && !isProfileOwner) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission or device owner privileges to: " + message);
+ }
+ }
+
+ private boolean missingManageUsersPermission(int uid) {
+ return uid != Process.SYSTEM_UID && uid != 0
+ && ActivityManager.checkComponentPermission(
+ android.Manifest.permission.MANAGE_USERS,
+ uid, -1, true) != PackageManager.PERMISSION_GRANTED;
+ }
+
private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
try {
File dir = new File(mUsersDir, Integer.toString(info.id));
@@ -1175,7 +1213,8 @@
public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
if (UserHandle.getCallingUserId() != userId
|| !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
- checkManageUsersPermission("Only system can get restrictions for other users/apps");
+ checkProfileOwnerOrManageUsersPermission(
+ "Only system or device owner can get restrictions for other users/apps");
}
synchronized (mPackagesLock) {
// Read the restrictions from XML
@@ -1188,7 +1227,8 @@
int userId) {
if (UserHandle.getCallingUserId() != userId
|| !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
- checkManageUsersPermission("Only system can set restrictions for other users/apps");
+ checkProfileOwnerOrManageUsersPermission(
+ "Only system or device owner can set restrictions for other users/apps");
}
synchronized (mPackagesLock) {
// Write the restrictions to XML
@@ -1290,7 +1330,8 @@
@Override
public void removeRestrictions() {
- checkManageUsersPermission("Only system can remove restrictions");
+ checkProfileOwnerOrManageUsersPermission(
+ "Only system or device owner can remove restrictions");
final int userHandle = UserHandle.getCallingUserId();
removeRestrictionsForUser(userHandle, true);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 82cef7f..cfe24fe 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -168,6 +168,10 @@
// Poll interval in milliseconds for watching boot animation finished.
private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
+ //powerHint
+ private static final int POWER_HINT_LOW_POWER_MODE = 5;
+ private static boolean mLowPowerModeEnabled;
+
private final Context mContext;
private LightsManager mLightsManager;
private BatteryService mBatteryService;
@@ -530,7 +534,9 @@
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS_MODE),
false, mSettingsObserver, UserHandle.USER_ALL);
-
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -610,6 +616,14 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ boolean mIsEnabled = Settings.Global.getInt(resolver,
+ Settings.Global.LOW_POWER_MODE, 0) != 0;
+ if (mIsEnabled != mLowPowerModeEnabled) {
+ BinderService bs = new BinderService();
+ bs.powerHint(POWER_HINT_LOW_POWER_MODE, mIsEnabled ? 1 : 0);
+ mLowPowerModeEnabled = mIsEnabled;
+ }
+
mDirty |= DIRTY_SETTINGS;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index ebfe9bc..8862f5b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -355,227 +355,162 @@
}
@LayoutlibDelegate
- /*package*/ static boolean native_setConcat(long native_object, long a, long b) {
+ /*package*/ static void native_setConcat(long native_object, long a, long b) {
if (a == native_object) {
- return native_preConcat(native_object, b);
+ native_preConcat(native_object, b);
+ return;
} else if (b == native_object) {
- return native_postConcat(native_object, a);
+ native_postConcat(native_object, a);
+ return;
}
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
Matrix_Delegate a_mtx = sManager.getDelegate(a);
- if (a_mtx == null) {
- return false;
- }
-
Matrix_Delegate b_mtx = sManager.getDelegate(b);
- if (b_mtx == null) {
- return false;
+ if (d != null && a_mtx != null && b_mtx != null) {
+ multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
}
-
- multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
-
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preTranslate(long native_object, float dx, float dy) {
+ /*package*/ static void native_preTranslate(long native_object, float dx, float dy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.preTransform(getTranslate(dx, dy));
}
-
- d.preTransform(getTranslate(dx, dy));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preScale(long native_object, float sx, float sy,
+ /*package*/ static void native_preScale(long native_object, float sx, float sy,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.preTransform(getScale(sx, sy, px, py));
}
-
- d.preTransform(getScale(sx, sy, px, py));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preScale(long native_object, float sx, float sy) {
+ /*package*/ static void native_preScale(long native_object, float sx, float sy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.preTransform(getScale(sx, sy));
}
-
- d.preTransform(getScale(sx, sy));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preRotate(long native_object, float degrees,
+ /*package*/ static void native_preRotate(long native_object, float degrees,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.preTransform(getRotate(degrees, px, py));
}
-
- d.preTransform(getRotate(degrees, px, py));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preRotate(long native_object, float degrees) {
+ /*package*/ static void native_preRotate(long native_object, float degrees) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+
+ double rad = Math.toRadians(degrees);
+ float sin = (float) Math.sin(rad);
+ float cos = (float) Math.cos(rad);
+
+ d.preTransform(getRotate(sin, cos));
}
-
- double rad = Math.toRadians(degrees);
- float sin = (float)Math.sin(rad);
- float cos = (float)Math.cos(rad);
-
- d.preTransform(getRotate(sin, cos));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preSkew(long native_object, float kx, float ky,
+ /*package*/ static void native_preSkew(long native_object, float kx, float ky,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.preTransform(getSkew(kx, ky, px, py));
}
-
- d.preTransform(getSkew(kx, ky, px, py));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preSkew(long native_object, float kx, float ky) {
+ /*package*/ static void native_preSkew(long native_object, float kx, float ky) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.preTransform(getSkew(kx, ky));
}
-
- d.preTransform(getSkew(kx, ky));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_preConcat(long native_object, long other_matrix) {
+ /*package*/ static void native_preConcat(long native_object, long other_matrix) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
Matrix_Delegate other = sManager.getDelegate(other_matrix);
- if (other == null) {
- return false;
+ if (d != null && other != null) {
+ d.preTransform(other.mValues);
}
-
- d.preTransform(other.mValues);
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postTranslate(long native_object, float dx, float dy) {
+ /*package*/ static void native_postTranslate(long native_object, float dx, float dy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getTranslate(dx, dy));
}
-
- d.postTransform(getTranslate(dx, dy));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postScale(long native_object, float sx, float sy,
+ /*package*/ static void native_postScale(long native_object, float sx, float sy,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getScale(sx, sy, px, py));
}
-
- d.postTransform(getScale(sx, sy, px, py));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postScale(long native_object, float sx, float sy) {
+ /*package*/ static void native_postScale(long native_object, float sx, float sy) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getScale(sx, sy));
}
-
- d.postTransform(getScale(sx, sy));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postRotate(long native_object, float degrees,
+ /*package*/ static void native_postRotate(long native_object, float degrees,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getRotate(degrees, px, py));
}
-
- d.postTransform(getRotate(degrees, px, py));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postRotate(long native_object, float degrees) {
+ /*package*/ static void native_postRotate(long native_object, float degrees) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getRotate(degrees));
}
-
- d.postTransform(getRotate(degrees));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postSkew(long native_object, float kx, float ky,
+ /*package*/ static void native_postSkew(long native_object, float kx, float ky,
float px, float py) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getSkew(kx, ky, px, py));
}
-
- d.postTransform(getSkew(kx, ky, px, py));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postSkew(long native_object, float kx, float ky) {
+ /*package*/ static void native_postSkew(long native_object, float kx, float ky) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
+ if (d != null) {
+ d.postTransform(getSkew(kx, ky));
}
-
- d.postTransform(getSkew(kx, ky));
- return true;
}
@LayoutlibDelegate
- /*package*/ static boolean native_postConcat(long native_object, long other_matrix) {
+ /*package*/ static void native_postConcat(long native_object, long other_matrix) {
Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
Matrix_Delegate other = sManager.getDelegate(other_matrix);
- if (other == null) {
- return false;
+ if (d != null && other != null) {
+ d.postTransform(other.mValues);
}
-
- d.postTransform(other.mValues);
- return true;
}
@LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index cdbe200..af22f44 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -32,10 +32,6 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import java.io.File;
@@ -155,6 +151,9 @@
@Override
public View inflate(int resource, ViewGroup root) {
Context context = getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
if (context instanceof BridgeContext) {
BridgeContext bridgeContext = (BridgeContext)context;
@@ -217,43 +216,16 @@
}
private void setupViewInContext(View view, AttributeSet attrs) {
- if (getContext() instanceof BridgeContext) {
- BridgeContext bc = (BridgeContext) getContext();
- if (attrs instanceof BridgeXmlBlockParser) {
- BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
-
- // get the view key
- Object viewKey = parser.getViewCookie();
-
- if (viewKey == null) {
- int currentDepth = parser.getDepth();
-
- // test whether we are in an included file or in a adapter binding view.
- BridgeXmlBlockParser previousParser = bc.getPreviousParser();
- if (previousParser != null) {
- // looks like we inside an embedded layout.
- // only apply the cookie of the calling node (<include>) if we are at the
- // top level of the embedded layout. If there is a merge tag, then
- // skip it and look for the 2nd level
- int testDepth = mIsInMerge ? 2 : 1;
- if (currentDepth == testDepth) {
- viewKey = previousParser.getViewCookie();
- // if we are in a merge, wrap the cookie in a MergeCookie.
- if (viewKey != null && mIsInMerge) {
- viewKey = new MergeCookie(viewKey);
- }
- }
- } else if (mResourceReference != null && currentDepth == 1) {
- // else if there's a resource reference, this means we are in an adapter
- // binding case. Set the resource ref as the view cookie only for the top
- // level view.
- viewKey = mResourceReference;
- }
- }
-
- if (viewKey != null) {
- bc.addViewKey(view, viewKey);
- }
+ Context context = getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
+ if (context instanceof BridgeContext) {
+ BridgeContext bc = (BridgeContext) context;
+ // get the view key
+ Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
+ if (viewKey != null) {
+ bc.addViewKey(view, viewKey);
}
}
}
@@ -270,4 +242,44 @@
public LayoutInflater cloneInContext(Context newContext) {
return new BridgeInflater(this, newContext);
}
+
+ /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
+ ResourceReference resourceReference, boolean isInMerge) {
+
+ if (!(attrs instanceof BridgeXmlBlockParser)) {
+ return null;
+ }
+ BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
+
+ // get the view key
+ Object viewKey = parser.getViewCookie();
+
+ if (viewKey == null) {
+ int currentDepth = parser.getDepth();
+
+ // test whether we are in an included file or in a adapter binding view.
+ BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+ if (previousParser != null) {
+ // looks like we are inside an embedded layout.
+ // only apply the cookie of the calling node (<include>) if we are at the
+ // top level of the embedded layout. If there is a merge tag, then
+ // skip it and look for the 2nd level
+ int testDepth = isInMerge ? 2 : 1;
+ if (currentDepth == testDepth) {
+ viewKey = previousParser.getViewCookie();
+ // if we are in a merge, wrap the cookie in a MergeCookie.
+ if (viewKey != null && isInMerge) {
+ viewKey = new MergeCookie(viewKey);
+ }
+ }
+ } else if (resourceReference != null && currentDepth == 1) {
+ // else if there's a resource reference, this means we are in an adapter
+ // binding case. Set the resource ref as the view cookie only for the top
+ // level view.
+ viewKey = resourceReference;
+ }
+ }
+
+ return viewKey;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
new file mode 100644
index 0000000..22232f3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.AttributeSet;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuInflater}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of MenuInflater have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p/>
+ * The main purpose of the class is to get the view key from the menu xml parser and add it to
+ * the menu item. The view key is used by the IDE to match the individual view elements to the
+ * corresponding xml tag in the menu/layout file.
+ * <p/>
+ * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the
+ * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link
+ * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
+ */
+public class MenuInflater_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void emptyMethod(MenuInflater thisInflater, MenuItem menuItem,
+ AttributeSet attrs) {
+ if (menuItem instanceof BridgeMenuItemImpl) {
+ Context context = thisInflater.getContext();
+ if (context instanceof ContextThemeWrapper) {
+ context = ((ContextThemeWrapper) context).getBaseContext();
+ }
+ if (context instanceof BridgeContext) {
+ Object viewKey = BridgeInflater.getViewKeyFromParser(
+ attrs, ((BridgeContext) context), null, false);
+ ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey);
+ return;
+ }
+ }
+ // This means that Bridge did not take over the instantiation of some object properly.
+ // This is most likely a bug in the LayoutLib code.
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Action Bar Menu rendering may be incorrect.", null);
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void emptyMethod(MenuInflater thisInflater, SubMenu subMenu,
+ AttributeSet parser) {
+ emptyMethod(thisInflater, subMenu.getItem(), parser);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
new file mode 100644
index 0000000..4bef424
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+/**
+ * An extension of the {@link MenuItemImpl} to store the view cookie also.
+ */
+public class BridgeMenuItemImpl extends MenuItemImpl {
+
+ /**
+ * An object returned by the IDE that helps mapping each View to the corresponding XML tag in
+ * the layout. For Menus, we store this cookie here and attach it to the corresponding view
+ * at the time of rendering.
+ */
+ private Object viewCookie;
+
+ /**
+ * Instantiates this menu item.
+ */
+ BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
+ CharSequence title, int showAsAction) {
+ super(menu, group, id, categoryOrder, ordering, title, showAsAction);
+ }
+
+
+ public Object getViewCookie() {
+ return viewCookie;
+ }
+
+ public void setViewCookie(Object viewCookie) {
+ this.viewCookie = viewCookie;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
new file mode 100644
index 0000000..505fb81
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of {@code MenuBuilder} have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+public class MenuBuilder_Delegate {
+ /**
+ * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of
+ * {@link BridgeMenuItemImpl} so that view cookies may be stored.
+ */
+ @LayoutlibDelegate
+ /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id,
+ int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) {
+ return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title,
+ defaultShowAsAction);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index afcadef..9787432 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -28,7 +28,6 @@
import com.android.ide.common.rendering.api.IAnimationListener;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
-import com.android.ide.common.rendering.api.RenderParams;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ResourceReference;
@@ -39,6 +38,12 @@
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.internal.util.XmlUtils;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.IconMenuItemView;
+import com.android.internal.view.menu.ListMenuItemView;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuView;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
@@ -101,11 +106,10 @@
/**
* Class implementing the render session.
- *
+ * <p/>
* A session is a stateful representation of a layout file. It is initialized with data coming
* through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
* be done on the layout.
- *
*/
public class RenderSessionImpl extends RenderAction<SessionParams> {
@@ -172,7 +176,7 @@
@Override
public Result init(long timeout) {
Result result = super.init(timeout);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -196,6 +200,7 @@
// FIXME: find those out, and possibly add them to the render params
boolean hasNavigationBar = true;
+ //noinspection ConstantConditions
IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
metrics, Surface.ROTATION_0,
hasNavigationBar);
@@ -229,10 +234,9 @@
BridgeContext context = getContext();
boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
- ActionBarLayout actionBar = null;
// the view group that receives the window background.
- ViewGroup backgroundView = null;
+ ViewGroup backgroundView;
if (mWindowIsFloating || params.isForceNoDecor()) {
backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
@@ -266,7 +270,7 @@
NavigationBar navigationBar = createNavigationBar(context,
hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
topLayout.addView(navigationBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
@@ -322,7 +326,7 @@
StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(),
layoutDirection, params.isRtlSupported());
topLayout.addView(statusBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
@@ -339,20 +343,16 @@
// if the theme says no title/action bar, then the size will be 0
if (mActionBarSize > 0) {
- try {
- actionBar = createActionBar(context, params);
- backgroundLayout.addView(actionBar);
- actionBar.createMenuPopup();
- mContentRoot = actionBar.getContentRoot();
- } catch (XmlPullParserException e) {
-
- }
+ ActionBarLayout actionBar = createActionBar(context, params);
+ backgroundLayout.addView(actionBar);
+ actionBar.createMenuPopup();
+ mContentRoot = actionBar.getContentRoot();
} else if (mTitleBarSize > 0) {
try {
TitleBar titleBar = createTitleBar(context,
hardwareConfig.getDensity(), params.getAppLabel());
backgroundLayout.addView(titleBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
@@ -374,7 +374,7 @@
NavigationBar navigationBar = createNavigationBar(context,
hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
topLayout.addView(navigationBar);
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException ignored) {
}
}
@@ -399,7 +399,7 @@
postInflateProcess(view, params.getProjectCallback());
// get the background drawable
- if (mWindowBackground != null && backgroundView != null) {
+ if (mWindowBackground != null) {
Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
backgroundView.setBackground(d);
}
@@ -476,6 +476,7 @@
// first measure the full layout, with EXACTLY to get the size of the
// content as it is inside the decor/dialog
+ @SuppressWarnings("deprecation")
Pair<Integer, Integer> exactMeasure = measureView(
mViewRoot, mContentRoot.getChildAt(0),
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
@@ -483,6 +484,7 @@
// now measure the content only using UNSPECIFIED (where applicable, based on
// the rendering mode). This will give us the size the content needs.
+ @SuppressWarnings("deprecation")
Pair<Integer, Integer> result = measureView(
mContentRoot, mContentRoot.getChildAt(0),
mMeasuredScreenWidth, widthMeasureSpecMode,
@@ -558,7 +560,7 @@
mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
}
- if (freshRender && newImage == false) {
+ if (freshRender && !newImage) {
Graphics2D gc = mImage.createGraphics();
gc.setComposite(AlphaComposite.Src);
@@ -573,7 +575,8 @@
mViewRoot.draw(mCanvas);
}
- mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false);
+ mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ false);
// success!
return SUCCESS.createResult();
@@ -603,6 +606,7 @@
* @param heightMode the MeasureSpec mode to use for the height.
* @return the measured width/height if measuredView is non-null, null otherwise.
*/
+ @SuppressWarnings("deprecation") // For the use of Pair
private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
int width, int widthMode, int height, int heightMode) {
int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
@@ -633,7 +637,7 @@
BridgeContext context = getContext();
// find the animation file.
- ResourceValue animationResource = null;
+ ResourceValue animationResource;
int animationId = 0;
if (isFrameworkAnimation) {
animationResource = context.getRenderResources().getFrameworkResource(
@@ -723,7 +727,7 @@
// add it to the parentView in the correct location
Result result = addView(parentView, child, index);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -793,13 +797,13 @@
public void run() {
Result result = moveView(previousParent, newParentView, childView, index,
params);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
listener.done(result);
}
// ready to do the work, acquire the scene.
result = acquire(250);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
listener.done(result);
return;
}
@@ -857,7 +861,7 @@
}
Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -991,7 +995,7 @@
}
Result result = removeView(parent, childView);
- if (result.isSuccess() == false) {
+ if (!result.isSuccess()) {
return result;
}
@@ -1019,7 +1023,7 @@
private void findBackground(RenderResources resources) {
- if (getParams().isBgColorOverridden() == false) {
+ if (!getParams().isBgColorOverridden()) {
mWindowBackground = resources.findItemInTheme("windowBackground",
true /*isFrameworkAttr*/);
if (mWindowBackground != null) {
@@ -1036,7 +1040,7 @@
boolean windowFullscreen = getBooleanThemeValue(resources,
"windowFullscreen", false /*defaultValue*/);
- if (windowFullscreen == false && mWindowIsFloating == false) {
+ if (!windowFullscreen && !mWindowIsFloating) {
// default value
mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
@@ -1090,7 +1094,7 @@
boolean windowNoTitle = getBooleanThemeValue(resources,
"windowNoTitle", false /*defaultValue*/);
- if (windowNoTitle == false) {
+ if (!windowNoTitle) {
// default size of the window title bar
mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
@@ -1117,7 +1121,7 @@
}
private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) {
- if (hasSoftwareButtons() && mWindowIsFloating == false) {
+ if (hasSoftwareButtons() && !mWindowIsFloating) {
// default value
mNavigationBarSize = 48; // ??
@@ -1131,15 +1135,12 @@
int shortSize = hardwareConfig.getScreenHeight();
// compute in dp
- int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue();
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
+ hardwareConfig.getDensity().getDpiValue();
- if (shortSizeDp < 600) {
- // 0-599dp: "phone" UI with bar on the side
- barOnBottom = false;
- } else {
- // 600+dp: "tablet" UI with bar on the bottom
- barOnBottom = true;
- }
+ // 0-599dp: "phone" UI with bar on the side
+ // 600+dp: "tablet" UI with bar on the bottom
+ barOnBottom = shortSizeDp >= 600;
}
if (barOnBottom) {
@@ -1190,13 +1191,15 @@
}
/**
- * Post process on a view hierachy that was just inflated.
- * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
+ * Post process on a view hierarchy that was just inflated.
+ * <p/>
+ * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
* {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
* based on the content of the {@link FrameLayout}.
* @param view the root view to process.
* @param projectCallback callback to the project.
*/
+ @SuppressWarnings("deprecation") // For the use of Pair
private void postInflateProcess(View view, IProjectCallback projectCallback)
throws PostInflateException {
if (view instanceof TabHost) {
@@ -1299,7 +1302,7 @@
"TabHost requires a TabWidget with id \"android:id/tabs\".\n");
}
- if ((v instanceof TabWidget) == false) {
+ if (!(v instanceof TabWidget)) {
throw new PostInflateException(String.format(
"TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
"View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
@@ -1308,12 +1311,14 @@
v = tabHost.findViewById(android.R.id.tabcontent);
if (v == null) {
- // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
+ // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
+ //noinspection SpellCheckingInspection
throw new PostInflateException(
"TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
}
- if ((v instanceof FrameLayout) == false) {
+ if (!(v instanceof FrameLayout)) {
+ //noinspection SpellCheckingInspection
throw new PostInflateException(String.format(
"TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
"View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
@@ -1321,7 +1326,7 @@
FrameLayout content = (FrameLayout)v;
- // now process the content of the framelayout and dynamically create tabs for it.
+ // now process the content of the frameLayout and dynamically create tabs for it.
final int count = content.getChildCount();
// this must be called before addTab() so that the TabHost searches its TabWidget
@@ -1339,13 +1344,13 @@
}
});
tabHost.addTab(spec);
- return;
} else {
- // for each child of the framelayout, add a new TabSpec
+ // for each child of the frameLayout, add a new TabSpec
for (int i = 0 ; i < count ; i++) {
View child = content.getChildAt(i);
String tabSpec = String.format("tab_spec%d", i+1);
int id = child.getId();
+ @SuppressWarnings("deprecation")
Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
String name;
if (resource != null) {
@@ -1468,13 +1473,13 @@
ViewInfo result;
if (isContentFrame) {
result = new ViewInfo(view.getClass().getName(),
- getContext().getViewKey(view),
+ getViewKey(view),
view.getLeft(), view.getTop() + offset, view.getRight(),
view.getBottom() + offset, view, view.getLayoutParams());
} else {
result = new SystemViewInfo(view.getClass().getName(),
- getContext().getViewKey(view),
+ getViewKey(view),
view.getLeft(), view.getTop(), view.getRight(),
view.getBottom(), view, view.getLayoutParams());
}
@@ -1495,6 +1500,32 @@
return result;
}
+ /**
+ * The cookie for menu items are stored in menu item and not in the map from View stored in
+ * BridgeContext.
+ */
+ private Object getViewKey(View view) {
+ BridgeContext context = getContext();
+ if (!(view instanceof MenuView.ItemView)) {
+ return context.getViewKey(view);
+ }
+ MenuItemImpl menuItem;
+ if (view instanceof ActionMenuItemView) {
+ menuItem = ((ActionMenuItemView) view).getItemData();
+ } else if (view instanceof ListMenuItemView) {
+ menuItem = ((ListMenuItemView) view).getItemData();
+ } else if (view instanceof IconMenuItemView) {
+ menuItem = ((IconMenuItemView) view).getItemData();
+ } else {
+ menuItem = null;
+ }
+ if (menuItem instanceof BridgeMenuItemImpl) {
+ return ((BridgeMenuItemImpl) menuItem).getViewCookie();
+ }
+
+ return null;
+ }
+
private void invalidateRenderingSize() {
mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
}
@@ -1545,8 +1576,7 @@
/**
* Creates the action bar. Also queries the project callback for missing information.
*/
- private ActionBarLayout createActionBar(BridgeContext context, SessionParams params)
- throws XmlPullParserException {
+ private ActionBarLayout createActionBar(BridgeContext context, SessionParams params) {
ActionBarLayout actionBar = new ActionBarLayout(context, params);
actionBar.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
index b16b4aa..19d249b 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -253,4 +253,14 @@
return true;
}
+
+ @LayoutlibDelegate
+ /*package*/ static void setDefaultLocale(String locale) {
+ ICU.setDefaultLocale(locale);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getDefaultLocale() {
+ return ICU.getDefaultLocale();
+ }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e0c05de..986b911 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -143,6 +143,8 @@
"android.view.ViewRootImpl#isInTouchMode",
"android.view.WindowManagerGlobal#getWindowManagerService",
"android.view.inputmethod.InputMethodManager#getInstance",
+ "android.view.MenuInflater#emptyMethod",
+ "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
"com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
};