diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
commit | 4d01eeaffaa720e4458a118baa137a11614f00f7 (patch) | |
tree | 66751893566986236788e3c796a7cc5e90d05f52 /android/content | |
parent | a192cc2a132cb0ee8588e2df755563ec7008c179 (diff) | |
download | android-28-4d01eeaffaa720e4458a118baa137a11614f00f7.tar.gz |
Import Android SDK Platform P [4697573]
/google/data/ro/projects/android/fetch_artifact \
--bid 4697573 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4697573.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
Diffstat (limited to 'android/content')
48 files changed, 3818 insertions, 1497 deletions
diff --git a/android/content/AbstractThreadedSyncAdapter.java b/android/content/AbstractThreadedSyncAdapter.java index 2629929e..b528e397 100644 --- a/android/content/AbstractThreadedSyncAdapter.java +++ b/android/content/AbstractThreadedSyncAdapter.java @@ -16,11 +16,17 @@ package android.content; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + import android.accounts.Account; +import android.annotation.MainThread; +import android.annotation.NonNull; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.Process; +import android.os.RemoteException; import android.os.Trace; import android.util.Log; @@ -166,6 +172,13 @@ public abstract class AbstractThreadedSyncAdapter { private class ISyncAdapterImpl extends ISyncAdapter.Stub { @Override + public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) { + Handler.getMain().sendMessage(obtainMessage( + AbstractThreadedSyncAdapter::handleOnUnsyncableAccount, + AbstractThreadedSyncAdapter.this, cb)); + } + + @Override public void startSync(ISyncContext syncContext, String authority, Account account, Bundle extras) { if (ENABLE_LOG) { @@ -374,6 +387,54 @@ public abstract class AbstractThreadedSyncAdapter { } /** + * Handle a call of onUnsyncableAccount. + * + * @param cb The callback to report the return value to + */ + private void handleOnUnsyncableAccount(@NonNull ISyncAdapterUnsyncableAccountCallback cb) { + boolean doSync; + try { + doSync = onUnsyncableAccount(); + } catch (RuntimeException e) { + Log.e(TAG, "Exception while calling onUnsyncableAccount, assuming 'true'", e); + doSync = true; + } + + try { + cb.onUnsyncableAccountDone(doSync); + } catch (RemoteException e) { + Log.e(TAG, "Could not report result of onUnsyncableAccount", e); + } + } + + /** + * Allows to defer syncing until all accounts are properly set up. + * + * <p>Called when a account / authority pair + * <ul> + * <li>that can be handled by this adapter</li> + * <li>{@link ContentResolver#requestSync(SyncRequest) is synced}</li> + * <li>and the account/provider {@link ContentResolver#getIsSyncable(Account, String) has + * unknown state (<0)}.</li> + * </ul> + * + * <p>This might be called on a different service connection as {@link #onPerformSync}. + * + * <p>The system expects this method to immediately return. If the call stalls the system + * behaves as if this method returned {@code true}. If it is required to perform a longer task + * (such as interacting with the user), return {@code false} and proceed in a difference + * context, such as an {@link android.app.Activity}, or foreground service. The sync can then be + * rescheduled once the account becomes syncable. + * + * @return If {@code false} syncing is deferred. Returns {@code true} by default, i.e. by + * default syncing starts immediately. + */ + @MainThread + public boolean onUnsyncableAccount() { + return true; + } + + /** * Perform a sync for this account. SyncAdapter-specific parameters may * be specified in extras, which is guaranteed to not be null. Invocations * of this method are guaranteed to be serialized. diff --git a/android/content/ClipboardManager.java b/android/content/ClipboardManager.java index 718e465b..73b6eb27 100644 --- a/android/content/ClipboardManager.java +++ b/android/content/ClipboardManager.java @@ -16,13 +16,16 @@ package android.content; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; import android.os.Handler; -import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; /** @@ -45,6 +48,7 @@ import java.util.ArrayList; @SystemService(Context.CLIPBOARD_SERVICE) public class ClipboardManager extends android.text.ClipboardManager { private final Context mContext; + private final Handler mHandler; private final IClipboard mService; private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners @@ -52,20 +56,11 @@ public class ClipboardManager extends android.text.ClipboardManager { private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener = new IOnPrimaryClipChangedListener.Stub() { - public void dispatchPrimaryClipChanged() { - mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED); - } - }; - - static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1; - - private final Handler mHandler = new Handler() { @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REPORT_PRIMARY_CLIP_CHANGED: - reportPrimaryClipChanged(); - } + public void dispatchPrimaryClipChanged() { + mHandler.post(() -> { + reportPrimaryClipChanged(); + }); } }; @@ -89,6 +84,7 @@ public class ClipboardManager extends android.text.ClipboardManager { /** {@hide} */ public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException { mContext = context; + mHandler = handler; mService = IClipboard.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE)); } @@ -98,12 +94,13 @@ public class ClipboardManager extends android.text.ClipboardManager { * is involved in normal cut and paste operations. * * @param clip The clipped data item to set. + * @see #getPrimaryClip() + * @see #clearPrimaryClip() */ - public void setPrimaryClip(ClipData clip) { + public void setPrimaryClip(@NonNull ClipData clip) { try { - if (clip != null) { - clip.prepareToLeaveProcess(true); - } + Preconditions.checkNotNull(clip); + clip.prepareToLeaveProcess(true); mService.setPrimaryClip(clip, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -111,9 +108,24 @@ public class ClipboardManager extends android.text.ClipboardManager { } /** + * Clears any current primary clip on the clipboard. + * + * @see #setPrimaryClip(ClipData) + */ + public void clearPrimaryClip() { + try { + mService.clearPrimaryClip(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the current primary clip on the clipboard. + * + * @see #setPrimaryClip(ClipData) */ - public ClipData getPrimaryClip() { + public @Nullable ClipData getPrimaryClip() { try { return mService.getPrimaryClip(mContext.getOpPackageName()); } catch (RemoteException e) { @@ -124,8 +136,10 @@ public class ClipboardManager extends android.text.ClipboardManager { /** * Returns a description of the current primary clip on the clipboard * but not a copy of its data. + * + * @see #setPrimaryClip(ClipData) */ - public ClipDescription getPrimaryClipDescription() { + public @Nullable ClipDescription getPrimaryClipDescription() { try { return mService.getPrimaryClipDescription(mContext.getOpPackageName()); } catch (RemoteException e) { diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java index 8d2e141a..9f3df377 100644 --- a/android/content/ContentResolver.java +++ b/android/content/ContentResolver.java @@ -166,6 +166,15 @@ public abstract class ContentResolver { public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered"; /** + * {@hide} Integer extra containing a SyncExemption flag. + * + * Only the system and the shell user can set it. + * + * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle. + */ + public static final String SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG = "v_exemption"; + + /** * Set by the SyncManager to request that the SyncAdapter initialize itself for * the given account/authority pair. One required initialization step is to * ensure that {@link #setIsSyncable(android.accounts.Account, String, int)} has been @@ -505,6 +514,38 @@ public abstract class ContentResolver { */ public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1; + /** + * No exception, throttled by app standby normally. + * @hide + */ + public static final int SYNC_EXEMPTION_NONE = 0; + + /** + * When executing a sync with this exemption, we'll put the target app in the ACTIVE bucket + * for 10 minutes. This will allow the sync adapter to schedule/run further syncs and jobs. + * + * Note this will still *not* let RARE apps to run syncs, because they still won't get network + * connection. + * @hide + */ + public static final int SYNC_EXEMPTION_ACTIVE = 1; + + /** + * In addition to {@link #SYNC_EXEMPTION_ACTIVE}, we put the sync adapter app in the + * temp whitelist for 10 minutes, so that even RARE apps can run syncs right away. + * @hide + */ + public static final int SYNC_EXEMPTION_ACTIVE_WITH_TEMP = 2; + + /** @hide */ + @IntDef(flag = false, prefix = { "SYNC_EXEMPTION_" }, value = { + SYNC_EXEMPTION_NONE, + SYNC_EXEMPTION_ACTIVE, + SYNC_EXEMPTION_ACTIVE_WITH_TEMP, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SyncExemption {} + // Always log queries which take 500ms+; shorter queries are // sampled accordingly. private static final boolean ENABLE_CONTENT_SAMPLE = false; @@ -2082,7 +2123,23 @@ public abstract class ContentResolver { Preconditions.checkNotNull(uri, "uri"); try { ActivityManager.getService().takePersistableUriPermission( - ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri)); + ContentProvider.getUriWithoutUserId(uri), modeFlags, /* toPackage= */ null, + resolveUserId(uri)); + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public void takePersistableUriPermission(@NonNull String toPackage, @NonNull Uri uri, + @Intent.AccessUriMode int modeFlags) { + Preconditions.checkNotNull(toPackage, "toPackage"); + Preconditions.checkNotNull(uri, "uri"); + try { + ActivityManager.getService().takePersistableUriPermission( + ContentProvider.getUriWithoutUserId(uri), modeFlags, toPackage, + resolveUserId(uri)); } catch (RemoteException e) { } } @@ -2100,7 +2157,8 @@ public abstract class ContentResolver { Preconditions.checkNotNull(uri, "uri"); try { ActivityManager.getService().releasePersistableUriPermission( - ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri)); + ContentProvider.getUriWithoutUserId(uri), modeFlags, /* toPackage= */ null, + resolveUserId(uri)); } catch (RemoteException e) { } } @@ -2427,21 +2485,16 @@ public abstract class ContentResolver { * @param account the account to specify in the sync * @param authority the provider to specify in the sync request * @param extras extra parameters to go along with the sync request - * @param pollFrequency how frequently the sync should be performed, in seconds. A minimum value - * of 1 hour is enforced. + * @param pollFrequency how frequently the sync should be performed, in seconds. + * On Android API level 24 and above, a minmam interval of 15 minutes is enforced. + * On previous versions, the minimum interval is 1 hour. * @throws IllegalArgumentException if an illegal extra was set or if any of the parameters * are null. */ public static void addPeriodicSync(Account account, String authority, Bundle extras, long pollFrequency) { validateSyncExtrasBundle(extras); - if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false) - || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false) - || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false) - || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false) - || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false) - || extras.getBoolean(SYNC_EXTRAS_FORCE, false) - || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) { + if (invalidPeriodicExtras(extras)) { throw new IllegalArgumentException("illegal extras were set"); } try { @@ -2999,6 +3052,11 @@ public abstract class ContentResolver { } /** @hide */ + public int getUserId() { + return mContext.getUserId(); + } + + /** @hide */ public Drawable getTypeDrawable(String mimeType) { return MimeIconUtils.loadMimeIcon(mContext, mimeType); } diff --git a/android/content/Context.java b/android/content/Context.java index 1b050330..920056a8 100644 --- a/android/content/Context.java +++ b/android/content/Context.java @@ -32,6 +32,7 @@ import android.annotation.StyleableRes; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; @@ -48,6 +49,7 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -558,6 +560,7 @@ public abstract class Context { * * @param resId Resource id for the CharSequence text */ + @NonNull public final CharSequence getText(@StringRes int resId) { return getResources().getText(resId); } @@ -614,8 +617,7 @@ public abstract class Context { * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. - * @return An object that can be used to draw this resource, or - * {@code null} if the resource could not be resolved. + * @return An object that can be used to draw this resource. * @throws android.content.res.Resources.NotFoundException if the given ID * does not exist. */ @@ -631,12 +633,11 @@ public abstract class Context { * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. - * @return A color state list, or {@code null} if the resource could not be - * resolved. + * @return A color state list. * @throws android.content.res.Resources.NotFoundException if the given ID * does not exist. */ - @Nullable + @NonNull public final ColorStateList getColorStateList(@ColorRes int id) { return getResources().getColorStateList(id, getTheme()); } @@ -1834,13 +1835,17 @@ public abstract class Context { * See {@link android.content.Context#startActivity(Intent, Bundle)} * Context.startActivity(Intent, Bundle)} for more details. * + * @return The corresponding flag {@link ActivityManager#START_CANCELED}, + * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was + * successful. + * * @throws ActivityNotFoundException * * @see #startActivities(Intent[]) * @see PackageManager#resolveActivity */ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -3365,7 +3370,11 @@ public abstract class Context { * Use with {@link #getSystemService(String)} to retrieve a {@link * android.app.SearchManager} for handling searches. * - * @see #getSystemService(String) + * <p> + * {@link Configuration#UI_MODE_TYPE_WATCH} does not support + * {@link android.app.SearchManager}. + * + * @see #getSystemService * @see android.app.SearchManager */ public static final String SEARCH_SERVICE = "search"; @@ -3533,6 +3542,7 @@ public abstract class Context { * @hide */ @SystemApi + @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager"; /** @@ -3665,10 +3675,8 @@ public abstract class Context { * * @see #getSystemService(String) * @see android.telephony.euicc.EuiccManager - * TODO(b/35851809): Unhide this API. - * @hide */ - public static final String EUICC_SERVICE = "euicc_service"; + public static final String EUICC_SERVICE = "euicc"; /** * Use with {@link #getSystemService(String)} to retrieve a @@ -3676,10 +3684,10 @@ public abstract class Context { * * @see #getSystemService(String) * @see android.telephony.euicc.EuiccCardManager - * TODO(b/35851809): Make this a SystemApi. * @hide */ - public static final String EUICC_CARD_SERVICE = "euicc_card_service"; + @SystemApi + public static final String EUICC_CARD_SERVICE = "euicc_card"; /** * Use with {@link #getSystemService(String)} to retrieve a @@ -4165,6 +4173,16 @@ public abstract class Context { public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.se.omapi.ISecureElementService} + * for accessing the SecureElementService. + * + * @hide + */ + @SystemApi + public static final String SECURE_ELEMENT_SERVICE = "secure_element"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * @@ -4663,9 +4681,15 @@ public abstract class Context { * * @hide */ - public abstract Context createPackageContextAsUser( + @SystemApi + public Context createPackageContextAsUser( String packageName, @CreatePackageOptions int flags, UserHandle user) - throws PackageManager.NameNotFoundException; + throws PackageManager.NameNotFoundException { + if (Build.IS_ENG) { + throw new IllegalStateException("createPackageContextAsUser not overridden!"); + } + return this; + } /** * Creates a context given an {@link android.content.pm.ApplicationInfo}. @@ -4690,13 +4714,22 @@ public abstract class Context { throws PackageManager.NameNotFoundException; /** - * Get the userId associated with this context - * @return user id - * + * Get the user associated with this context + * @hide + */ + @TestApi + public UserHandle getUser() { + return android.os.Process.myUserHandle(); + } + + /** + * Get the user associated with this context * @hide */ @TestApi - public abstract @UserIdInt int getUserId(); + public @UserIdInt int getUserId() { + return android.os.UserHandle.myUserId(); + } /** * Return a new Context object for the current Context but whose resources @@ -4890,7 +4923,22 @@ public abstract class Context { /** * @hide */ - public void setAutofillClient(AutofillClient client) { + public void setAutofillClient(@SuppressWarnings("unused") AutofillClient client) { + } + + /** + * @hide + */ + public boolean isAutofillCompatibilityEnabled() { + return false; + } + + /** + * @hide + */ + @TestApi + public void setAutofillCompatibilityEnabled( + @SuppressWarnings("unused") boolean autofillCompatEnabled) { } /** diff --git a/android/content/ContextWrapper.java b/android/content/ContextWrapper.java index 67de4fe6..1867a6d8 100644 --- a/android/content/ContextWrapper.java +++ b/android/content/ContextWrapper.java @@ -17,6 +17,7 @@ package android.content; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.content.pm.ApplicationInfo; @@ -418,8 +419,8 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { - mBase.startActivitiesAsUser(intents, options, userHandle); + public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + return mBase.startActivitiesAsUser(intents, options, userHandle); } @Override @@ -994,4 +995,23 @@ public class ContextWrapper extends Context { public void setAutofillClient(AutofillClient client) { mBase.setAutofillClient(client); } + + /** + * @hide + */ + @Override + public boolean isAutofillCompatibilityEnabled() { + return mBase != null && mBase.isAutofillCompatibilityEnabled(); + } + + /** + * @hide + */ + @TestApi + @Override + public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) { + if (mBase != null) { + mBase.setAutofillCompatibilityEnabled(autofillCompatEnabled); + } + } } diff --git a/android/content/Intent.java b/android/content/Intent.java index acbdf142..000912cd 100644 --- a/android/content/Intent.java +++ b/android/content/Intent.java @@ -50,6 +50,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.OpenableColumns; +import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -265,8 +266,8 @@ import java.util.Set; * </ul> * * <p>For example, consider the Note Pad sample application that - * allows user to browse through a list of notes data and view details about - * individual items. Text in italics indicate places were you would replace a + * allows a user to browse through a list of notes data and view details about + * individual items. Text in italics indicates places where you would replace a * name with one specific to your own package.</p> * * <pre> <manifest xmlns:android="http://schemas.android.com/apk/res/android" @@ -1553,16 +1554,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE"; /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}. - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE - = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; - /** * Activity Action: Launch instant application installer. * <p class="note"> * This is a protected intent that can only be sent by the system. @@ -1576,16 +1567,6 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE"; /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}. - */ - @SystemApi - @SdkConstant(SdkConstantType.SERVICE_ACTION) - public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE - = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; - /** * Service Action: Resolve instant application. * <p> * The system will have a persistent connection to this service. @@ -1600,16 +1581,6 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}. - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS - = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; - /** * Activity Action: Launch instant app settings. * * <p class="note"> @@ -1843,6 +1814,17 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME"; /** + * Intent extra: A {@link Bundle} of extras for a package being suspended. Will be sent with + * {@link #ACTION_MY_PACKAGE_SUSPENDED}. + * + * @see #ACTION_MY_PACKAGE_SUSPENDED + * @see #ACTION_MY_PACKAGE_UNSUSPENDED + * @see PackageManager#isPackageSuspended() + * @see PackageManager#getSuspendedPackageAppExtras() + */ + public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS"; + + /** * Intent extra: An app split name. * <p> * Type: String @@ -1870,6 +1852,17 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; /** + * Intent extra: A {@link Bundle} of extras supplied for the launcher when any packages on + * device are suspended. Will be sent with {@link #ACTION_PACKAGES_SUSPENDED}. + * + * @see PackageManager#isPackageSuspended() + * @see #ACTION_PACKAGES_SUSPENDED + * + * @hide + */ + public static final String EXTRA_LAUNCHER_EXTRAS = "android.intent.extra.LAUNCHER_EXTRAS"; + + /** * Activity action: Launch UI to manage which apps have a given permission. * <p> * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission access @@ -2266,6 +2259,43 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED"; + + /** + * Broadcast Action: Sent to a package that has been suspended by the system. This is sent + * whenever a package is put into a suspended state or any of its app extras change while in the + * suspended state. + * <p> Optionally includes the following extras: + * <ul> + * <li> {@link #EXTRA_SUSPENDED_PACKAGE_EXTRAS} which is a {@link Bundle} which will contain + * useful information for the app being suspended. + * </ul> + * <p class="note">This is a protected intent that can only be sent + * by the system. <em>This will be delivered to {@link BroadcastReceiver} components declared in + * the manifest.</em> + * + * @see #ACTION_MY_PACKAGE_UNSUSPENDED + * @see #EXTRA_SUSPENDED_PACKAGE_EXTRAS + * @see PackageManager#isPackageSuspended() + * @see PackageManager#getSuspendedPackageAppExtras() + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED"; + + /** + * Broadcast Action: Sent to a package that has been unsuspended. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. <em>This will be delivered to {@link BroadcastReceiver} components declared in + * the manifest.</em> + * + * @see #ACTION_MY_PACKAGE_SUSPENDED + * @see #EXTRA_SUSPENDED_PACKAGE_EXTRAS + * @see PackageManager#isPackageSuspended() + * @see PackageManager#getSuspendedPackageAppExtras() + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED"; + /** * Broadcast Action: A user ID has been removed from the system. The user * ID number is stored in the extra data under {@link #EXTRA_UID}. @@ -2428,6 +2458,26 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; + + /** + * Broadcast Action: The current device {@link android.content.res.Configuration} has changed + * such that the device may be eligible for the installation of additional configuration splits. + * Configuration properties that can trigger this broadcast include locale and display density. + * + * <p class="note"> + * Unlike {@link #ACTION_CONFIGURATION_CHANGED}, you <em>can</em> receive this through + * components declared in manifests. However, the receiver <em>must</em> hold the + * {@link android.Manifest.permission#INSTALL_PACKAGES} permission. + * + * <p class="note"> + * This is a protected intent that can only be sent by the system. + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = + "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; /** * Broadcast Action: The current device's locale has changed. * @@ -2457,6 +2507,23 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED"; + + + /** + * Broadcast Action: Sent when the current battery level changes. + * + * It has {@link android.os.BatteryManager#EXTRA_EVENTS} that carries a list of {@link Bundle} + * instances representing individual battery level changes with associated + * extras from {@link #ACTION_BATTERY_CHANGED}. + * + * <p class="note"> + * This broadcast requires {@link android.Manifest.permission#BATTERY_STATS} permission. + * + * @hide + */ + @SystemApi + public static final String ACTION_BATTERY_LEVEL_CHANGED = + "android.intent.action.BATTERY_LEVEL_CHANGED"; /** * Broadcast Action: Indicates low battery condition on the device. * This broadcast corresponds to the "Low battery warning" system dialog. @@ -2507,6 +2574,9 @@ public class Intent implements Parcelable, Cloneable { * off, not sleeping). Once the broadcast is complete, the final shutdown * will proceed and all unsaved data lost. Apps will not normally need * to handle this, since the foreground activity will be paused as well. + * <p>As of {@link Build.VERSION_CODES#P} this broadcast is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. * * <p class="note">This is a protected intent that can only be sent * by the system. @@ -4443,45 +4513,109 @@ public class Intent implements Parcelable, Cloneable { /** * A {@link IntentSender} to start after ephemeral installation success. + * @deprecated Use {@link #EXTRA_INSTANT_APP_SUCCESS). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_SUCCESS = "android.intent.extra.EPHEMERAL_SUCCESS"; /** + * A {@link IntentSender} to start after instant app installation success. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_SUCCESS = + "android.intent.extra.INSTANT_APP_SUCCESS"; + + /** * A {@link IntentSender} to start after ephemeral installation failure. + * @deprecated Use {@link #EXTRA_INSTANT_APP_FAILURE). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE"; /** + * A {@link IntentSender} to start after instant app installation failure. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_FAILURE = + "android.intent.extra.INSTANT_APP_FAILURE"; + + /** * The host name that triggered an ephemeral resolution. + * @deprecated Use {@link #EXTRA_INSTANT_APP_HOSTNAME). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_HOSTNAME = "android.intent.extra.EPHEMERAL_HOSTNAME"; /** + * The host name that triggered an instant app resolution. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_HOSTNAME = + "android.intent.extra.INSTANT_APP_HOSTNAME"; + + /** * An opaque token to track ephemeral resolution. + * @deprecated Use {@link #EXTRA_INSTANT_APP_TOKEN). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN"; /** + * An opaque token to track instant app resolution. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_TOKEN = + "android.intent.extra.INSTANT_APP_TOKEN"; + + /** * The action that triggered an instant application resolution. * @hide */ + @SystemApi public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; /** - * A {@link Bundle} of metadata that describes the instanta application that needs to be + * An array of {@link Bundle}s containing details about resolved instant apps.. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_BUNDLES = + "android.intent.extra.INSTANT_APP_BUNDLES"; + + /** + * A {@link Bundle} of metadata that describes the instant application that needs to be * installed. This data is populated from the response to * {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered * instant application resolver. * @hide */ + @SystemApi public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; /** + * A boolean value indicating that the instant app resolver was unable to state with certainty + * that it did or did not have an app for the sanitized {@link Intent} defined at + * {@link #EXTRA_INTENT}. + * @hide + */ + @SystemApi + public static final String EXTRA_UNKNOWN_INSTANT_APP = + "android.intent.extra.UNKNOWN_INSTANT_APP"; + + /** * The version code of the app to install components from. * @deprecated Use {@link #EXTRA_LONG_VERSION_CODE). * @hide @@ -4493,12 +4627,14 @@ public class Intent implements Parcelable, Cloneable { * The version code of the app to install components from. * @hide */ + @SystemApi public static final String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE"; /** - * The app that triggered the ephemeral installation. + * The app that triggered the instant app installation. * @hide */ + @SystemApi public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE"; @@ -4507,6 +4643,7 @@ public class Intent implements Parcelable, Cloneable { * installer may use. * @hide */ + @SystemApi public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; @@ -5029,6 +5166,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_GRANT_PREFIX_URI_PERMISSION, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, + FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5072,6 +5210,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, + FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5475,6 +5614,14 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000; + + /** + * If set, resolution of this intent may take place via an instant app not + * yet on the device if there does not yet exist an app on device to + * resolve it. + */ + public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800; + /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. @@ -7028,7 +7175,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if none was found. * * @deprecated @@ -7046,7 +7193,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, boolean) @@ -7063,7 +7210,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, byte) @@ -7080,7 +7227,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, short) @@ -7097,7 +7244,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, char) @@ -7114,7 +7261,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, int) @@ -7131,7 +7278,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, long) @@ -7148,7 +7295,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra(), + * @return the value of an item previously added with putExtra(), * or the default value if no such item is present * * @see #putExtra(String, float) @@ -7165,7 +7312,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, double) @@ -7180,7 +7327,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no String value was found. * * @see #putExtra(String, String) @@ -7194,7 +7341,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no CharSequence value was found. * * @see #putExtra(String, CharSequence) @@ -7208,7 +7355,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Parcelable value was found. * * @see #putExtra(String, Parcelable) @@ -7222,7 +7369,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Parcelable[] value was found. * * @see #putExtra(String, Parcelable[]) @@ -7236,8 +7383,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<Parcelable> value was found. + * @return the value of an item previously added with + * putParcelableArrayListExtra(), or null if no + * ArrayList<Parcelable> value was found. * * @see #putParcelableArrayListExtra(String, ArrayList) */ @@ -7250,7 +7398,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Serializable value was found. * * @see #putExtra(String, Serializable) @@ -7264,8 +7412,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<Integer> value was found. + * @return the value of an item previously added with + * putIntegerArrayListExtra(), or null if no + * ArrayList<Integer> value was found. * * @see #putIntegerArrayListExtra(String, ArrayList) */ @@ -7278,8 +7427,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<String> value was found. + * @return the value of an item previously added with + * putStringArrayListExtra(), or null if no + * ArrayList<String> value was found. * * @see #putStringArrayListExtra(String, ArrayList) */ @@ -7292,8 +7442,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<CharSequence> value was found. + * @return the value of an item previously added with + * putCharSequenceArrayListExtra, or null if no + * ArrayList<CharSequence> value was found. * * @see #putCharSequenceArrayListExtra(String, ArrayList) */ @@ -7306,7 +7457,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no boolean array value was found. * * @see #putExtra(String, boolean[]) @@ -7320,7 +7471,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no byte array value was found. * * @see #putExtra(String, byte[]) @@ -7334,7 +7485,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no short array value was found. * * @see #putExtra(String, short[]) @@ -7348,7 +7499,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no char array value was found. * * @see #putExtra(String, char[]) @@ -7362,7 +7513,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no int array value was found. * * @see #putExtra(String, int[]) @@ -7376,7 +7527,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no long array value was found. * * @see #putExtra(String, long[]) @@ -7390,7 +7541,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no float array value was found. * * @see #putExtra(String, float[]) @@ -7404,7 +7555,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no double array value was found. * * @see #putExtra(String, double[]) @@ -7418,7 +7569,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no String array value was found. * * @see #putExtra(String, String[]) @@ -7432,7 +7583,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no CharSequence array value was found. * * @see #putExtra(String, CharSequence[]) @@ -7446,7 +7597,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Bundle value was found. * * @see #putExtra(String, Bundle) @@ -7460,7 +7611,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no IBinder value was found. * * @see #putExtra(String, IBinder) @@ -7480,7 +7631,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue The default value to return in case no item is * associated with the key 'name' * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or defaultValue if none was found. * * @see #putExtra @@ -9451,7 +9602,7 @@ public class Intent implements Parcelable, Cloneable { proto.write(IntentProto.PACKAGE, mPackage); } if (comp && mComponent != null) { - proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString()); + mComponent.writeToProto(proto, IntentProto.COMPONENT); } if (mSourceBounds != null) { proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString()); @@ -10024,6 +10175,24 @@ public class Intent implements Parcelable, Cloneable { } } + /** @hide */ + public boolean hasWebURI() { + if (getData() == null) { + return false; + } + final String scheme = getScheme(); + if (TextUtils.isEmpty(scheme)) { + return false; + } + return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); + } + + /** @hide */ + public boolean isWebIntent() { + return ACTION_VIEW.equals(mAction) + && hasWebURI(); + } + /** * @hide */ diff --git a/android/content/IntentFilter.java b/android/content/IntentFilter.java index a957aed8..cec3badd 100644 --- a/android/content/IntentFilter.java +++ b/android/content/IntentFilter.java @@ -1872,9 +1872,10 @@ public class IntentFilter implements Parcelable { du.println(sb.toString()); } } - if (mPriority != 0 || mHasPartialTypes) { + if (mPriority != 0 || mOrder != 0 || mHasPartialTypes) { sb.setLength(0); sb.append(prefix); sb.append("mPriority="); sb.append(mPriority); + sb.append(", mOrder="); sb.append(mOrder); sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes); du.println(sb.toString()); } @@ -1951,6 +1952,7 @@ public class IntentFilter implements Parcelable { dest.writeInt(mHasPartialTypes ? 1 : 0); dest.writeInt(getAutoVerify() ? 1 : 0); dest.writeInt(mInstantAppVisibility); + dest.writeInt(mOrder); } /** @@ -2020,6 +2022,7 @@ public class IntentFilter implements Parcelable { mHasPartialTypes = source.readInt() > 0; setAutoVerify(source.readInt() > 0); setVisibilityToInstantApp(source.readInt()); + mOrder = source.readInt(); } private final boolean findMimeType(String type) { diff --git a/android/content/PermissionChecker.java b/android/content/PermissionChecker.java new file mode 100644 index 00000000..9f5c877e --- /dev/null +++ b/android/content/PermissionChecker.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 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.content; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Process; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class provides permission check APIs that verify both the + * permission and the associated app op for this permission if + * such is defined. + * <p> + * In the new permission model permissions with protection level + * dangerous are runtime permissions. For apps targeting {@link android.os.Build.VERSION_CODES#M} + * and above the user may not grant such permissions or revoke + * them at any time. For apps targeting API lower than {@link android.os.Build.VERSION_CODES#M} + * these permissions are always granted as such apps do not expect + * permission revocations and would crash. Therefore, when the + * user disables a permission for a legacy app in the UI the + * platform disables the APIs guarded by this permission making + * them a no-op which is doing nothing or returning an empty + * result or default error. + * </p> + * <p> + * It is important that when you perform an operation on behalf of + * another app you use these APIs to check for permissions as the + * app may be a legacy app that does not participate in the new + * permission model for which the user had disabled the "permission" + * which is achieved by disallowing the corresponding app op. + * </p> + * + * @hide + */ +public final class PermissionChecker { + /** Permission result: The permission is granted. */ + public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED; + + /** Permission result: The permission is denied. */ + public static final int PERMISSION_DENIED = PackageManager.PERMISSION_DENIED; + + /** Permission result: The permission is denied because the app op is not allowed. */ + public static final int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1; + + /** @hide */ + @IntDef({PERMISSION_GRANTED, + PERMISSION_DENIED, + PERMISSION_DENIED_APP_OP}) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionResult {} + + private PermissionChecker() { + /* do nothing */ + } + + /** + * Checks whether a given package in a UID and PID has a given permission + * and whether the app op that corresponds to this permission is allowed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param pid The process id for which to check. + * @param uid The uid for which to check. + * @param packageName The package name for which to check. If null the + * the first package for the calling UID will be used. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + */ + @PermissionResult + public static int checkPermission(@NonNull Context context, @NonNull String permission, + int pid, int uid, @Nullable String packageName) { + if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { + return PERMISSION_DENIED; + } + + AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + String op = appOpsManager.permissionToOp(permission); + if (op == null) { + return PERMISSION_GRANTED; + } + + if (packageName == null) { + String[] packageNames = context.getPackageManager().getPackagesForUid(uid); + if (packageNames == null || packageNames.length <= 0) { + return PERMISSION_DENIED; + } + packageName = packageNames[0]; + } + + if (appOpsManager.noteProxyOpNoThrow(op, packageName) + != AppOpsManager.MODE_ALLOWED) { + return PERMISSION_DENIED_APP_OP; + } + + return PERMISSION_GRANTED; + } + + /** + * Checks whether your app has a given permission and whether the app op + * that corresponds to this permission is allowed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + */ + @PermissionResult + public static int checkSelfPermission(@NonNull Context context, + @NonNull String permission) { + return checkPermission(context, permission, Process.myPid(), + Process.myUid(), context.getPackageName()); + } + + /** + * Checks whether the IPC you are handling has a given permission and whether + * the app op that corresponds to this permission is allowed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param packageName The package name making the IPC. If null the + * the first package for the calling UID will be used. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + */ + @PermissionResult + public static int checkCallingPermission(@NonNull Context context, + @NonNull String permission, @Nullable String packageName) { + if (Binder.getCallingPid() == Process.myPid()) { + return PERMISSION_DENIED; + } + return checkPermission(context, permission, Binder.getCallingPid(), + Binder.getCallingUid(), packageName); + } + + /** + * Checks whether the IPC you are handling or your app has a given permission + * and whether the app op that corresponds to this permission is allowed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + */ + @PermissionResult + public static int checkCallingOrSelfPermission(@NonNull Context context, + @NonNull String permission) { + String packageName = (Binder.getCallingPid() == Process.myPid()) + ? context.getPackageName() : null; + return checkPermission(context, permission, Binder.getCallingPid(), + Binder.getCallingUid(), packageName); + } +} diff --git a/android/content/SyncResult.java b/android/content/SyncResult.java index 4f86af98..f67d7f53 100644 --- a/android/content/SyncResult.java +++ b/android/content/SyncResult.java @@ -79,7 +79,17 @@ public final class SyncResult implements Parcelable { /** * Used to indicate to the SyncManager that future sync requests that match the request's - * Account and authority should be delayed at least this many seconds. + * Account and authority should be delayed until a time in seconds since Java epoch. + * + * <p>For example, if you want to delay the next sync for at least 5 minutes, then: + * <pre> + * result.delayUntil = (System.currentTimeMillis() / 1000) + 5 * 60; + * </pre> + * + * <p>By default, when a sync fails, the system retries later with an exponential back-off + * with the system default initial delay time, which always wins over {@link #delayUntil} -- + * i.e. if the system back-off time is larger than {@link #delayUntil}, {@link #delayUntil} + * will essentially be ignored. */ public long delayUntil; diff --git a/android/content/om/OverlayInfo.java b/android/content/om/OverlayInfo.java index 1a207ba0..6e633426 100644 --- a/android/content/om/OverlayInfo.java +++ b/android/content/om/OverlayInfo.java @@ -16,10 +16,15 @@ package android.content.om; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Immutable overlay information about a package. All PackageInfos that * represent an overlay package will have a corresponding OverlayInfo. @@ -27,6 +32,19 @@ import android.os.Parcelable; * @hide */ public final class OverlayInfo implements Parcelable { + + @IntDef(prefix = "STATE_", value = { + STATE_UNKNOWN, + STATE_MISSING_TARGET, + STATE_NO_IDMAP, + STATE_DISABLED, + STATE_ENABLED, + STATE_TARGET_UPGRADING, + STATE_OVERLAY_UPGRADING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + /** * An internal state used as the initial state of an overlay. OverlayInfo * objects exposed outside the {@link @@ -49,18 +67,35 @@ public final class OverlayInfo implements Parcelable { /** * The overlay is currently disabled. It can be enabled. * - * @see IOverlayManager.setEnabled + * @see IOverlayManager#setEnabled */ public static final int STATE_DISABLED = 2; /** * The overlay is currently enabled. It can be disabled. * - * @see IOverlayManager.setEnabled + * @see IOverlayManager#setEnabled */ public static final int STATE_ENABLED = 3; /** + * The target package is currently being upgraded; the state will change + * once the package installation has finished. + */ + public static final int STATE_TARGET_UPGRADING = 4; + + /** + * The overlay package is currently being upgraded; the state will change + * once the package installation has finished. + */ + public static final int STATE_OVERLAY_UPGRADING = 5; + + /** + * Category for theme overlays. + */ + public static final String CATEGORY_THEME = "android.theme"; + + /** * Package name of the overlay package */ public final String packageName; @@ -71,19 +106,19 @@ public final class OverlayInfo implements Parcelable { public final String targetPackageName; /** + * Category of the overlay package + */ + public final String category; + + /** * Full path to the base APK for this overlay package */ public final String baseCodePath; /** * The state of this OverlayInfo as defined by the STATE_* constants in this class. - * - * @see #STATE_MISSING_TARGET - * @see #STATE_NO_IDMAP - * @see #STATE_DISABLED - * @see #STATE_ENABLED */ - public final int state; + public final @State int state; /** * User handle for which this overlay applies @@ -96,15 +131,16 @@ public final class OverlayInfo implements Parcelable { * @param source the source OverlayInfo to base the new instance on * @param state the new state for the source OverlayInfo */ - public OverlayInfo(@NonNull OverlayInfo source, int state) { - this(source.packageName, source.targetPackageName, source.baseCodePath, state, - source.userId); + public OverlayInfo(@NonNull OverlayInfo source, @State int state) { + this(source.packageName, source.targetPackageName, source.category, source.baseCodePath, + state, source.userId); } public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName, - @NonNull String baseCodePath, int state, int userId) { + @Nullable String category, @NonNull String baseCodePath, int state, int userId) { this.packageName = packageName; this.targetPackageName = targetPackageName; + this.category = category; this.baseCodePath = baseCodePath; this.state = state; this.userId = userId; @@ -114,6 +150,7 @@ public final class OverlayInfo implements Parcelable { public OverlayInfo(Parcel source) { packageName = source.readString(); targetPackageName = source.readString(); + category = source.readString(); baseCodePath = source.readString(); state = source.readInt(); userId = source.readInt(); @@ -136,6 +173,8 @@ public final class OverlayInfo implements Parcelable { case STATE_NO_IDMAP: case STATE_DISABLED: case STATE_ENABLED: + case STATE_TARGET_UPGRADING: + case STATE_OVERLAY_UPGRADING: break; default: throw new IllegalArgumentException("State " + state + " is not a valid state"); @@ -151,12 +190,14 @@ public final class OverlayInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(packageName); dest.writeString(targetPackageName); + dest.writeString(category); dest.writeString(baseCodePath); dest.writeInt(state); dest.writeInt(userId); } - public static final Parcelable.Creator<OverlayInfo> CREATOR = new Parcelable.Creator<OverlayInfo>() { + public static final Parcelable.Creator<OverlayInfo> CREATOR = + new Parcelable.Creator<OverlayInfo>() { @Override public OverlayInfo createFromParcel(Parcel source) { return new OverlayInfo(source); @@ -189,14 +230,9 @@ public final class OverlayInfo implements Parcelable { * Translate a state to a human readable string. Only intended for * debugging purposes. * - * @see #STATE_MISSING_TARGET - * @see #STATE_NO_IDMAP - * @see #STATE_DISABLED - * @see #STATE_ENABLED - * * @return a human readable String representing the state. */ - public static String stateToString(int state) { + public static String stateToString(@State int state) { switch (state) { case STATE_UNKNOWN: return "STATE_UNKNOWN"; @@ -208,6 +244,10 @@ public final class OverlayInfo implements Parcelable { return "STATE_DISABLED"; case STATE_ENABLED: return "STATE_ENABLED"; + case STATE_TARGET_UPGRADING: + return "STATE_TARGET_UPGRADING"; + case STATE_OVERLAY_UPGRADING: + return "STATE_OVERLAY_UPGRADING"; default: return "<unknown state>"; } @@ -249,6 +289,9 @@ public final class OverlayInfo implements Parcelable { if (!targetPackageName.equals(other.targetPackageName)) { return false; } + if (!category.equals(other.category)) { + return false; + } if (!baseCodePath.equals(other.baseCodePath)) { return false; } diff --git a/android/content/pm/AndroidTestBaseUpdater.java b/android/content/pm/AndroidTestBaseUpdater.java new file mode 100644 index 00000000..2aaac028 --- /dev/null +++ b/android/content/pm/AndroidTestBaseUpdater.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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.content.pm; + +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; + +import android.content.pm.PackageParser.Package; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Updates a package to ensure that if it targets < P that the android.test.base library is + * included by default. + * + * <p>This is separated out so that it can be conditionally included at build time depending on + * whether android.test.base is on the bootclasspath or not. In order to include this at + * build time, and remove android.test.base from the bootclasspath pass + * REMOVE_ATB_FROM_BCP=true on the build command line, otherwise this class will not be included + * and the + * + * @hide + */ +@VisibleForTesting +public class AndroidTestBaseUpdater extends PackageSharedLibraryUpdater { + + @Override + public void updatePackage(Package pkg) { + // Packages targeted at <= O_MR1 expect the classes in the android.test.base library + // to be accessible so this maintains backward compatibility by adding the + // android.test.base library to those packages. + if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) { + prefixRequiredLibrary(pkg, ANDROID_TEST_BASE); + } else { + // If a package already depends on android.test.runner then add a dependency on + // android.test.base because android.test.runner depends on classes from the + // android.test.base library. + prefixImplicitDependency(pkg, ANDROID_TEST_RUNNER, ANDROID_TEST_BASE); + } + } +} diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java index 746a0902..e85058df 100644 --- a/android/content/pm/ApplicationInfo.java +++ b/android/content/pm/ApplicationInfo.java @@ -37,6 +37,7 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.util.ArrayUtils; +import com.android.server.SystemConfig; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -602,6 +603,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_VENDOR = 1 << 18; + /** + * Value for {@linl #privateFlags}: whether this app is pre-installed on the + * product partition of the system image. + * @hide + */ + public static final int PRIVATE_FLAG_PRODUCT = 1 << 19; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE, @@ -619,6 +627,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_OEM, PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE, PRIVATE_FLAG_PRIVILEGED, + PRIVATE_FLAG_PRODUCT, PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER, PRIVATE_FLAG_STATIC_SHARED_LIBRARY, PRIVATE_FLAG_VENDOR, @@ -754,15 +763,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String[] resourceDirs; /** - * String retrieved from the seinfo tag found in selinux policy. This value - * can be overridden with a value set through the mac_permissions.xml policy - * construct. This value is useful in setting an SELinux security context on - * the process as well as its data directory. The String default is being used - * here to represent a catchall label when no policy matches. + * String retrieved from the seinfo tag found in selinux policy. This value can be set through + * the mac_permissions.xml policy construct. This value is used for setting an SELinux security + * context on the process as well as its data directory. * * {@hide} */ - public String seInfo = "default"; + public String seInfo; /** * The seinfo tag generated per-user. This value may change based upon the @@ -950,6 +957,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Version of the sandbox the application wants to run in. * @hide */ + @SystemApi public int targetSandboxVersion; /** @@ -1093,6 +1101,58 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** @hide */ public String[] splitClassLoaderNames; + /** + * Represents the default policy. The actual policy used will depend on other properties of + * the application, e.g. the target SDK version. + * @hide + */ + public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1; + /** + * No API enforcement; the app can access the entire internal private API. Only for use by + * system apps. + * @hide + */ + public static final int HIDDEN_API_ENFORCEMENT_NONE = 0; + /** + * Light grey list enforcement, the strictest option. Enforces the light grey, dark grey and + * black lists. + * @hide + * */ + public static final int HIDDEN_API_ENFORCEMENT_ALL_LISTS = 1; + /** + * Dark grey list enforcement. Enforces the dark grey and black lists + * @hide + */ + public static final int HIDDEN_API_ENFORCEMENT_DARK_GREY_AND_BLACK = 2; + /** + * Blacklist enforcement only. + * @hide + */ + public static final int HIDDEN_API_ENFORCEMENT_BLACK = 3; + + private static final int HIDDEN_API_ENFORCEMENT_MAX = HIDDEN_API_ENFORCEMENT_BLACK; + + /** + * Values in this IntDef MUST be kept in sync with enum hiddenapi::EnforcementPolicy in + * art/runtime/hidden_api.h + * @hide + */ + @IntDef(prefix = { "HIDDEN_API_ENFORCEMENT_" }, value = { + HIDDEN_API_ENFORCEMENT_DEFAULT, + HIDDEN_API_ENFORCEMENT_NONE, + HIDDEN_API_ENFORCEMENT_ALL_LISTS, + HIDDEN_API_ENFORCEMENT_DARK_GREY_AND_BLACK, + HIDDEN_API_ENFORCEMENT_BLACK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HiddenApiEnforcementPolicy {} + + private boolean isValidHiddenApiEnforcementPolicy(int policy) { + return policy >= HIDDEN_API_ENFORCEMENT_DEFAULT && policy <= HIDDEN_API_ENFORCEMENT_MAX; + } + + private int mHiddenApiPolicy = HIDDEN_API_ENFORCEMENT_DEFAULT; + public void dump(Printer pw, String prefix) { dump(pw, prefix, DUMP_FLAG_ALL); } @@ -1180,6 +1240,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (category != CATEGORY_UNDEFINED) { pw.println(prefix + "category=" + category); } + pw.println(prefix + "HiddenApiEnforcementPolicy=" + getHiddenApiEnforcementPolicy()); } super.dumpBack(pw, prefix); } @@ -1375,6 +1436,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { classLoaderName = orig.classLoaderName; splitClassLoaderNames = orig.splitClassLoaderNames; appComponentFactory = orig.appComponentFactory; + compileSdkVersion = orig.compileSdkVersion; + compileSdkVersionCodename = orig.compileSdkVersionCodename; + mHiddenApiPolicy = orig.mHiddenApiPolicy; } public String toString() { @@ -1448,6 +1512,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); dest.writeString(appComponentFactory); + dest.writeInt(mHiddenApiPolicy); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -1518,6 +1583,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); appComponentFactory = source.readString(); + mHiddenApiPolicy = source.readInt(); } /** @@ -1588,11 +1654,31 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } + private boolean isPackageWhitelistedForHiddenApis() { + return SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(packageName); + } + + /** + * @hide + */ + public @HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() { + if (mHiddenApiPolicy != HIDDEN_API_ENFORCEMENT_DEFAULT) { + return mHiddenApiPolicy; + } + if (isPackageWhitelistedForHiddenApis() && (isSystemApp() || isUpdatedSystemApp())) { + return HIDDEN_API_ENFORCEMENT_NONE; + } + return HIDDEN_API_ENFORCEMENT_BLACK; + } + /** * @hide */ - public boolean isAllowedToUseHiddenApi() { - return isSystemApp(); + public void setHiddenApiEnforcementPolicy(@HiddenApiEnforcementPolicy int policy) { + if (!isValidHiddenApiEnforcementPolicy(policy)) { + throw new IllegalArgumentException("Invalid API enforcement policy: " + policy); + } + mHiddenApiPolicy = policy; } /** @@ -1647,7 +1733,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; } - /** @hide */ + /** + * True if the application is installed as an instant app. + * @hide + */ + @SystemApi public boolean isInstantApp() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; } @@ -1699,6 +1789,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0; } + /** @hide */ + public boolean isProduct() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0; + } + /** * Returns whether or not this application was installed as a virtual preload. */ diff --git a/android/content/pm/AuxiliaryResolveInfo.java b/android/content/pm/AuxiliaryResolveInfo.java index 6bdcefbe..202df50d 100644 --- a/android/content/pm/AuxiliaryResolveInfo.java +++ b/android/content/pm/AuxiliaryResolveInfo.java @@ -21,6 +21,10 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; + +import java.util.Collections; +import java.util.List; /** * Auxiliary application resolution response. @@ -31,56 +35,95 @@ import android.content.IntentFilter; * hasn't been installed. * @hide */ -public final class AuxiliaryResolveInfo extends IntentFilter { - /** Resolved information returned from the external instant resolver */ - public final InstantAppResolveInfo resolveInfo; - /** The resolved package. Copied from {@link #resolveInfo}. */ - public final String packageName; +public final class AuxiliaryResolveInfo { /** The activity to launch if there's an installation failure. */ public final ComponentName installFailureActivity; - /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ - public final String splitName; /** Whether or not instant resolution needs the second phase */ public final boolean needsPhaseTwo; /** Opaque token to track the instant application resolution */ public final String token; - /** The version code of the package */ - public final long versionCode; /** An intent to start upon failure to install */ public final Intent failureIntent; + /** The matching filters for this resolve info. */ + public final List<AuxiliaryFilter> filters; /** Create a response for installing an instant application. */ - public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo, - @NonNull IntentFilter orig, - @Nullable String splitName, - @NonNull String token, + public AuxiliaryResolveInfo(@NonNull String token, boolean needsPhase2, - @Nullable Intent failureIntent) { - super(orig); - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.splitName = splitName; + @Nullable Intent failureIntent, + @Nullable List<AuxiliaryFilter> filters) { this.token = token; this.needsPhaseTwo = needsPhase2; - this.versionCode = resolveInfo.getVersionCode(); this.failureIntent = failureIntent; + this.filters = filters; this.installFailureActivity = null; } /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@NonNull String packageName, - @Nullable String splitName, - @Nullable ComponentName failureActivity, - long versionCode, - @Nullable Intent failureIntent) { + public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, + @Nullable Intent failureIntent, + @Nullable List<AuxiliaryFilter> filters) { super(); - this.packageName = packageName; this.installFailureActivity = failureActivity; - this.splitName = splitName; - this.versionCode = versionCode; - this.resolveInfo = null; + this.filters = filters; this.token = null; this.needsPhaseTwo = false; this.failureIntent = failureIntent; } + + /** Create a response for installing a split on demand. */ + public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, + String packageName, long versionCode, String splitName) { + this(failureActivity, null, Collections.singletonList( + new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName))); + } + + /** @hide */ + public static final class AuxiliaryFilter extends IntentFilter { + /** Resolved information returned from the external instant resolver */ + public final InstantAppResolveInfo resolveInfo; + /** The resolved package. Copied from {@link #resolveInfo}. */ + public final String packageName; + /** The version code of the package */ + public final long versionCode; + /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ + public final String splitName; + /** The extras to pass on to the installer for this filter. */ + public final Bundle extras; + + public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo, + String splitName, Bundle extras) { + super(orig); + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.versionCode = resolveInfo.getLongVersionCode(); + this.splitName = splitName; + this.extras = extras; + } + + public AuxiliaryFilter(InstantAppResolveInfo resolveInfo, + String splitName, Bundle extras) { + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.versionCode = resolveInfo.getLongVersionCode(); + this.splitName = splitName; + this.extras = extras; + } + + public AuxiliaryFilter(String packageName, long versionCode, String splitName) { + this.resolveInfo = null; + this.packageName = packageName; + this.versionCode = versionCode; + this.splitName = splitName; + this.extras = null; + } + + @Override + public String toString() { + return "AuxiliaryFilter{" + + "packageName='" + packageName + '\'' + + ", versionCode=" + versionCode + + ", splitName='" + splitName + '\'' + '}'; + } + } }
\ No newline at end of file diff --git a/android/content/pm/EphemeralIntentFilter.java b/android/content/pm/EphemeralIntentFilter.java deleted file mode 100644 index 1dbbf816..00000000 --- a/android/content/pm/EphemeralIntentFilter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 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.content.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.IntentFilter; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Information about an ephemeral application intent filter. - * @hide - * @removed - */ -@Deprecated -@SystemApi -public final class EphemeralIntentFilter implements Parcelable { - private final InstantAppIntentFilter mInstantAppIntentFilter; - - public EphemeralIntentFilter(@Nullable String splitName, @NonNull List<IntentFilter> filters) { - mInstantAppIntentFilter = new InstantAppIntentFilter(splitName, filters); - } - - EphemeralIntentFilter(@NonNull InstantAppIntentFilter intentFilter) { - mInstantAppIntentFilter = intentFilter; - } - - EphemeralIntentFilter(Parcel in) { - mInstantAppIntentFilter = in.readParcelable(null /*loader*/); - } - - public String getSplitName() { - return mInstantAppIntentFilter.getSplitName(); - } - - public List<IntentFilter> getFilters() { - return mInstantAppIntentFilter.getFilters(); - } - - /** @hide */ - InstantAppIntentFilter getInstantAppIntentFilter() { - return mInstantAppIntentFilter; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppIntentFilter, flags); - } - - public static final Parcelable.Creator<EphemeralIntentFilter> CREATOR - = new Parcelable.Creator<EphemeralIntentFilter>() { - @Override - public EphemeralIntentFilter createFromParcel(Parcel in) { - return new EphemeralIntentFilter(in); - } - @Override - public EphemeralIntentFilter[] newArray(int size) { - return new EphemeralIntentFilter[size]; - } - }; -} diff --git a/android/content/pm/EphemeralResolveInfo.java b/android/content/pm/EphemeralResolveInfo.java deleted file mode 100644 index 12131a3e..00000000 --- a/android/content/pm/EphemeralResolveInfo.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2015 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.content.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.IntentFilter; -import android.content.pm.InstantAppResolveInfo.InstantAppDigest; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * Information about an ephemeral application. - * @hide - * @removed - */ -@Deprecated -@SystemApi -public final class EphemeralResolveInfo implements Parcelable { - /** Algorithm that will be used to generate the domain digest */ - public static final String SHA_ALGORITHM = "SHA-256"; - - private final InstantAppResolveInfo mInstantAppResolveInfo; - @Deprecated - private final List<IntentFilter> mLegacyFilters; - - @Deprecated - public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName, - @NonNull List<IntentFilter> filters) { - if (uri == null || packageName == null || filters == null || filters.isEmpty()) { - throw new IllegalArgumentException(); - } - final List<EphemeralIntentFilter> ephemeralFilters = new ArrayList<>(1); - ephemeralFilters.add(new EphemeralIntentFilter(packageName, filters)); - mInstantAppResolveInfo = new InstantAppResolveInfo(uri.getHost(), packageName, - createInstantAppIntentFilterList(ephemeralFilters)); - mLegacyFilters = new ArrayList<IntentFilter>(filters.size()); - mLegacyFilters.addAll(filters); - } - - @Deprecated - public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, - @Nullable List<EphemeralIntentFilter> filters) { - this(digest, packageName, filters, -1 /*versionCode*/); - } - - public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, - @Nullable List<EphemeralIntentFilter> filters, int versionCode) { - mInstantAppResolveInfo = new InstantAppResolveInfo( - digest.getInstantAppDigest(), packageName, - createInstantAppIntentFilterList(filters), versionCode); - mLegacyFilters = null; - } - - public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName, - @Nullable List<EphemeralIntentFilter> filters) { - this(new EphemeralDigest(hostName), packageName, filters); - } - - EphemeralResolveInfo(Parcel in) { - mInstantAppResolveInfo = in.readParcelable(null /*loader*/); - mLegacyFilters = new ArrayList<IntentFilter>(); - in.readList(mLegacyFilters, null /*loader*/); - } - - /** @hide */ - public InstantAppResolveInfo getInstantAppResolveInfo() { - return mInstantAppResolveInfo; - } - - private static List<InstantAppIntentFilter> createInstantAppIntentFilterList( - List<EphemeralIntentFilter> filters) { - if (filters == null) { - return null; - } - final int filterCount = filters.size(); - final List<InstantAppIntentFilter> returnList = new ArrayList<>(filterCount); - for (int i = 0; i < filterCount; i++) { - returnList.add(filters.get(i).getInstantAppIntentFilter()); - } - return returnList; - } - - public byte[] getDigestBytes() { - return mInstantAppResolveInfo.getDigestBytes(); - } - - public int getDigestPrefix() { - return mInstantAppResolveInfo.getDigestPrefix(); - } - - public String getPackageName() { - return mInstantAppResolveInfo.getPackageName(); - } - - public List<EphemeralIntentFilter> getIntentFilters() { - final List<InstantAppIntentFilter> filters = mInstantAppResolveInfo.getIntentFilters(); - final int filterCount = filters.size(); - final List<EphemeralIntentFilter> returnList = new ArrayList<>(filterCount); - for (int i = 0; i < filterCount; i++) { - returnList.add(new EphemeralIntentFilter(filters.get(i))); - } - return returnList; - } - - public int getVersionCode() { - return mInstantAppResolveInfo.getVersionCode(); - } - - @Deprecated - public List<IntentFilter> getFilters() { - return mLegacyFilters; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppResolveInfo, flags); - out.writeList(mLegacyFilters); - } - - public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR - = new Parcelable.Creator<EphemeralResolveInfo>() { - @Override - public EphemeralResolveInfo createFromParcel(Parcel in) { - return new EphemeralResolveInfo(in); - } - @Override - public EphemeralResolveInfo[] newArray(int size) { - return new EphemeralResolveInfo[size]; - } - }; - - /** - * Helper class to generate and store each of the digests and prefixes - * sent to the Ephemeral Resolver. - * <p> - * Since intent filters may want to handle multiple hosts within a - * domain [eg “*.google.com”], the resolver is presented with multiple - * hash prefixes. For example, "a.b.c.d.e" generates digests for - * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e". - * - * @hide - */ - @SystemApi - public static final class EphemeralDigest implements Parcelable { - private final InstantAppDigest mInstantAppDigest; - - public EphemeralDigest(@NonNull String hostName) { - this(hostName, -1 /*maxDigests*/); - } - - /** @hide */ - public EphemeralDigest(@NonNull String hostName, int maxDigests) { - mInstantAppDigest = new InstantAppDigest(hostName, maxDigests); - } - - EphemeralDigest(Parcel in) { - mInstantAppDigest = in.readParcelable(null /*loader*/); - } - - /** @hide */ - InstantAppDigest getInstantAppDigest() { - return mInstantAppDigest; - } - - public byte[][] getDigestBytes() { - return mInstantAppDigest.getDigestBytes(); - } - - public int[] getDigestPrefix() { - return mInstantAppDigest.getDigestPrefix(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppDigest, flags); - } - - @SuppressWarnings("hiding") - public static final Parcelable.Creator<EphemeralDigest> CREATOR = - new Parcelable.Creator<EphemeralDigest>() { - @Override - public EphemeralDigest createFromParcel(Parcel in) { - return new EphemeralDigest(in); - } - @Override - public EphemeralDigest[] newArray(int size) { - return new EphemeralDigest[size]; - } - }; - } -} diff --git a/android/content/pm/InstantAppRequest.java b/android/content/pm/InstantAppRequest.java index 38f02256..361d4e4a 100644 --- a/android/content/pm/InstantAppRequest.java +++ b/android/content/pm/InstantAppRequest.java @@ -18,12 +18,14 @@ package android.content.pm; import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; /** * Information needed to make an instant application resolution request. * @hide */ public final class InstantAppRequest { + /** Response from the first phase of instant application resolution */ public final AuxiliaryResolveInfo responseObj; /** The original intent that triggered instant application resolution */ @@ -40,6 +42,8 @@ public final class InstantAppRequest { public final Bundle verificationBundle; /** Whether resolution occurs because an application is starting */ public final boolean resolveForStart; + /** The instant app digest for this request */ + public final InstantAppResolveInfo.InstantAppDigest digest; public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, int userId, Bundle verificationBundle, @@ -51,5 +55,11 @@ public final class InstantAppRequest { this.userId = userId; this.verificationBundle = verificationBundle; this.resolveForStart = resolveForStart; + if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) { + digest = new InstantAppResolveInfo.InstantAppDigest( + origIntent.getData().getHost(), 5 /*maxDigests*/); + } else { + digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED; + } } } diff --git a/android/content/pm/InstantAppResolveInfo.java b/android/content/pm/InstantAppResolveInfo.java index 19cb9323..8184361b 100644 --- a/android/content/pm/InstantAppResolveInfo.java +++ b/android/content/pm/InstantAppResolveInfo.java @@ -19,18 +19,46 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Intent; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Random; /** - * Information about an instant application. + * Describes an externally resolvable instant application. There are three states that this class + * can represent: <p/> + * <ul> + * <li> + * The first, usable only for non http/s intents, implies that the resolver cannot + * immediately resolve this intent and would prefer that resolution be deferred to the + * instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}. + * If the {@link android.content.Intent} has the scheme set to http/s and a set of digest + * prefixes were passed into one of the resolve methods in + * {@link android.app.InstantAppResolverService}, this state cannot be used. + * </li> + * <li> + * The second represents a partial match and is constructed with any of the other + * constructors. By setting one or more of the {@link Nullable}arguments to null, you + * communicate to the resolver in response to + * {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[], + * String, InstantAppResolverService.InstantAppResolutionCallback)} + * that you need a 2nd round of resolution to complete the request. + * </li> + * <li> + * The third represents a complete match and is constructed with all @Nullable parameters + * populated. + * </li> + * </ul> * @hide */ @SystemApi @@ -38,6 +66,8 @@ public final class InstantAppResolveInfo implements Parcelable { /** Algorithm that will be used to generate the domain digest */ private static final String SHA_ALGORITHM = "SHA-256"; + private static final byte[] EMPTY_DIGEST = new byte[0]; + private final InstantAppDigest mDigest; private final String mPackageName; /** The filters used to match domain */ @@ -46,15 +76,43 @@ public final class InstantAppResolveInfo implements Parcelable { private final long mVersionCode; /** Data about the app that should be passed along to the Instant App installer on resolve */ private final Bundle mExtras; + /** + * A flag that indicates that the resolver is aware that an app may match, but would prefer + * that the installer get the sanitized intent to decide. + */ + private final boolean mShouldLetInstallerDecide; + /** Constructor for intent-based InstantApp resolution results. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List<InstantAppIntentFilter> filters, int versionCode) { this(digest, packageName, filters, (long) versionCode, null /* extras */); } + /** Constructor for intent-based InstantApp resolution results with extras. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List<InstantAppIntentFilter> filters, long versionCode, @Nullable Bundle extras) { + this(digest, packageName, filters, versionCode, extras, false); + } + + /** Constructor for intent-based InstantApp resolution results by hostname. */ + public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName, + @Nullable List<InstantAppIntentFilter> filters) { + this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/, + null /* extras */); + } + + /** + * Constructor that indicates that resolution could be delegated to the installer when the + * sanitized intent contains enough information to resolve completely. + */ + public InstantAppResolveInfo(@Nullable Bundle extras) { + this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true); + } + + private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, + @Nullable List<InstantAppIntentFilter> filters, long versionCode, + @Nullable Bundle extras, boolean shouldLetInstallerDecide) { // validate arguments if ((packageName == null && (filters != null && filters.size() != 0)) || (packageName != null && (filters == null || filters.size() == 0))) { @@ -62,7 +120,7 @@ public final class InstantAppResolveInfo implements Parcelable { } mDigest = digest; if (filters != null) { - mFilters = new ArrayList<InstantAppIntentFilter>(filters.size()); + mFilters = new ArrayList<>(filters.size()); mFilters.addAll(filters); } else { mFilters = null; @@ -70,25 +128,37 @@ public final class InstantAppResolveInfo implements Parcelable { mPackageName = packageName; mVersionCode = versionCode; mExtras = extras; - } - - public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName, - @Nullable List<InstantAppIntentFilter> filters) { - this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/, - null /* extras */); + mShouldLetInstallerDecide = shouldLetInstallerDecide; } InstantAppResolveInfo(Parcel in) { - mDigest = in.readParcelable(null /*loader*/); - mPackageName = in.readString(); - mFilters = new ArrayList<InstantAppIntentFilter>(); - in.readList(mFilters, null /*loader*/); - mVersionCode = in.readLong(); + mShouldLetInstallerDecide = in.readBoolean(); mExtras = in.readBundle(); + if (mShouldLetInstallerDecide) { + mDigest = InstantAppDigest.UNDEFINED; + mPackageName = null; + mFilters = Collections.emptyList(); + mVersionCode = -1; + } else { + mDigest = in.readParcelable(null /*loader*/); + mPackageName = in.readString(); + mFilters = new ArrayList<>(); + in.readList(mFilters, null /*loader*/); + mVersionCode = in.readLong(); + } + } + + /** + * Returns true if the resolver is aware that an app may match, but would prefer + * that the installer get the sanitized intent to decide. This should not be true for + * resolutions that include a host and will be ignored in such cases. + */ + public boolean shouldLetInstallerDecide() { + return mShouldLetInstallerDecide; } public byte[] getDigestBytes() { - return mDigest.getDigestBytes()[0]; + return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST; } public int getDigestPrefix() { @@ -127,11 +197,15 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + out.writeBoolean(mShouldLetInstallerDecide); + out.writeBundle(mExtras); + if (mShouldLetInstallerDecide) { + return; + } out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); out.writeLong(mVersionCode); - out.writeBundle(mExtras); } public static final Parcelable.Creator<InstantAppResolveInfo> CREATOR @@ -158,12 +232,30 @@ public final class InstantAppResolveInfo implements Parcelable { */ @SystemApi public static final class InstantAppDigest implements Parcelable { - private static final int DIGEST_MASK = 0xfffff000; - private static final int DIGEST_PREFIX_COUNT = 5; + static final int DIGEST_MASK = 0xfffff000; + + /** + * A special instance that represents and undefined digest used for cases that a host was + * not provided or is irrelevant to the response. + */ + public static final InstantAppDigest UNDEFINED = + new InstantAppDigest(new byte[][]{}, new int[]{}); + + private static Random sRandom = null; + static { + try { + sRandom = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + // oh well + sRandom = new Random(); + } + } /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; - /** The first 4 bytes of the domain hashes */ + /** The first 5 bytes of the domain hashes */ private final int[] mDigestPrefix; + /** The first 5 bytes of the domain hashes interspersed with random data */ + private int[] mDigestPrefixSecure; public InstantAppDigest(@NonNull String hostName) { this(hostName, -1 /*maxDigests*/); @@ -186,6 +278,11 @@ public final class InstantAppResolveInfo implements Parcelable { } } + private InstantAppDigest(byte[][] digestBytes, int[] prefix) { + this.mDigestPrefix = prefix; + this.mDigestBytes = digestBytes; + } + private static byte[][] generateDigest(String hostName, int maxDigests) { ArrayList<byte[]> digests = new ArrayList<>(); try { @@ -230,6 +327,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } mDigestPrefix = in.createIntArray(); + mDigestPrefixSecure = in.createIntArray(); } public byte[][] getDigestBytes() { @@ -240,6 +338,26 @@ public final class InstantAppResolveInfo implements Parcelable { return mDigestPrefix; } + /** + * Returns a digest prefix with additional random prefixes interspersed. + * @hide + */ + public int[] getDigestPrefixSecure() { + if (this == InstantAppResolveInfo.InstantAppDigest.UNDEFINED) { + return getDigestPrefix(); + } else if (mDigestPrefixSecure == null) { + // let's generate some random data to intersperse throughout the set of prefixes + final int realSize = getDigestPrefix().length; + final int manufacturedSize = realSize + 10 + sRandom.nextInt(10); + mDigestPrefixSecure = Arrays.copyOf(getDigestPrefix(), manufacturedSize); + for (int i = realSize; i < manufacturedSize; i++) { + mDigestPrefixSecure[i] = sRandom.nextInt() & DIGEST_MASK; + } + Arrays.sort(mDigestPrefixSecure); + } + return mDigestPrefixSecure; + } + @Override public int describeContents() { return 0; @@ -247,6 +365,11 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + final boolean isUndefined = this == UNDEFINED; + out.writeBoolean(isUndefined); + if (isUndefined) { + return; + } if (mDigestBytes == null) { out.writeInt(-1); } else { @@ -256,6 +379,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } out.writeIntArray(mDigestPrefix); + out.writeIntArray(mDigestPrefixSecure); } @SuppressWarnings("hiding") @@ -263,6 +387,9 @@ public final class InstantAppResolveInfo implements Parcelable { new Parcelable.Creator<InstantAppDigest>() { @Override public InstantAppDigest createFromParcel(Parcel in) { + if (in.readBoolean() /* is undefined */) { + return UNDEFINED; + } return new InstantAppDigest(in); } @Override diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java index b4a7eec0..9aace2e7 100644 --- a/android/content/pm/LauncherApps.java +++ b/android/content/pm/LauncherApps.java @@ -211,6 +211,10 @@ public class LauncherApps { * example, this can happen when a Device Administrator suspends * an applicaton. * + * <p>Note: On devices running {@link android.os.Build.VERSION_CODES#P Android P} or higher, + * any apps that override {@link #onPackagesSuspended(String[], Bundle, UserHandle)} will + * not receive this callback. + * * @param packageNames The names of the packages that have just been * suspended. * @param user The UserHandle of the profile that generated the change. @@ -219,6 +223,22 @@ public class LauncherApps { } /** + * Indicates that one or more packages have been suspended. A device administrator or an app + * with {@code android.permission.SUSPEND_APPS} can do this. + * + * @param packageNames The names of the packages that have just been suspended. + * @param launcherExtras A {@link Bundle} of extras for the launcher. + * @param user the user for which the given packages were suspended. + * + * @see PackageManager#isPackageSuspended() + * @see #getSuspendedPackageLauncherExtras(String, UserHandle) + */ + public void onPackagesSuspended(String[] packageNames, @Nullable Bundle launcherExtras, + UserHandle user) { + onPackagesSuspended(packageNames, user); + } + + /** * Indicates that one or more packages have been unsuspended. For * example, this can happen when a Device Administrator unsuspends * an applicaton. @@ -638,6 +658,31 @@ public class LauncherApps { } /** + * Gets the launcher extras supplied to the system when the given package was suspended via + * {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, String)}. + * + * <p>Note: This just returns whatever extras were provided to the system, <em>which might + * even be {@code null}.</em> + * + * @param packageName The package for which to fetch the launcher extras. + * @param user The {@link UserHandle} of the profile. + * @return A {@link Bundle} of launcher extras. Or {@code null} if the package is not currently + * suspended. + * + * @see Callback#onPackagesSuspended(String[], Bundle, UserHandle) + * @see PackageManager#isPackageSuspended() + */ + public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) { + logErrorForInvalidProfileAccess(user); + try { + return mService.getSuspendedPackageLauncherExtras(packageName, user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns {@link ApplicationInfo} about an application installed for a specific user profile. * * @param packageName The package name of the application @@ -652,7 +697,7 @@ public class LauncherApps { @ApplicationInfoFlags int flags, @NonNull UserHandle user) throws PackageManager.NameNotFoundException { Preconditions.checkNotNull(packageName, "packageName"); - Preconditions.checkNotNull(packageName, "user"); + Preconditions.checkNotNull(user, "user"); logErrorForInvalidProfileAccess(user); try { final ApplicationInfo ai = mService @@ -1163,14 +1208,15 @@ public class LauncherApps { } @Override - public void onPackagesSuspended(UserHandle user, String[] packageNames) + public void onPackagesSuspended(UserHandle user, String[] packageNames, + Bundle launcherExtras) throws RemoteException { if (DEBUG) { Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { - callback.postOnPackagesSuspended(packageNames, user); + callback.postOnPackagesSuspended(packageNames, launcherExtras, user); } } } @@ -1218,6 +1264,7 @@ public class LauncherApps { private static class CallbackInfo { String[] packageNames; String packageName; + Bundle launcherExtras; boolean replacing; UserHandle user; List<ShortcutInfo> shortcuts; @@ -1251,7 +1298,8 @@ public class LauncherApps { mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing); break; case MSG_SUSPENDED: - mCallback.onPackagesSuspended(info.packageNames, info.user); + mCallback.onPackagesSuspended(info.packageNames, info.launcherExtras, + info.user); break; case MSG_UNSUSPENDED: mCallback.onPackagesUnsuspended(info.packageNames, info.user); @@ -1301,10 +1349,12 @@ public class LauncherApps { obtainMessage(MSG_UNAVAILABLE, info).sendToTarget(); } - public void postOnPackagesSuspended(String[] packageNames, UserHandle user) { + public void postOnPackagesSuspended(String[] packageNames, Bundle launcherExtras, + UserHandle user) { CallbackInfo info = new CallbackInfo(); info.packageNames = packageNames; info.user = user; + info.launcherExtras = launcherExtras; obtainMessage(MSG_SUSPENDED, info).sendToTarget(); } @@ -1429,6 +1479,10 @@ public class LauncherApps { * Always non-null for a {@link #REQUEST_TYPE_APPWIDGET} request, and always null for a * different request type. * + * <p>Launcher should not show any configuration activity associated with the provider, and + * assume that the widget is already fully configured. Upon accepting the widget, it should + * pass the widgetId in {@link #accept(Bundle)}. + * * @return requested {@link AppWidgetProviderInfo} when a request is of the * {@link #REQUEST_TYPE_APPWIDGET} type. Null otherwise. */ diff --git a/android/content/pm/OrgApacheHttpLegacyUpdater.java b/android/content/pm/OrgApacheHttpLegacyUpdater.java index 81041e9d..81e4105f 100644 --- a/android/content/pm/OrgApacheHttpLegacyUpdater.java +++ b/android/content/pm/OrgApacheHttpLegacyUpdater.java @@ -15,13 +15,12 @@ */ package android.content.pm; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + import android.content.pm.PackageParser.Package; -import android.os.Build; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; - /** * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is * included by default. @@ -37,30 +36,13 @@ import java.util.ArrayList; @VisibleForTesting public class OrgApacheHttpLegacyUpdater extends PackageSharedLibraryUpdater { - private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy"; - @Override public void updatePackage(Package pkg) { - ArrayList<String> usesLibraries = pkg.usesLibraries; - ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; - // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library // to be accessible so this maintains backward compatibility by adding the // org.apache.http.legacy library to those packages. if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) { - boolean apacheHttpLegacyPresent = isLibraryPresent( - usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY); - if (!apacheHttpLegacyPresent) { - usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY); - } + prefixRequiredLibrary(pkg, ORG_APACHE_HTTP_LEGACY); } - - pkg.usesLibraries = usesLibraries; - pkg.usesOptionalLibraries = usesOptionalLibraries; - } - - private static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(Package pkg) { - int targetSdkVersion = pkg.applicationInfo.targetSdkVersion; - return targetSdkVersion <= Build.VERSION_CODES.O_MR1; } } diff --git a/android/content/pm/PackageBackwardCompatibility.java b/android/content/pm/PackageBackwardCompatibility.java index 9bdb78be..a16f81b1 100644 --- a/android/content/pm/PackageBackwardCompatibility.java +++ b/android/content/pm/PackageBackwardCompatibility.java @@ -16,14 +16,19 @@ package android.content.pm; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + import android.content.pm.PackageParser.Package; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; /** * Modifies {@link Package} in order to maintain backwards compatibility. @@ -35,54 +40,90 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { private static final String TAG = PackageBackwardCompatibility.class.getSimpleName(); - private static final String ANDROID_TEST_MOCK = "android.test.mock"; - - private static final String ANDROID_TEST_RUNNER = "android.test.runner"; - private static final PackageBackwardCompatibility INSTANCE; static { - String className = "android.content.pm.OrgApacheHttpLegacyUpdater"; + final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + + // Attempt to load and add the optional updater that will only be available when + // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that + // will remove any references to org.apache.http.library from the package so that it does + // not try and load the library when it is on the bootclasspath. + boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters, + "android.content.pm.OrgApacheHttpLegacyUpdater", + RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new); + + // Add this before adding AndroidTestBaseUpdater so that android.test.base comes before + // android.test.mock. + packageUpdaters.add(new AndroidTestRunnerSplitUpdater()); + + // Attempt to load and add the optional updater that will only be available when + // REMOVE_ATB_FROM_BCP=true. If that could not be found then add the default updater that + // will remove any references to org.apache.http.library from the package so that it does + // not try and load the library when it is on the bootclasspath. + boolean bootClassPathContainsATB = !addOptionalUpdater(packageUpdaters, + "android.content.pm.AndroidTestBaseUpdater", + RemoveUnnecessaryAndroidTestBaseLibrary::new); + + PackageSharedLibraryUpdater[] updaterArray = packageUpdaters + .toArray(new PackageSharedLibraryUpdater[0]); + INSTANCE = new PackageBackwardCompatibility( + bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray); + } + + /** + * Add an optional {@link PackageSharedLibraryUpdater} instance to the list, if it could not be + * found then add a default instance instead. + * + * @param packageUpdaters the list to update. + * @param className the name of the optional class. + * @param defaultUpdater the supplier of the default instance. + * @return true if the optional updater was added false otherwise. + */ + private static boolean addOptionalUpdater(List<PackageSharedLibraryUpdater> packageUpdaters, + String className, Supplier<PackageSharedLibraryUpdater> defaultUpdater) { Class<? extends PackageSharedLibraryUpdater> clazz; try { clazz = (PackageBackwardCompatibility.class.getClassLoader() .loadClass(className) .asSubclass(PackageSharedLibraryUpdater.class)); + Log.i(TAG, "Loaded " + className); } catch (ClassNotFoundException e) { Log.i(TAG, "Could not find " + className + ", ignoring"); clazz = null; } - boolean hasOrgApacheHttpLegacy = false; - final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + boolean usedOptional = false; + PackageSharedLibraryUpdater updater; if (clazz == null) { - // Add an updater that will remove any references to org.apache.http.library from the - // package so that it does not try and load the library when it is on the - // bootclasspath. - packageUpdaters.add(new RemoveUnnecessaryOrgApacheHttpLegacyLibrary()); + updater = defaultUpdater.get(); } else { try { - packageUpdaters.add(clazz.getConstructor().newInstance()); - hasOrgApacheHttpLegacy = true; + updater = clazz.getConstructor().newInstance(); + usedOptional = true; } catch (ReflectiveOperationException e) { throw new IllegalStateException("Could not create instance of " + className, e); } } + packageUpdaters.add(updater); + return usedOptional; + } - packageUpdaters.add(new AndroidTestRunnerSplitUpdater()); - - PackageSharedLibraryUpdater[] updaterArray = packageUpdaters - .toArray(new PackageSharedLibraryUpdater[0]); - INSTANCE = new PackageBackwardCompatibility(hasOrgApacheHttpLegacy, updaterArray); + @VisibleForTesting + public static PackageSharedLibraryUpdater getInstance() { + return INSTANCE; } - private final boolean mRemovedOAHLFromBCP; + private final boolean mBootClassPathContainsOAHL; + + private final boolean mBootClassPathContainsATB; private final PackageSharedLibraryUpdater[] mPackageUpdaters; - public PackageBackwardCompatibility(boolean removedOAHLFromBCP, - PackageSharedLibraryUpdater[] packageUpdaters) { - this.mRemovedOAHLFromBCP = removedOAHLFromBCP; + public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL, + boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) { + this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL; + this.mBootClassPathContainsATB = bootClassPathContainsATB; this.mPackageUpdaters = packageUpdaters; } @@ -99,17 +140,25 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { @Override public void updatePackage(Package pkg) { - for (PackageSharedLibraryUpdater packageUpdater : mPackageUpdaters) { packageUpdater.updatePackage(pkg); } } /** - * True if the org.apache.http.legacy has been removed the bootclasspath, false otherwise. + * True if the org.apache.http.legacy is on the bootclasspath, false otherwise. + */ + @VisibleForTesting + public static boolean bootClassPathContainsOAHL() { + return INSTANCE.mBootClassPathContainsOAHL; + } + + /** + * True if the android.test.base is on the bootclasspath, false otherwise. */ - public static boolean removeOAHLFromBCP() { - return INSTANCE.mRemovedOAHLFromBCP; + @VisibleForTesting + public static boolean bootClassPathContainsATB() { + return INSTANCE.mBootClassPathContainsATB; } /** @@ -126,24 +175,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { @Override public void updatePackage(Package pkg) { - ArrayList<String> usesLibraries = pkg.usesLibraries; - ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; - // android.test.runner has a dependency on android.test.mock so if android.test.runner // is present but android.test.mock is not then add android.test.mock. - boolean androidTestMockPresent = isLibraryPresent( - usesLibraries, usesOptionalLibraries, ANDROID_TEST_MOCK); - if (ArrayUtils.contains(usesLibraries, ANDROID_TEST_RUNNER) - && !androidTestMockPresent) { - usesLibraries.add(ANDROID_TEST_MOCK); - } - if (ArrayUtils.contains(usesOptionalLibraries, ANDROID_TEST_RUNNER) - && !androidTestMockPresent) { - usesOptionalLibraries.add(ANDROID_TEST_MOCK); - } - - pkg.usesLibraries = usesLibraries; - pkg.usesOptionalLibraries = usesOptionalLibraries; + prefixImplicitDependency(pkg, ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK); } } @@ -155,13 +189,24 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { public static class RemoveUnnecessaryOrgApacheHttpLegacyLibrary extends PackageSharedLibraryUpdater { - private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy"; + @Override + public void updatePackage(Package pkg) { + removeLibrary(pkg, ORG_APACHE_HTTP_LEGACY); + } + + } + + /** + * Remove any usages of android.test.base from the shared library as the library is on the + * bootclasspath. + */ + @VisibleForTesting + public static class RemoveUnnecessaryAndroidTestBaseLibrary + extends PackageSharedLibraryUpdater { @Override public void updatePackage(Package pkg) { - pkg.usesLibraries = ArrayUtils.remove(pkg.usesLibraries, APACHE_HTTP_LEGACY); - pkg.usesOptionalLibraries = - ArrayUtils.remove(pkg.usesOptionalLibraries, APACHE_HTTP_LEGACY); + removeLibrary(pkg, ANDROID_TEST_BASE); } } } diff --git a/android/content/pm/PackageInfo.java b/android/content/pm/PackageInfo.java index 09a46b8a..627ceb78 100644 --- a/android/content/pm/PackageInfo.java +++ b/android/content/pm/PackageInfo.java @@ -362,6 +362,13 @@ public class PackageInfo implements Parcelable { */ public String overlayTarget; + /** + * The overlay category, if any, of this package + * + * @hide + */ + public String overlayCategory; + /** @hide */ public int overlayPriority; @@ -464,10 +471,23 @@ public class PackageInfo implements Parcelable { dest.writeString(restrictedAccountType); dest.writeString(requiredAccountType); dest.writeString(overlayTarget); + dest.writeString(overlayCategory); dest.writeInt(overlayPriority); dest.writeBoolean(mOverlayIsStatic); dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); + writeSigningCertificateHistoryToParcel(dest, parcelableFlags); + } + + private void writeSigningCertificateHistoryToParcel(Parcel dest, int parcelableFlags) { + if (signingCertificateHistory != null) { + dest.writeInt(signingCertificateHistory.length); + for (int i = 0; i < signingCertificateHistory.length; i++) { + dest.writeTypedArray(signingCertificateHistory[i], parcelableFlags); + } + } else { + dest.writeInt(-1); + } } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -519,10 +539,12 @@ public class PackageInfo implements Parcelable { restrictedAccountType = source.readString(); requiredAccountType = source.readString(); overlayTarget = source.readString(); + overlayCategory = source.readString(); overlayPriority = source.readInt(); mOverlayIsStatic = source.readBoolean(); compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); + readSigningCertificateHistoryFromParcel(source); // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. @@ -534,6 +556,16 @@ public class PackageInfo implements Parcelable { } } + private void readSigningCertificateHistoryFromParcel(Parcel source) { + int len = source.readInt(); + if (len != -1) { + signingCertificateHistory = new Signature[len][]; + for (int i = 0; i < len; i++) { + signingCertificateHistory[i] = source.createTypedArray(Signature.CREATOR); + } + } + } + private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) { if (components != null) { for (ComponentInfo ci : components) { diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java index df677d20..25af1a76 100644 --- a/android/content/pm/PackageInstaller.java +++ b/android/content/pm/PackageInstaller.java @@ -448,11 +448,17 @@ public class PackageInstaller { /** * Uninstall the given package, removing it completely from the device. This - * method is only available to the current "installer of record" for the - * package. + * method is available to: + * <ul> + * <li>the current "installer of record" for the package + * <li>the device owner + * <li>the affiliated profile owner + * </ul> * * @param packageName The package to uninstall. * @param statusReceiver Where to deliver the result. + * + * @see android.app.admin.DevicePolicyManager */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, @@ -480,14 +486,22 @@ public class PackageInstaller { /** * Uninstall the given package with a specific version code, removing it - * completely from the device. This method is only available to the current - * "installer of record" for the package. If the version code of the package + * completely from the device. If the version code of the package * does not match the one passed in the versioned package argument this * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to * uninstall the latest version of the package. + * <p> + * This method is available to: + * <ul> + * <li>the current "installer of record" for the package + * <li>the device owner + * <li>the affiliated profile owner + * </ul> * * @param versionedPackage The versioned package to uninstall. * @param statusReceiver Where to deliver the result. + * + * @see android.app.admin.DevicePolicyManager */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, @@ -829,7 +843,19 @@ public class PackageInstaller { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + } + /** {@hide} */ + public void write(@NonNull String name, long offsetBytes, long lengthBytes, + @NonNull ParcelFileDescriptor fd) throws IOException { + try { + mSession.write(name, offsetBytes, lengthBytes, fd); + } catch (RuntimeException e) { + ExceptionUtils.maybeUnwrapIOException(e); + throw e; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -929,9 +955,14 @@ public class PackageInstaller { * Once this method is called, the session is sealed and no additional * mutations may be performed on the session. If the device reboots * before the session has been finalized, you may commit the session again. + * <p> + * If the installer is the device owner or the affiliated profile owner, there will be no + * user intervention. * * @throws SecurityException if streams opened through * {@link #openWrite(String, long, long)} are still open. + * + * @see android.app.admin.DevicePolicyManager */ public void commit(@NonNull IntentSender statusReceiver) { try { diff --git a/android/content/pm/PackageItemInfo.java b/android/content/pm/PackageItemInfo.java index 2c0c6ad0..53ffd55d 100644 --- a/android/content/pm/PackageItemInfo.java +++ b/android/content/pm/PackageItemInfo.java @@ -43,6 +43,14 @@ import java.util.Comparator; */ public class PackageItemInfo { private static final float MAX_LABEL_SIZE_PX = 500f; + + private static volatile boolean sForceSafeLabels = false; + + /** {@hide} */ + public static void setForceSafeLabels(boolean forceSafeLabels) { + sForceSafeLabels = forceSafeLabels; + } + /** * Public name of this item. From the "android:name" attribute. */ @@ -128,7 +136,16 @@ public class PackageItemInfo { * @return Returns a CharSequence containing the item's label. If the * item does not have a label, its name is returned. */ - public CharSequence loadLabel(PackageManager pm) { + public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) { + if (sForceSafeLabels) { + return loadSafeLabel(pm); + } else { + return loadUnsafeLabel(pm); + } + } + + /** {@hide} */ + public CharSequence loadUnsafeLabel(PackageManager pm) { if (nonLocalizedLabel != null) { return nonLocalizedLabel; } @@ -163,7 +180,7 @@ public class PackageItemInfo { @SystemApi public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) { // loadLabel() always returns non-null - String label = loadLabel(pm).toString(); + String label = loadUnsafeLabel(pm).toString(); // strip HTML tags to avoid <br> and other tags overwriting original message String labelStr = Html.fromHtml(label).toString(); diff --git a/android/content/pm/PackageManager.java b/android/content/pm/PackageManager.java index df69d803..491f0af2 100644 --- a/android/content/pm/PackageManager.java +++ b/android/content/pm/PackageManager.java @@ -51,6 +51,7 @@ import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -441,6 +442,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int MATCH_FACTORY_ONLY = 0x00200000; /** @@ -1950,6 +1952,14 @@ public abstract class PackageManager { * <li>Minor version number in bits 21-12</li> * <li>Patch version number in bits 11-0</li> * </ul> + * A version of 1.1.0 or higher also indicates: + * <ul> + * <li>The {@code VK_ANDROID_external_memory_android_hardware_buffer} extension is + * supported.</li> + * <li>{@code SYNC_FD} external semaphore and fence handles are supported.</li> + * <li>{@code VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion} is + * supported.</li> + * </ul> */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version"; @@ -2061,6 +2071,15 @@ public abstract class PackageManager { "android.hardware.sensor.hifi_sensors"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports a hardware mechanism for invoking an assist gesture. + * @see android.provider.Settings.Secure#ASSIST_GESTURE_ENABLED + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_ASSIST_GESTURE = "android.hardware.sensor.assist"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device has a telephony radio with data * communication support. @@ -2099,8 +2118,6 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device * supports embedded subscriptions on eUICCs. - * TODO(b/35851809): Make this public. - * @hide */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; @@ -2604,6 +2621,14 @@ public abstract class PackageManager { public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has a StrongBox hardware-backed Keystore. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_STRONGBOX_KEYSTORE = + "android.hardware.strongbox_keystore"; + + /** * Action to external storage service to clean out removed apps. * @hide */ @@ -2949,6 +2974,11 @@ public abstract class PackageManager { */ public static final int VERSION_CODE_HIGHEST = -1; + /** {@hide} */ + public int getUserId() { + return UserHandle.myUserId(); + } + /** * Retrieve overall information about an application package that is * installed on the system. @@ -3681,6 +3711,7 @@ public abstract class PackageManager { * * @hide */ + @TestApi public abstract @Nullable String[] getNamesForUids(int[] uids); /** @@ -4154,6 +4185,12 @@ public abstract class PackageManager { public abstract ResolveInfo resolveService(Intent intent, @ResolveInfoFlags int flags); /** + * @hide + */ + public abstract ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags, + @UserIdInt int userId); + + /** * Retrieve all services that can match the given intent. * * @param intent The desired intent as per resolveService(). @@ -4750,7 +4787,7 @@ public abstract class PackageManager { PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); if ((flags & GET_SIGNATURES) != 0) { - PackageParser.collectCertificates(pkg, 0); + PackageParser.collectCertificates(pkg, false /* skipVerify */); } PackageUserState state = new PackageUserState(); return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); @@ -5045,6 +5082,7 @@ public abstract class PackageManager { * which market the package came from. * * @param packageName The name of the package to query + * @throws IllegalArgumentException if the given package name is not installed */ public abstract String getInstallerPackageName(String packageName); @@ -5196,7 +5234,7 @@ public abstract class PackageManager { */ @Deprecated public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) { - getPackageSizeInfoAsUser(packageName, UserHandle.myUserId(), observer); + getPackageSizeInfoAsUser(packageName, getUserId(), observer); } /** @@ -5474,28 +5512,49 @@ public abstract class PackageManager { /** * Puts the package in a suspended state, where attempts at starting activities are denied. * - * <p>It doesn't remove the data or the actual package file. The application notifications - * will be hidden, the application will not show up in recents, will not be able to show - * toasts or dialogs or ring the device. + * <p>It doesn't remove the data or the actual package file. The application's notifications + * will be hidden, any of its started activities will be stopped and it will not be able to + * show toasts or dialogs or ring the device. When the user tries to launch a suspended app, a + * system dialog with the given {@code dialogMessage} will be shown instead.</p> * * <p>The package must already be installed. If the package is uninstalled while suspended - * the package will no longer be suspended. + * the package will no longer be suspended. </p> + * + * <p>Optionally, the suspending app can provide extra information in the form of + * {@link PersistableBundle} objects to be shared with the apps being suspended and the + * launcher to support customization that they might need to handle the suspended state. </p> + * + * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} or + * {@link Manifest.permission#MANAGE_USERS} to use this api.</p> * * @param packageNames The names of the packages to set the suspended status. * @param suspended If set to {@code true} than the packages will be suspended, if set to - * {@code false} the packages will be unsuspended. - * @param userId The user id. + * {@code false}, the packages will be unsuspended. + * @param appExtras An optional {@link PersistableBundle} that the suspending app can provide + * which will be shared with the apps being suspended. Ignored if + * {@code suspended} is false. + * @param launcherExtras An optional {@link PersistableBundle} that the suspending app can + * provide which will be shared with the launcher. Ignored if + * {@code suspended} is false. + * @param dialogMessage The message to be displayed to the user, when they try to launch a + * suspended app. * * @return an array of package names for which the suspended status is not set as requested in * this method. * * @hide */ - public abstract String[] setPackagesSuspendedAsUser( - String[] packageNames, boolean suspended, @UserIdInt int userId); + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.SUSPEND_APPS, + Manifest.permission.MANAGE_USERS}) + public String[] setPackagesSuspended(String[] packageNames, boolean suspended, + @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, + String dialogMessage) { + throw new UnsupportedOperationException("setPackagesSuspended not implemented"); + } /** - * @see #setPackageSuspendedAsUser(String, boolean, int) + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) * @param packageName The name of the package to get the suspended status of. * @param userId The user id. * @return {@code true} if the package is suspended or {@code false} if the package is not @@ -5505,6 +5564,103 @@ public abstract class PackageManager { public abstract boolean isPackageSuspendedForUser(String packageName, int userId); /** + * Query if an app is currently suspended. + * + * @return {@code true} if the given package is suspended, {@code false} otherwise + * + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) + * @hide + */ + @SystemApi + public boolean isPackageSuspended(String packageName) { + throw new UnsupportedOperationException("isPackageSuspended not implemented"); + } + + /** + * Apps can query this to know if they have been suspended. A system app with the permission + * {@code android.permission.SUSPEND_APPS} can put any app on the device into a suspended state. + * + * <p>While in this state, the application's notifications will be hidden, any of its started + * activities will be stopped and it will not be able to show toasts or dialogs or ring the + * device. When the user tries to launch a suspended app, the system will, instead, show a + * dialog to the user informing them that they cannot use this app while it is suspended. + * + * <p>When an app is put into this state, the broadcast action + * {@link Intent#ACTION_MY_PACKAGE_SUSPENDED} will be delivered to any of its broadcast + * receivers that included this action in their intent-filters, <em>including manifest + * receivers.</em> Similarly, a broadcast action {@link Intent#ACTION_MY_PACKAGE_UNSUSPENDED} + * is delivered when a previously suspended app is taken out of this state. + * </p> + * + * @return {@code true} if the calling package has been suspended, {@code false} otherwise. + * + * @see #getSuspendedPackageAppExtras() + * @see Intent#ACTION_MY_PACKAGE_SUSPENDED + * @see Intent#ACTION_MY_PACKAGE_UNSUSPENDED + */ + public boolean isPackageSuspended() { + throw new UnsupportedOperationException("isPackageSuspended not implemented"); + } + + /** + * Retrieve the {@link PersistableBundle} that was passed as {@code appExtras} when the given + * package was suspended. + * + * <p> The caller must hold permission {@link Manifest.permission#SUSPEND_APPS} to use this + * api.</p> + * + * @param packageName The package to retrieve extras for. + * @return The {@code appExtras} for the suspended package. + * + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SUSPEND_APPS) + public @Nullable PersistableBundle getSuspendedPackageAppExtras(String packageName) { + throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented"); + } + + /** + * Set the app extras for a suspended package. This method can be used to update the appExtras + * for a package that was earlier suspended using + * {@link #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, + * String)} + * Does nothing if the given package is not already in a suspended state. + * + * @param packageName The package for which the appExtras need to be updated + * @param appExtras The new appExtras for the given package + * + * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SUSPEND_APPS) + public void setSuspendedPackageAppExtras(String packageName, + @Nullable PersistableBundle appExtras) { + throw new UnsupportedOperationException("setSuspendedPackageAppExtras not implemented"); + } + + /** + * Returns any extra information supplied as {@code appExtras} to the system when the calling + * app was suspended. + * + * <p>Note: If no extras were supplied to the system, this method will return {@code null}, even + * when the calling app has been suspended.</p> + * + * @return A {@link Bundle} containing the extras for the app, or {@code null} if the + * package is not currently suspended. + * + * @see #isPackageSuspended() + * @see Intent#ACTION_MY_PACKAGE_UNSUSPENDED + * @see Intent#ACTION_MY_PACKAGE_SUSPENDED + */ + public @Nullable Bundle getSuspendedPackageAppExtras() { + throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented"); + } + + /** * Provide a hint of what the {@link ApplicationInfo#category} value should * be for the given package. * <p> @@ -5992,4 +6148,26 @@ public abstract class PackageManager { throw new UnsupportedOperationException( "hasSigningCertificate not implemented in subclass"); } + + /** + * @return the system defined text classifier package name, or null if there's none. + * + * @hide + */ + public String getSystemTextClassifierPackageName() { + throw new UnsupportedOperationException( + "getSystemTextClassifierPackageName not implemented in subclass"); + } + + /** + * @return whether a given package's state is protected, e.g. package cannot be disabled, + * suspended, hidden or force stopped. + * + * @hide + */ + public boolean isPackageStateProtected(String packageName, int userId) { + throw new UnsupportedOperationException( + "isPackageStateProtected not implemented in subclass"); + } + } diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java index 6f093ba8..c9b78c08 100644 --- a/android/content/pm/PackageManagerInternal.java +++ b/android/content/pm/PackageManagerInternal.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager.ComponentInfoFlags; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.ResolveInfoFlags; import android.os.Bundle; +import android.os.PersistableBundle; import android.util.SparseArray; import java.lang.annotation.Retention; @@ -43,12 +44,14 @@ public abstract class PackageManagerInternal { public static final int PACKAGE_INSTALLER = 2; public static final int PACKAGE_VERIFIER = 3; public static final int PACKAGE_BROWSER = 4; + public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5; @IntDef(value = { PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, PACKAGE_INSTALLER, PACKAGE_VERIFIER, PACKAGE_BROWSER, + PACKAGE_SYSTEM_TEXT_CLASSIFIER, }) @Retention(RetentionPolicy.SOURCE) public @interface KnownPackage {} @@ -186,6 +189,22 @@ public abstract class PackageManagerInternal { @PackageInfoFlags int flags, int filterCallingUid, int userId); /** + * Retrieve launcher extras for a suspended package provided to the system in + * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, String)} + * + * @param packageName The package for which to return launcher extras. + * @param userId The user for which to check, + * @return The launcher extras. + * + * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, String) + * @see PackageManager#isPackageSuspended() + */ + public abstract Bundle getSuspendedPackageLauncherExtras(String packageName, + int userId); + + /** * Do a straight uid lookup for the given package/application in the given user. * @see PackageManager#getPackageUidAsUser(String, int, int) * @return The app's uid, or < 0 if the package was not found in that user @@ -234,6 +253,11 @@ public abstract class PackageManagerInternal { int userId); /** + * @return The default home activity component name. + */ + public abstract ComponentName getDefaultHomeActivity(int userId); + + /** * Called by DeviceOwnerManagerService to set the package names of device owner and profile * owners. */ @@ -444,6 +468,9 @@ public abstract class PackageManagerInternal { /** Whether the binder caller can access instant apps. */ public abstract boolean canAccessInstantApps(int callingUid, int userId); + /** Whether the binder caller can access the given component. */ + public abstract boolean canAccessComponent(int callingUid, ComponentName component, int userId); + /** * Returns {@code true} if a given package has instant application meta-data. * Otherwise, returns {@code false}. Meta-data is state (eg. cookie, app icon, etc) @@ -537,4 +564,18 @@ public abstract class PackageManagerInternal { /** Updates the flags for the given permission. */ public abstract void updatePermissionFlagsTEMP(@NonNull String permName, @NonNull String packageName, int flagMask, int flagValues, int userId); + + /** + * Returns true if it's still safe to restore data backed up from this app's version + * that was signed with restoringFromSigHash. + */ + public abstract boolean isDataRestoreSafe(@NonNull byte[] restoringFromSigHash, + @NonNull String packageName); + + /** + * Returns true if it's still safe to restore data backed up from this app's version + * that was signed with restoringFromSig. + */ + public abstract boolean isDataRestoreSafe(@NonNull Signature restoringFromSig, + @NonNull String packageName); } diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java index 24e3dfa9..2f0faf25 100644 --- a/android/content/pm/PackageParser.java +++ b/android/content/pm/PackageParser.java @@ -54,6 +54,7 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -78,8 +79,10 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; +import android.util.ByteStringUtils; import android.util.DisplayMetrics; import android.util.Log; +import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -194,10 +197,6 @@ public class PackageParser { private static final String TAG_RESTRICT_UPDATE = "restrict-update"; private static final String TAG_USES_SPLIT = "uses-split"; - // [b/36551762] STOPSHIP remove the ability to expose components via meta-data - // Temporary workaround; allow meta-data to expose components to instant apps - private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed"; - private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; /** @@ -678,6 +677,7 @@ public class PackageParser { pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; pi.overlayTarget = p.mOverlayTarget; + pi.overlayCategory = p.mOverlayCategory; pi.overlayPriority = p.mOverlayPriority; pi.mOverlayIsStatic = p.mOverlayIsStatic; pi.compileSdkVersion = p.mCompileSdkVersion; @@ -1288,7 +1288,6 @@ public class PackageParser { */ @Deprecated public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { - final AssetManager assets = newConfiguredAssetManager(); final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps) { if (!lite.coreApp) { @@ -1297,8 +1296,9 @@ public class PackageParser { } } + final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { - final Package pkg = parseBaseApk(apkFile, assets, flags); + final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags); pkg.setCodePath(apkFile.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; @@ -1306,26 +1306,8 @@ public class PackageParser { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to get path: " + apkFile, e); } finally { - IoUtils.closeQuietly(assets); - } - } - - private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParserException { - if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); - } - - // The AssetManager guarantees uniqueness for asset paths, so if this asset path - // already exists in the AssetManager, addAssetPath will only return the cookie - // assigned to it. - int cookie = assets.addAssetPath(apkPath); - if (cookie == 0) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + IoUtils.closeQuietly(assetLoader); } - return cookie; } private Package parseBaseApk(File apkFile, AssetManager assets, int flags) @@ -1343,13 +1325,15 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - - Resources res = null; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); @@ -1384,15 +1368,18 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - final Resources res; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + // This must always succeed, as the path has been added to the AssetManager before. + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } + parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); @@ -1496,9 +1483,9 @@ public class PackageParser { * populating {@link Package#mSigningDetails}. Also asserts that all APK * contents are signed correctly and consistently. */ - public static void collectCertificates(Package pkg, @ParseFlags int parseFlags) + public static void collectCertificates(Package pkg, boolean skipVerify) throws PackageParserException { - collectCertificatesInternal(pkg, parseFlags); + collectCertificatesInternal(pkg, skipVerify); final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { Package childPkg = pkg.childPackages.get(i); @@ -1506,17 +1493,17 @@ public class PackageParser { } } - private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags) + private static void collectCertificatesInternal(Package pkg, boolean skipVerify) throws PackageParserException { pkg.mSigningDetails = SigningDetails.UNKNOWN; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags); + collectCertificates(pkg, new File(pkg.baseCodePath), skipVerify); if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { for (int i = 0; i < pkg.splitCodePaths.length; i++) { - collectCertificates(pkg, new File(pkg.splitCodePaths[i]), parseFlags); + collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify); } } } finally { @@ -1524,7 +1511,7 @@ public class PackageParser { } } - private static void collectCertificates(Package pkg, File apkFile, @ParseFlags int parseFlags) + private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); @@ -1534,7 +1521,7 @@ public class PackageParser { minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; } SigningDetails verified; - if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) { + if (skipVerify) { // systemDir APKs are already trusted, save time by not verifying verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts( apkPath, minSignatureScheme); @@ -1594,29 +1581,28 @@ public class PackageParser { int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); - AssetManager assets = null; XmlResourceParser parser = null; try { - assets = newConfiguredAssetManager(); - int cookie = fd != null - ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath); - if (cookie == 0) { + final ApkAssets apkAssets; + try { + apkAssets = fd != null + ? ApkAssets.loadFromFd(fd, debugPathName, false, false) + : ApkAssets.loadFromPath(apkPath); + } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath); } - final DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - - parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object final Package tempPkg = new Package((String) null); + final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - collectCertificates(tempPkg, apkFile, flags); + collectCertificates(tempPkg, apkFile, skipVerify); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -1634,7 +1620,7 @@ public class PackageParser { "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); - IoUtils.closeQuietly(assets); + // TODO(b/72056911): Implement and call close() on ApkAssets. } } @@ -2074,6 +2060,8 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestResourceOverlay); pkg.mOverlayTarget = sa.getString( com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage); + pkg.mOverlayCategory = sa.getString( + com.android.internal.R.styleable.AndroidManifestResourceOverlay_category); pkg.mOverlayPriority = sa.getInt( com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority, 0); @@ -3183,12 +3171,12 @@ public class PackageParser { perm.info.protectionLevel = PermissionInfo.fixProtectionLevel(perm.info.protectionLevel); - if ((perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_FLAGS) != 0) { + if (perm.info.getProtectionFlags() != 0) { if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_INSTANT) == 0 && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0 && (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) != PermissionInfo.PROTECTION_SIGNATURE) { - outError[0] = "<permission> protectionLevel specifies a non-instnat flag but is " + outError[0] = "<permission> protectionLevel specifies a non-instant flag but is " + "not based on signature type"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; @@ -3504,7 +3492,7 @@ public class PackageParser { if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_usesCleartextTraffic, - true)) { + owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P)) { ai.flags |= ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC; } @@ -3639,7 +3627,9 @@ public class PackageParser { // getting added to the wrong package. final CachedComponentArgs cachedArgs = new CachedComponentArgs(); int type; - + boolean hasActivityOrder = false; + boolean hasReceiverOrder = false; + boolean hasServiceOrder = false; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -3655,6 +3645,7 @@ public class PackageParser { return false; } + hasActivityOrder |= (a.order != 0); owner.activities.add(a); } else if (tagName.equals("receiver")) { @@ -3665,6 +3656,7 @@ public class PackageParser { return false; } + hasReceiverOrder |= (a.order != 0); owner.receivers.add(a); } else if (tagName.equals("service")) { @@ -3674,6 +3666,7 @@ public class PackageParser { return false; } + hasServiceOrder |= (s.order != 0); owner.services.add(s); } else if (tagName.equals("provider")) { @@ -3692,6 +3685,7 @@ public class PackageParser { return false; } + hasActivityOrder |= (a.order != 0); owner.activities.add(a); } else if (parser.getName().equals("meta-data")) { @@ -3825,6 +3819,15 @@ public class PackageParser { } } + if (hasActivityOrder) { + Collections.sort(owner.activities, (a1, a2) -> Integer.compare(a2.order, a1.order)); + } + if (hasReceiverOrder) { + Collections.sort(owner.receivers, (r1, r2) -> Integer.compare(r2.order, r1.order)); + } + if (hasServiceOrder) { + Collections.sort(owner.services, (s1, s2) -> Integer.compare(s2.order, s1.order)); + } // Must be ran after the entire {@link ApplicationInfo} has been fully processed and after // every activity info has had a chance to set it from its attributes. setMaxAspectRatio(owner); @@ -4366,6 +4369,7 @@ public class PackageParser { + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { + a.order = Math.max(intent.getOrder(), a.order); a.intents.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter @@ -4427,24 +4431,6 @@ public class PackageParser { outError)) == null) { return null; } - // we don't have an attribute [or it's false], but, we have meta-data - if (!visibleToEphemeral && a.metaData.getBoolean(META_DATA_INSTANT_APPS)) { - visibleToEphemeral = true; // set in case there are more intent filters - a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; - a.info.flags &= ~ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP; - owner.visibleToInstantApps = true; - // cycle through any filters already seen - for (int i = a.intents.size() - 1; i >= 0; --i) { - a.intents.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - if (owner.preferredActivityFilters != null) { - for (int i = owner.preferredActivityFilters.size() - 1; i >= 0; --i) { - owner.preferredActivityFilters.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - } - } } else if (!receiver && parser.getName().equals("layout")) { parseLayout(res, parser, a); } else { @@ -4694,6 +4680,7 @@ public class PackageParser { info.windowLayout = target.info.windowLayout; info.resizeMode = target.info.resizeMode; info.maxAspectRatio = target.info.maxAspectRatio; + info.requestedVrComponent = target.info.requestedVrComponent; info.encryptionAware = info.directBootAware = target.info.directBootAware; @@ -4761,6 +4748,7 @@ public class PackageParser { + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { + a.order = Math.max(intent.getOrder(), a.order); a.intents.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter @@ -4939,7 +4927,7 @@ public class PackageParser { p.info.authority = cpname.intern(); if (!parseProviderTags( - res, parser, visibleToEphemeral, owner, p, outError)) { + res, parser, visibleToEphemeral, p, outError)) { return null; } @@ -4947,7 +4935,7 @@ public class PackageParser { } private boolean parseProviderTags(Resources res, XmlResourceParser parser, - boolean visibleToEphemeral, Package owner, Provider outInfo, String[] outError) + boolean visibleToEphemeral, Provider outInfo, String[] outError) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); int type; @@ -4968,6 +4956,7 @@ public class PackageParser { intent.setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; } + outInfo.order = Math.max(intent.getOrder(), outInfo.order); outInfo.intents.add(intent); } else if (parser.getName().equals("meta-data")) { @@ -4975,17 +4964,6 @@ public class PackageParser { outInfo.metaData, outError)) == null) { return false; } - // we don't have an attribute [or it's false], but, we have meta-data - if (!visibleToEphemeral && outInfo.metaData.getBoolean(META_DATA_INSTANT_APPS)) { - visibleToEphemeral = true; // set in case there are more intent filters - outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; - owner.visibleToInstantApps = true; - // cycle through any filters already seen - for (int i = outInfo.intents.size() - 1; i >= 0; --i) { - outInfo.intents.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - } } else if (parser.getName().equals("grant-uri-permission")) { TypedArray sa = res.obtainAttributes(parser, @@ -5268,23 +5246,13 @@ public class PackageParser { intent.setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP; } + s.order = Math.max(intent.getOrder(), s.order); s.intents.add(intent); } else if (parser.getName().equals("meta-data")) { if ((s.metaData=parseMetaData(res, parser, s.metaData, outError)) == null) { return null; } - // we don't have an attribute [or it's false], but, we have meta-data - if (!visibleToEphemeral && s.metaData.getBoolean(META_DATA_INSTANT_APPS)) { - visibleToEphemeral = true; // set in case there are more intent filters - s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP; - owner.visibleToInstantApps = true; - // cycle through any filters already seen - for (int i = s.intents.size() - 1; i >= 0; --i) { - s.intents.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - } } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under <service>: " @@ -5504,6 +5472,10 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0); outInfo.setPriority(priority); + int order = sa.getInt( + com.android.internal.R.styleable.AndroidManifestIntentFilter_order, 0); + outInfo.setOrder(order); + TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestIntentFilter_label); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { @@ -5682,7 +5654,10 @@ public class PackageParser { return true; } - /** A container for signing-related data of an application package. */ + /** + * A container for signing-related data of an application package. + * @hide + */ public static final class SigningDetails implements Parcelable { @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN, @@ -5704,15 +5679,58 @@ public class PackageParser { public final ArraySet<PublicKey> publicKeys; /** - * Collection of {@code Signature} objects, each of which is formed from a former signing - * certificate of this APK before it was changed by signing certificate rotation. + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * This collection of {@code Signature} objects, each of which is formed from a former + * signing certificate of this APK before it was changed by signing certificate rotation, + * represents the first piece of information. It is the APK saying to the rest of the + * world: "hey if you trust the old cert, you can trust me!" This is useful, if for + * instance, the platform would like to determine whether or not to allow this APK to do + * something it would've allowed it to do under the old cert (like upgrade). */ @Nullable public final Signature[] pastSigningCertificates; + /** special value used to see if cert is in package - not exposed to callers */ + private static final int PAST_CERT_EXISTS = 0; + + @IntDef( + flag = true, + value = {CertCapabilities.INSTALLED_DATA, + CertCapabilities.SHARED_USER_ID, + CertCapabilities.PERMISSION, + CertCapabilities.ROLLBACK}) + public @interface CertCapabilities { + + /** accept data from already installed pkg with this cert */ + int INSTALLED_DATA = 1; + + /** accept sharedUserId with pkg with this cert */ + int SHARED_USER_ID = 2; + + /** grant SIGNATURE permissions to pkgs with this cert */ + int PERMISSION = 4; + + /** allow pkg to update to one signed by this certificate */ + int ROLLBACK = 8; + } + /** - * Flags for the {@code pastSigningCertificates} collection, which indicate the capabilities - * the including APK wishes to grant to its past signing certificates. + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * These flags, which have a one-to-one relationship for the {@code pastSigningCertificates} + * collection, represent the second piece of information and are viewed as capabilities. + * They are an APK's way of telling the platform: "this is how I want to trust my old certs, + * please enforce that." This is useful for situation where this app itself is using its + * signing certificate as an authorization mechanism, like whether or not to allow another + * app to have its SIGNATURE permission. An app could specify whether to allow other apps + * signed by its old cert 'X' to still get a signature permission it defines, for example. */ @Nullable public final int[] pastSigningCertificatesFlags; @@ -5783,6 +5801,244 @@ public class PackageParser { return pastSigningCertificates != null && pastSigningCertificates.length > 0; } + /** + * Determines if the provided {@code oldDetails} is an ancestor of or the same as this one. + * If the {@code oldDetails} signing certificate appears in our pastSigningCertificates, + * then that means it has authorized a signing certificate rotation, which eventually leads + * to our certificate, and thus can be trusted. If this method evaluates to true, this + * SigningDetails object should be trusted if the previous one is. + */ + public boolean hasAncestorOrSelf(SigningDetails oldDetails) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (oldDetails.signatures.length > 1) { + + // multiple-signer packages cannot rotate signing certs, so we just compare current + // signers for an exact match + return signaturesMatchExactly(oldDetails); + } else { + + // we may have signing certificate rotation history, check to see if the oldDetails + // was one of our old signing certificates + return hasCertificate(oldDetails.signatures[0]); + } + } + + /** + * Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails} + * is a descendant of {@code oldDetails}, not if they're the same. This is used to + * determine if this object is newer than the provided one. + */ + public boolean hasAncestor(SigningDetails oldDetails) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (this.hasPastSigningCertificates() && oldDetails.signatures.length == 1) { + + // the last entry in pastSigningCertificates is the current signer, ignore it + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + if (pastSigningCertificates[i].equals(oldDetails.signatures[i])) { + return true; + } + } + } + return false; + } + + /** + * Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or + * not this one grants it the provided capability, represented by the {@code flags} + * parameter. In the event of signing certificate rotation, a package may still interact + * with entities signed by its old signing certificate and not want to break previously + * functioning behavior. The {@code flags} value determines which capabilities the app + * signed by the newer signing certificate would like to continue to give to its previous + * signing certificate(s). + */ + public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (oldDetails.signatures.length > 1) { + + // multiple-signer packages cannot rotate signing certs, so we must have an exact + // match, which also means all capabilities are granted + return signaturesMatchExactly(oldDetails); + } else { + + // we may have signing certificate rotation history, check to see if the oldDetails + // was one of our old signing certificates, and if we grant it the capability it's + // requesting + return hasCertificate(oldDetails.signatures[0], flags); + } + } + + /** + * A special case of {@code checkCapability} which re-encodes both sets of signing + * certificates to counteract a previous re-encoding. + */ + public boolean checkCapabilityRecover(SigningDetails oldDetails, + @CertCapabilities int flags) throws CertificateException { + if (oldDetails == UNKNOWN || this == UNKNOWN) { + return false; + } + if (hasPastSigningCertificates() && oldDetails.signatures.length == 1) { + + // signing certificates may have rotated, check entire history for effective match + for (int i = 0; i < pastSigningCertificates.length; i++) { + if (Signature.areEffectiveMatch( + oldDetails.signatures[0], + pastSigningCertificates[i]) + && pastSigningCertificatesFlags[i] == flags) { + return true; + } + } + } else { + return Signature.areEffectiveMatch(oldDetails.signatures, signatures); + } + return false; + } + + /** + * Determine if {@code signature} is in this SigningDetails' signing certificate history, + * including the current signer. Automatically returns false if this object has multiple + * signing certificates, since rotation is only supported for single-signers; this is + * enforced by {@code hasCertificateInternal}. + */ + public boolean hasCertificate(Signature signature) { + return hasCertificateInternal(signature, PAST_CERT_EXISTS); + } + + /** + * Determine if {@code signature} is in this SigningDetails' signing certificate history, + * including the current signer, and whether or not it has the given permission. + * Certificates which match our current signer automatically get all capabilities. + * Automatically returns false if this object has multiple signing certificates, since + * rotation is only supported for single-signers. + */ + public boolean hasCertificate(Signature signature, @CertCapabilities int flags) { + return hasCertificateInternal(signature, flags); + } + + /** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */ + public boolean hasCertificate(byte[] certificate) { + Signature signature = new Signature(certificate); + return hasCertificate(signature); + } + + private boolean hasCertificateInternal(Signature signature, int flags) { + if (this == UNKNOWN) { + return false; + } + + // only single-signed apps can have pastSigningCertificates + if (hasPastSigningCertificates()) { + + // check all past certs, except for the current one, which automatically gets all + // capabilities, since it is the same as the current signature + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + if (pastSigningCertificates[i].equals(signature)) { + if (flags == PAST_CERT_EXISTS + || (flags & pastSigningCertificatesFlags[i]) == flags) { + return true; + } + } + } + } + + // not in previous certs signing history, just check the current signer and make sure + // we are singly-signed + return signatures.length == 1 && signatures[0].equals(signature); + } + + /** + * Determines if the provided {@code sha256String} is an ancestor of this one, and whether + * or not this one grants it the provided capability, represented by the {@code flags} + * parameter. In the event of signing certificate rotation, a package may still interact + * with entities signed by its old signing certificate and not want to break previously + * functioning behavior. The {@code flags} value determines which capabilities the app + * signed by the newer signing certificate would like to continue to give to its previous + * signing certificate(s). + * + * @param sha256String A hex-encoded representation of a sha256 digest. In the case of an + * app with multiple signers, this represents the hex-encoded sha256 + * digest of the combined hex-encoded sha256 digests of each individual + * signing certificate according to {@link + * PackageUtils#computeSignaturesSha256Digest(Signature[])} + */ + public boolean checkCapability(String sha256String, @CertCapabilities int flags) { + if (this == UNKNOWN) { + return false; + } + + // first see if the hash represents a single-signer in our signing history + byte[] sha256Bytes = ByteStringUtils.fromHexToByteArray(sha256String); + if (hasSha256Certificate(sha256Bytes, flags)) { + return true; + } + + // Not in signing history, either represents multiple signatures or not a match. + // Multiple signers can't rotate, so no need to check flags, just see if the SHAs match. + // We already check the single-signer case above as part of hasSha256Certificate, so no + // need to verify we have multiple signers, just run the old check + // just consider current signing certs + final String[] mSignaturesSha256Digests = + PackageUtils.computeSignaturesSha256Digests(signatures); + final String mSignaturesSha256Digest = + PackageUtils.computeSignaturesSha256Digest(mSignaturesSha256Digests); + return mSignaturesSha256Digest.equals(sha256String); + } + + /** + * Determine if the {@code sha256Certificate} is in this SigningDetails' signing certificate + * history, including the current signer. Automatically returns false if this object has + * multiple signing certificates, since rotation is only supported for single-signers. + */ + public boolean hasSha256Certificate(byte[] sha256Certificate) { + return hasSha256CertificateInternal(sha256Certificate, PAST_CERT_EXISTS); + } + + /** + * Determine if the {@code sha256Certificate} certificate hash corresponds to a signing + * certificate in this SigningDetails' signing certificate history, including the current + * signer, and whether or not it has the given permission. Certificates which match our + * current signer automatically get all capabilities. Automatically returns false if this + * object has multiple signing certificates, since rotation is only supported for + * single-signers. + */ + public boolean hasSha256Certificate(byte[] sha256Certificate, @CertCapabilities int flags) { + return hasSha256CertificateInternal(sha256Certificate, flags); + } + + private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags) { + if (this == UNKNOWN) { + return false; + } + if (hasPastSigningCertificates()) { + + // check all past certs, except for the last one, which automatically gets all + // capabilities, since it is the same as the current signature, and is checked below + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + byte[] digest = PackageUtils.computeSha256DigestBytes( + pastSigningCertificates[i].toByteArray()); + if (Arrays.equals(sha256Certificate, digest)) { + if (flags == PAST_CERT_EXISTS + || (flags & pastSigningCertificatesFlags[i]) == flags) { + return true; + } + } + } + } + + // not in previous certs signing history, just check the current signer + if (signatures.length == 1) { + byte[] digest = + PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray()); + return Arrays.equals(sha256Certificate, digest); + } + return false; + } + /** Returns true if the signatures in this and other match exactly. */ public boolean signaturesMatchExactly(SigningDetails other) { return Signature.areExactMatch(this.signatures, other.signatures); @@ -6085,6 +6341,7 @@ public class PackageParser { public String mRequiredAccountType; public String mOverlayTarget; + public String mOverlayCategory; public int mOverlayPriority; public boolean mOverlayIsStatic; @@ -6393,6 +6650,11 @@ public class PackageParser { } /** @hide */ + public boolean isProduct() { + return applicationInfo.isProduct(); + } + + /** @hide */ public boolean isPrivileged() { return applicationInfo.isPrivilegedApp(); } @@ -6450,31 +6712,6 @@ public class PackageParser { + " " + packageName + "}"; } - public String dumpState_temp() { - String flags = ""; - flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : ""); - flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : ""); - if ("".equals(flags)) { - flags = "-"; - } - String privFlags = ""; - privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : ""); - privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : ""); - privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : ""); - if ("".equals(privFlags)) { - privFlags = "-"; - } - return "Package{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + packageName - + ", ver:" + getLongVersionCode() - + ", path: " + codePath - + ", flags: " + flags - + ", privFlags: " + privFlags - + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}") - + "}"; - } - @Override public int describeContents() { return 0; @@ -6615,6 +6852,7 @@ public class PackageParser { mRestrictedAccountType = dest.readString(); mRequiredAccountType = dest.readString(); mOverlayTarget = dest.readString(); + mOverlayCategory = dest.readString(); mOverlayPriority = dest.readInt(); mOverlayIsStatic = (dest.readInt() == 1); mCompileSdkVersion = dest.readInt(); @@ -6738,6 +6976,7 @@ public class PackageParser { dest.writeString(mRestrictedAccountType); dest.writeString(mRequiredAccountType); dest.writeString(mOverlayTarget); + dest.writeString(mOverlayCategory); dest.writeInt(mOverlayPriority); dest.writeInt(mOverlayIsStatic ? 1 : 0); dest.writeInt(mCompileSdkVersion); @@ -6828,6 +7067,8 @@ public class PackageParser { public Bundle metaData; public Package owner; + /** The order of this component in relation to its peers */ + public int order; ComponentName componentName; String componentShortName; @@ -7346,6 +7587,7 @@ public class PackageParser { for (ActivityIntentInfo aii : intents) { aii.activity = this; + order = Math.max(aii.getOrder(), order); } if (info.permission != null) { @@ -7435,6 +7677,7 @@ public class PackageParser { for (ServiceIntentInfo aii : intents) { aii.service = this; + order = Math.max(aii.getOrder(), order); } if (info.permission != null) { diff --git a/android/content/pm/PackageSharedLibraryUpdater.java b/android/content/pm/PackageSharedLibraryUpdater.java index 49d884ca..fa894320 100644 --- a/android/content/pm/PackageSharedLibraryUpdater.java +++ b/android/content/pm/PackageSharedLibraryUpdater.java @@ -17,6 +17,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Build; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -38,6 +39,12 @@ public abstract class PackageSharedLibraryUpdater { */ public abstract void updatePackage(PackageParser.Package pkg); + static void removeLibrary(PackageParser.Package pkg, String libraryName) { + pkg.usesLibraries = ArrayUtils.remove(pkg.usesLibraries, libraryName); + pkg.usesOptionalLibraries = + ArrayUtils.remove(pkg.usesOptionalLibraries, libraryName); + } + static @NonNull <T> ArrayList<T> prefix(@Nullable ArrayList<T> cur, T val) { if (cur == null) { @@ -47,9 +54,54 @@ public abstract class PackageSharedLibraryUpdater { return cur; } - static boolean isLibraryPresent(ArrayList<String> usesLibraries, + private static boolean isLibraryPresent(ArrayList<String> usesLibraries, ArrayList<String> usesOptionalLibraries, String apacheHttpLegacy) { return ArrayUtils.contains(usesLibraries, apacheHttpLegacy) || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy); } + + static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(PackageParser.Package pkg) { + int targetSdkVersion = pkg.applicationInfo.targetSdkVersion; + return targetSdkVersion <= Build.VERSION_CODES.O_MR1; + } + + /** + * Add an implicit dependency. + * + * <p>If the package has an existing dependency on {@code existingLibrary} then prefix it with + * the {@code implicitDependency} if it is not already in the list of libraries. + * + * @param pkg the {@link PackageParser.Package} to update. + * @param existingLibrary the existing library. + * @param implicitDependency the implicit dependency to add + */ + void prefixImplicitDependency(PackageParser.Package pkg, String existingLibrary, + String implicitDependency) { + ArrayList<String> usesLibraries = pkg.usesLibraries; + ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; + + if (!isLibraryPresent(usesLibraries, usesOptionalLibraries, implicitDependency)) { + if (ArrayUtils.contains(usesLibraries, existingLibrary)) { + prefix(usesLibraries, implicitDependency); + } else if (ArrayUtils.contains(usesOptionalLibraries, existingLibrary)) { + prefix(usesOptionalLibraries, implicitDependency); + } + + pkg.usesLibraries = usesLibraries; + pkg.usesOptionalLibraries = usesOptionalLibraries; + } + } + + void prefixRequiredLibrary(PackageParser.Package pkg, String libraryName) { + ArrayList<String> usesLibraries = pkg.usesLibraries; + ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; + + boolean alreadyPresent = isLibraryPresent( + usesLibraries, usesOptionalLibraries, libraryName); + if (!alreadyPresent) { + usesLibraries = prefix(usesLibraries, libraryName); + + pkg.usesLibraries = usesLibraries; + } + } } diff --git a/android/content/pm/PackageUserState.java b/android/content/pm/PackageUserState.java index 293beb2b..f7b6e091 100644 --- a/android/content/pm/PackageUserState.java +++ b/android/content/pm/PackageUserState.java @@ -27,6 +27,8 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import android.os.BaseBundle; +import android.os.PersistableBundle; import android.util.ArraySet; import com.android.internal.util.ArrayUtils; @@ -44,6 +46,9 @@ public class PackageUserState { public boolean notLaunched; public boolean hidden; // Is the app restricted by owner / admin public boolean suspended; + public String suspendingPackage; + public PersistableBundle suspendedAppExtras; + public PersistableBundle suspendedLauncherExtras; public boolean instantApp; public boolean virtualPreload; public int enabled; @@ -76,6 +81,9 @@ public class PackageUserState { notLaunched = o.notLaunched; hidden = o.hidden; suspended = o.suspended; + suspendingPackage = o.suspendingPackage; + suspendedAppExtras = o.suspendedAppExtras; + suspendedLauncherExtras = o.suspendedLauncherExtras; instantApp = o.instantApp; virtualPreload = o.virtualPreload; enabled = o.enabled; @@ -195,6 +203,20 @@ public class PackageUserState { if (suspended != oldState.suspended) { return false; } + if (suspended) { + if (suspendingPackage == null + || !suspendingPackage.equals(oldState.suspendingPackage)) { + return false; + } + if (!BaseBundle.kindofEquals(suspendedAppExtras, + oldState.suspendedAppExtras)) { + return false; + } + if (!BaseBundle.kindofEquals(suspendedLauncherExtras, + oldState.suspendedLauncherExtras)) { + return false; + } + } if (instantApp != oldState.instantApp) { return false; } diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java index 21bd7f0c..938409af 100644 --- a/android/content/pm/PermissionInfo.java +++ b/android/content/pm/PermissionInfo.java @@ -16,12 +16,16 @@ package android.content.pm; +import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information you can retrieve about a particular security permission * known to the system. This corresponds to information collected from the @@ -56,6 +60,16 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { @Deprecated public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3; + /** @hide */ + @IntDef(flag = false, prefix = { "PROTECTION_" }, value = { + PROTECTION_NORMAL, + PROTECTION_DANGEROUS, + PROTECTION_SIGNATURE, + PROTECTION_SIGNATURE_OR_SYSTEM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Protection {} + /** * Additional flag for {@link #protectionLevel}, corresponding * to the <code>privileged</code> value of @@ -155,30 +169,71 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000; /** + * Additional flag for {@link #protectionLevel}, corresponding + * to the <code>text_classifier</code> value of + * {@link android.R.attr#protectionLevel}. + * + * @hide + */ + @SystemApi + @TestApi + public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 0x10000; + + /** @hide */ + @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = { + PROTECTION_FLAG_PRIVILEGED, + PROTECTION_FLAG_SYSTEM, + PROTECTION_FLAG_DEVELOPMENT, + PROTECTION_FLAG_APPOP, + PROTECTION_FLAG_PRE23, + PROTECTION_FLAG_INSTALLER, + PROTECTION_FLAG_VERIFIER, + PROTECTION_FLAG_PREINSTALLED, + PROTECTION_FLAG_SETUP, + PROTECTION_FLAG_INSTANT, + PROTECTION_FLAG_RUNTIME_ONLY, + PROTECTION_FLAG_OEM, + PROTECTION_FLAG_VENDOR_PRIVILEGED, + PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProtectionFlags {} + + /** * Mask for {@link #protectionLevel}: the basic protection type. + * + * @deprecated Use #getProtection() instead. */ + @Deprecated public static final int PROTECTION_MASK_BASE = 0xf; /** * Mask for {@link #protectionLevel}: additional flag bits. + * + * @deprecated Use #getProtectionFlags() instead. */ + @Deprecated public static final int PROTECTION_MASK_FLAGS = 0xfff0; /** * The level of access this permission is protecting, as per * {@link android.R.attr#protectionLevel}. Consists of - * a base permission type and zero or more flags: + * a base permission type and zero or more flags. Use the following functions + * to extract them. * * <pre> - * int basePermissionType = protectionLevel & {@link #PROTECTION_MASK_BASE}; - * int permissionFlags = protectionLevel & {@link #PROTECTION_MASK_FLAGS}; + * int basePermissionType = permissionInfo.getProtection(); + * int permissionFlags = permissionInfo.getProtectionFlags(); * </pre> * * <p></p>Base permission types are {@link #PROTECTION_NORMAL}, * {@link #PROTECTION_DANGEROUS}, {@link #PROTECTION_SIGNATURE} * and the deprecated {@link #PROTECTION_SIGNATURE_OR_SYSTEM}. * Flags are listed under {@link android.R.attr#protectionLevel}. + * + * @deprecated Use #getProtection() and #getProtectionFlags() instead. */ + @Deprecated public int protectionLevel; /** @@ -304,6 +359,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0) { protLevel += "|vendorPrivileged"; } + if ((level & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0) { + protLevel += "|textClassifier"; + } return protLevel; } @@ -344,6 +402,22 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { return null; } + /** + * Return the base permission type. + */ + @Protection + public int getProtection() { + return protectionLevel & PROTECTION_MASK_BASE; + } + + /** + * Return the additional flags in {@link #protectionLevel}. + */ + @ProtectionFlags + public int getProtectionFlags() { + return protectionLevel & ~PROTECTION_MASK_BASE; + } + @Override public String toString() { return "PermissionInfo{" diff --git a/android/content/pm/ResolveInfo.java b/android/content/pm/ResolveInfo.java index 3f63d80f..fc2eba28 100644 --- a/android/content/pm/ResolveInfo.java +++ b/android/content/pm/ResolveInfo.java @@ -277,7 +277,7 @@ public class ResolveInfo implements Parcelable { dr = pm.getDrawable(ci.packageName, iconResourceId, ai); } if (dr != null) { - return pm.getUserBadgedIcon(dr, new UserHandle(UserHandle.myUserId())); + return pm.getUserBadgedIcon(dr, new UserHandle(pm.getUserId())); } return ci.loadIcon(pm); } diff --git a/android/content/pm/SharedLibraryNames.java b/android/content/pm/SharedLibraryNames.java new file mode 100644 index 00000000..83e86636 --- /dev/null +++ b/android/content/pm/SharedLibraryNames.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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.content.pm; + +/** + * A set of shared library names + * + * @hide + */ +public class SharedLibraryNames { + + static final String ANDROID_TEST_BASE = "android.test.base"; + + static final String ANDROID_TEST_MOCK = "android.test.mock"; + + static final String ANDROID_TEST_RUNNER = "android.test.runner"; + + static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy"; +} diff --git a/android/content/pm/ShortcutManager.java b/android/content/pm/ShortcutManager.java index 30222b74..25e0ccd8 100644 --- a/android/content/pm/ShortcutManager.java +++ b/android/content/pm/ShortcutManager.java @@ -770,6 +770,6 @@ public class ShortcutManager { /** @hide injection point */ @VisibleForTesting protected int injectMyUserId() { - return UserHandle.myUserId(); + return mContext.getUserId(); } } diff --git a/android/content/pm/Signature.java b/android/content/pm/Signature.java index fdc54aed..a2a14edd 100644 --- a/android/content/pm/Signature.java +++ b/android/content/pm/Signature.java @@ -285,6 +285,29 @@ public class Signature implements Parcelable { } /** + * Test if given {@link Signature} objects are effectively equal. In rare + * cases, certificates can have slightly malformed encoding which causes + * exact-byte checks to fail. + * <p> + * To identify effective equality, we bounce the certificates through an + * decode/encode pass before doing the exact-byte check. To reduce attack + * surface area, we only allow a byte size delta of a few bytes. + * + * @throws CertificateException if the before/after length differs + * substantially, usually a signal of something fishy going on. + * @hide + */ + public static boolean areEffectiveMatch(Signature a, Signature b) + throws CertificateException { + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + final Signature aPrime = bounce(cf, a); + final Signature bPrime = bounce(cf, b); + + return aPrime.equals(bPrime); + } + + /** * Bounce the given {@link Signature} through a decode/encode cycle. * * @throws CertificateException if the before/after length differs diff --git a/android/content/pm/VerifierDeviceIdentity.java b/android/content/pm/VerifierDeviceIdentity.java index a8cdb6ae..90be6f31 100644 --- a/android/content/pm/VerifierDeviceIdentity.java +++ b/android/content/pm/VerifierDeviceIdentity.java @@ -19,6 +19,8 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import java.util.Random; @@ -86,6 +88,7 @@ public class VerifierDeviceIdentity implements Parcelable { * @return verifier device identity based on the input from the provided * random number generator */ + @VisibleForTesting static VerifierDeviceIdentity generate(Random rng) { long identity = rng.nextLong(); return new VerifierDeviceIdentity(identity); diff --git a/android/content/pm/dex/ArtManager.java b/android/content/pm/dex/ArtManager.java index aa9c46e6..41293981 100644 --- a/android/content/pm/dex/ArtManager.java +++ b/android/content/pm/dex/ArtManager.java @@ -16,16 +16,22 @@ package android.content.pm.dex; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; +import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Slog; +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + /** * Class for retrieving various kinds of information related to the runtime artifacts of * packages that are currently installed on the device. @@ -43,6 +49,20 @@ public class ArtManager { /** The snapshot failed because of an internal error (e.g. error during opening profiles). */ public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2; + /** Constant used for applications profiles. */ + public static final int PROFILE_APPS = 0; + /** Constant used for the boot image profile. */ + public static final int PROFILE_BOOT_IMAGE = 1; + + /** @hide */ + @IntDef(flag = true, prefix = { "PROFILE_" }, value = { + PROFILE_APPS, + PROFILE_BOOT_IMAGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProfileType {} + + private IArtManager mArtManager; /** @@ -53,41 +73,59 @@ public class ArtManager { } /** - * Snapshots the runtime profile for an apk belonging to the package {@code packageName}. - * The apk is identified by {@code codePath}. The calling process must have - * {@code android.permission.READ_RUNTIME_PROFILE} permission. + * Snapshots a runtime profile according to the {@code profileType} parameter. + * + * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot + * the profile for for an apk belonging to the package {@code packageName}. + * The apk is identified by {@code codePath}. + * + * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot + * the profile for the boot image. In this case {@code codePath can be null}. The parameters + * {@code packageName} and {@code codePath} are ignored. + *u + * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission. * - * The result will be posted on {@code handler} using the given {@code callback}. - * The profile being available as a read-only {@link android.os.ParcelFileDescriptor}. + * The result will be posted on the {@code executor} using the given {@code callback}. + * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}. * - * @param packageName the target package name - * @param codePath the code path for which the profile should be retrieved + * This method will throw {@link IllegalStateException} if + * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given + * {@code profileType}. + * + * @param profileType the type of profile that should be snapshot (boot image or app) + * @param packageName the target package name or null if the target is the boot image + * @param codePath the code path for which the profile should be retrieved or null if + * the target is the boot image * @param callback the callback which should be used for the result - * @param handler the handler which should be used to post the result + * @param executor the executor which should be used to post the result */ @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES) - public void snapshotRuntimeProfile(@NonNull String packageName, @NonNull String codePath, - @NonNull SnapshotRuntimeProfileCallback callback, @NonNull Handler handler) { + public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName, + @Nullable String codePath, @NonNull @CallbackExecutor Executor executor, + @NonNull SnapshotRuntimeProfileCallback callback) { Slog.d(TAG, "Requesting profile snapshot for " + packageName + ":" + codePath); SnapshotRuntimeProfileCallbackDelegate delegate = - new SnapshotRuntimeProfileCallbackDelegate(callback, handler.getLooper()); + new SnapshotRuntimeProfileCallbackDelegate(callback, executor); try { - mArtManager.snapshotRuntimeProfile(packageName, codePath, delegate); + mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } } /** - * Returns true if runtime profiles are enabled, false otherwise. + * Returns true if runtime profiles are enabled for the given type, false otherwise. * * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission. + * + * @param profileType can be either {@link ArtManager#PROFILE_APPS} + * or {@link ArtManager#PROFILE_BOOT_IMAGE} */ @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES) - public boolean isRuntimeProfilingEnabled() { + public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) { try { - return mArtManager.isRuntimeProfilingEnabled(); + return mArtManager.isRuntimeProfilingEnabled(profileType); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } @@ -116,41 +154,24 @@ public class ArtManager { } private static class SnapshotRuntimeProfileCallbackDelegate - extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub - implements Handler.Callback { - private static final int MSG_SNAPSHOT_OK = 1; - private static final int MSG_ERROR = 2; + extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub { private final ArtManager.SnapshotRuntimeProfileCallback mCallback; - private final Handler mHandler; + private final Executor mExecutor; private SnapshotRuntimeProfileCallbackDelegate( - ArtManager.SnapshotRuntimeProfileCallback callback, Looper looper) { + ArtManager.SnapshotRuntimeProfileCallback callback, Executor executor) { mCallback = callback; - mHandler = new Handler(looper, this); + mExecutor = executor; } @Override - public void onSuccess(ParcelFileDescriptor profileReadFd) { - mHandler.obtainMessage(MSG_SNAPSHOT_OK, profileReadFd).sendToTarget(); + public void onSuccess(final ParcelFileDescriptor profileReadFd) { + mExecutor.execute(() -> mCallback.onSuccess(profileReadFd)); } @Override public void onError(int errCode) { - mHandler.obtainMessage(MSG_ERROR, errCode, 0).sendToTarget(); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_SNAPSHOT_OK: - mCallback.onSuccess((ParcelFileDescriptor) msg.obj); - break; - case MSG_ERROR: - mCallback.onError(msg.arg1); - break; - default: return false; - } - return true; + mExecutor.execute(() -> mCallback.onError(errCode)); } } @@ -163,4 +184,27 @@ public class ArtManager { public static String getProfileName(String splitName) { return splitName == null ? "primary.prof" : splitName + ".split.prof"; } + + /** + * Return the path to the current profile corresponding to given package and split. + * + * @hide + */ + public static String getCurrentProfilePath(String packageName, int userId, String splitName) { + File profileDir = Environment.getDataProfilesDePackageDirectory(userId, packageName); + return new File(profileDir, getProfileName(splitName)).getAbsolutePath(); + } + + /** + * Return the snapshot profile file for the given package and profile name. + * + * KEEP in sync with installd dexopt.cpp. + * TODO(calin): inject the snapshot profile name from PM to avoid the dependency. + * + * @hide + */ + public static File getProfileSnapshotFileForName(String packageName, String profileName) { + File profileDir = Environment.getDataRefProfilesDePackageDirectory(packageName); + return new File(profileDir, profileName + ".snapshot"); + } } diff --git a/android/content/pm/dex/ArtManagerInternal.java b/android/content/pm/dex/ArtManagerInternal.java new file mode 100644 index 00000000..62ab9e02 --- /dev/null +++ b/android/content/pm/dex/ArtManagerInternal.java @@ -0,0 +1,34 @@ +/** + * Copyright 2018 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.content.pm.dex; + +import android.content.pm.ApplicationInfo; + +/** + * Art manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class ArtManagerInternal { + + /** + * Return optimization information about the application {@code info} when + * in executes using the specified {@code abi}. + */ + public abstract PackageOptimizationInfo getPackageOptimizationInfo( + ApplicationInfo info, String abi); +} diff --git a/android/content/pm/dex/PackageOptimizationInfo.java b/android/content/pm/dex/PackageOptimizationInfo.java new file mode 100644 index 00000000..7e7d29e6 --- /dev/null +++ b/android/content/pm/dex/PackageOptimizationInfo.java @@ -0,0 +1,47 @@ +/** + * Copyright 2018 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.content.pm.dex; + +/** + * Encapsulates information about the optimizations performed on a package. + * + * @hide + */ +public class PackageOptimizationInfo { + private final int mCompilationFilter; + private final int mCompilationReason; + + public PackageOptimizationInfo(int compilerFilter, int compilationReason) { + this.mCompilationReason = compilationReason; + this.mCompilationFilter = compilerFilter; + } + + public int getCompilationReason() { + return mCompilationReason; + } + + public int getCompilationFilter() { + return mCompilationFilter; + } + + /** + * Create a default optimization info object for the case when we have no information. + */ + public static PackageOptimizationInfo createWithNoInfo() { + return new PackageOptimizationInfo(-1, -1); + } +} diff --git a/android/content/pm/permission/RuntimePermissionPresenter.java b/android/content/pm/permission/RuntimePermissionPresenter.java index 02d0a6d8..79bc9a30 100644 --- a/android/content/pm/permission/RuntimePermissionPresenter.java +++ b/android/content/pm/permission/RuntimePermissionPresenter.java @@ -274,6 +274,7 @@ public final class RuntimePermissionPresenter { } } + @GuardedBy("mLock") private void scheduleNextMessageIfNeededLocked() { if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) { Message nextMessage = mPendingWork.remove(0); diff --git a/android/content/pm/split/DefaultSplitAssetLoader.java b/android/content/pm/split/DefaultSplitAssetLoader.java index 99eb4702..9e3a8f48 100644 --- a/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/android/content/pm/split/DefaultSplitAssetLoader.java @@ -15,10 +15,13 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; +import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; @@ -26,6 +29,8 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; +import java.io.IOException; + /** * Loads the base and split APKs into a single AssetManager. * @hide @@ -33,68 +38,66 @@ import libcore.io.IoUtils; public class DefaultSplitAssetLoader implements SplitAssetLoader { private final String mBaseCodePath; private final String[] mSplitCodePaths; - private final int mFlags; - + private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; - public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) { + public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; mFlags = flags; } - private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParser.PackageParserException { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); } - if (assets.addAssetPath(apkPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + try { + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { if (mCachedAssetManager != null) { return mCachedAssetManager; } - AssetManager assets = new AssetManager(); - try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - loadApkIntoAssetManager(assets, mBaseCodePath, mFlags); - - if (!ArrayUtils.isEmpty(mSplitCodePaths)) { - for (String apkPath : mSplitCodePaths) { - loadApkIntoAssetManager(assets, apkPath, mFlags); - } - } + ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null + ? mSplitCodePaths.length : 0) + 1]; - mCachedAssetManager = assets; - assets = null; - return mCachedAssetManager; - } finally { - if (assets != null) { - IoUtils.closeQuietly(assets); + // Load the base. + int splitIdx = 0; + apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + + // Load any splits. + if (!ArrayUtils.isEmpty(mSplitCodePaths)) { + for (String apkPath : mSplitCodePaths) { + apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); } } + + AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + + mCachedAssetManager = assets; + return mCachedAssetManager; } @Override - public AssetManager getSplitAssetManager(int splitIdx) - throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { return getBaseAssetManager(); } @Override public void close() throws Exception { - if (mCachedAssetManager != null) { - IoUtils.closeQuietly(mCachedAssetManager); - } + IoUtils.closeQuietly(mCachedAssetManager); } } diff --git a/android/content/pm/split/SplitAssetDependencyLoader.java b/android/content/pm/split/SplitAssetDependencyLoader.java index 16023f0d..58eaabfa 100644 --- a/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/android/content/pm/split/SplitAssetDependencyLoader.java @@ -15,17 +15,21 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; +import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; import libcore.io.IoUtils; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -34,17 +38,15 @@ import java.util.Collections; * is to be used when an application opts-in to isolated split loading. * @hide */ -public class SplitAssetDependencyLoader - extends SplitDependencyLoader<PackageParser.PackageParserException> +public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException> implements SplitAssetLoader { private final String[] mSplitPaths; - private final int mFlags; - - private String[][] mCachedPaths; - private AssetManager[] mCachedAssetManagers; + private final @ParseFlags int mFlags; + private final ApkAssets[][] mCachedSplitApks; + private final AssetManager[] mCachedAssetManagers; public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, - SparseArray<int[]> dependencies, int flags) { + SparseArray<int[]> dependencies, @ParseFlags int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. @@ -53,7 +55,7 @@ public class SplitAssetDependencyLoader System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); mFlags = flags; - mCachedPaths = new String[mSplitPaths.length][]; + mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; mCachedAssetManagers = new AssetManager[mSplitPaths.length]; } @@ -62,58 +64,60 @@ public class SplitAssetDependencyLoader return mCachedAssetManagers[splitIdx] != null; } - private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags) - throws PackageParser.PackageParserException { - final AssetManager assets = new AssetManager(); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); + } + try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - - for (String assetPath : assetPaths) { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && - !PackageParser.isApkPath(assetPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + assetPath); - } - - if (assets.addAssetPath(assetPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + assetPath); - } - } - return assets; - } catch (Throwable e) { - IoUtils.closeQuietly(assets); - throw e; + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } + private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { + final AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + return assets; + } + @Override protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, - int parentSplitIdx) throws PackageParser.PackageParserException { - final ArrayList<String> assetPaths = new ArrayList<>(); + int parentSplitIdx) throws PackageParserException { + final ArrayList<ApkAssets> assets = new ArrayList<>(); + + // Include parent ApkAssets. if (parentSplitIdx >= 0) { - Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]); + Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); } - assetPaths.add(mSplitPaths[splitIdx]); + // Include this ApkAssets. + assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); + + // Load and include all config splits for this feature. for (int configSplitIdx : configSplitIndices) { - assetPaths.add(mSplitPaths[configSplitIdx]); + assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); } - mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]); - mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx], - mFlags); + + // Cache the results. + mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); + mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]); } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { loadDependenciesForSplit(0); return mCachedAssetManagers[0]; } @Override - public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int idx) throws PackageParserException { // Since we insert the base at position 0, and PackageParser keeps splits separate from // the base, we need to adjust the index. loadDependenciesForSplit(idx + 1); diff --git a/android/content/res/ApkAssets.java b/android/content/res/ApkAssets.java new file mode 100644 index 00000000..9de8be3e --- /dev/null +++ b/android/content/res/ApkAssets.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 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.content.res; + +import android.annotation.NonNull; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * The loaded, immutable, in-memory representation of an APK. + * + * The main implementation is native C++ and there is very little API surface exposed here. The APK + * is mainly accessed via {@link AssetManager}. + * + * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, + * making the creation of AssetManagers very cheap. + * @hide + */ +public final class ApkAssets { + @GuardedBy("this") private final long mNativePtr; + @GuardedBy("this") private StringBlock mStringBlock; + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { + return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system) + throws IOException { + return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, + boolean forceSharedLibrary) throws IOException { + return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications. + * + * Performs a dup of the underlying fd, so you must take care of still closing + * the FileDescriptor yourself (and can do that whenever you want). + * + * @param fd The FileDescriptor of an open, readable APK. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLibrary) + throws IOException { + return new ApkAssets(fd, friendlyName, system, forceSharedLibrary); + } + + /** + * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path + * is encoded within the IDMAP. + * + * @param idmapPath Path to the IDMAP of an overlay APK. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) + throws IOException { + return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/); + } + + private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException { + Preconditions.checkNotNull(path, "path"); + mNativePtr = nativeLoad(path, system, forceSharedLib, overlay); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system, + boolean forceSharedLib) throws IOException { + Preconditions.checkNotNull(fd, "fd"); + Preconditions.checkNotNull(friendlyName, "friendlyName"); + mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + public @NonNull String getAssetPath() { + synchronized (this) { + return nativeGetAssetPath(mNativePtr); + } + } + + CharSequence getStringFromPool(int idx) { + synchronized (this) { + return mStringBlock.get(idx); + } + } + + /** + * Retrieve a parser for a compiled XML file. This is associated with a single APK and + * <em>NOT</em> a full AssetManager. This means that shared-library references will not be + * dynamically assigned runtime package IDs. + * + * @param fileName The path to the file within the APK. + * @return An XmlResourceParser. + * @throws IOException if the file was not found or an error occurred retrieving it. + */ + public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); + synchronized (this) { + long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); + try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { + XmlResourceParser parser = block.newParser(); + // If nativeOpenXml doesn't throw, it will always return a valid native pointer, + // which makes newParser always return non-null. But let's be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } + } + } + + /** + * Returns false if the underlying APK was changed since this ApkAssets was loaded. + */ + public boolean isUpToDate() { + synchronized (this) { + return nativeIsUpToDate(mNativePtr); + } + } + + @Override + public String toString() { + return "ApkAssets{path=" + getAssetPath() + "}"; + } + + @Override + protected void finalize() throws Throwable { + nativeDestroy(mNativePtr); + } + + private static native long nativeLoad( + @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException; + private static native long nativeLoadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLib) + throws IOException; + private static native void nativeDestroy(long ptr); + private static native @NonNull String nativeGetAssetPath(long ptr); + private static native long nativeGetStringBlock(long ptr); + private static native boolean nativeIsUpToDate(long ptr); + private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; +} diff --git a/android/content/res/AssetManager.java b/android/content/res/AssetManager.java index 78665609..28953427 100644 --- a/android/content/res/AssetManager.java +++ b/android/content/res/AssetManager.java @@ -18,20 +18,33 @@ package android.content.res; import android.annotation.AnyRes; import android.annotation.ArrayRes; +import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; +import android.annotation.StyleRes; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; import android.os.ParcelFileDescriptor; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; -import java.io.FileDescriptor; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import libcore.io.IoUtils; + +import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -42,7 +55,20 @@ import java.util.HashMap; * bytes. */ public final class AssetManager implements AutoCloseable { - /* modes used when opening an asset */ + private static final String TAG = "AssetManager"; + private static final boolean DEBUG_REFS = false; + + private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk"; + + private static final Object sSync = new Object(); + + private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0]; + + // Not private for LayoutLib's BridgeAssetManager. + @GuardedBy("sSync") static AssetManager sSystem = null; + + @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0]; + @GuardedBy("sSync") private static ArraySet<ApkAssets> sSystemApkAssetsSet; /** * Mode for {@link #open(String, int)}: no specific information about how @@ -65,87 +91,391 @@ public final class AssetManager implements AutoCloseable { */ public static final int ACCESS_BUFFER = 3; - private static final String TAG = "AssetManager"; - private static final boolean localLOGV = false || false; - - private static final boolean DEBUG_REFS = false; - - private static final Object sSync = new Object(); - /*package*/ static AssetManager sSystem = null; + @GuardedBy("this") private final TypedValue mValue = new TypedValue(); + @GuardedBy("this") private final long[] mOffsets = new long[2]; - private final TypedValue mValue = new TypedValue(); - private final long[] mOffsets = new long[2]; - - // For communication with native code. - private long mObject; + // Pointer to native implementation, stuffed inside a long. + @GuardedBy("this") private long mObject; + + // The loaded asset paths. + @GuardedBy("this") private ApkAssets[] mApkAssets; + + // Debug/reference counting implementation. + @GuardedBy("this") private boolean mOpen = true; + @GuardedBy("this") private int mNumRefs = 1; + @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; + + /** + * A Builder class that helps create an AssetManager with only a single invocation of + * {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder, + * AssetManager must ensure there are system ApkAssets loaded at all times, which when combined + * with the user's call to add additional ApkAssets, results in multiple calls to + * {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. + * @hide + */ + public static class Builder { + private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>(); + + public Builder addApkAssets(ApkAssets apkAssets) { + mUserApkAssets.add(apkAssets); + return this; + } + + public AssetManager build() { + // Retrieving the system ApkAssets forces their creation as well. + final ApkAssets[] systemApkAssets = getSystem().getApkAssets(); + + final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size(); + final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount]; + + System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length); + + final int userApkAssetCount = mUserApkAssets.size(); + for (int i = 0; i < userApkAssetCount; i++) { + apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i); + } + + // Calling this constructor prevents creation of system ApkAssets, which we took care + // of in this Builder. + final AssetManager assetManager = new AssetManager(false /*sentinel*/); + assetManager.mApkAssets = apkAssets; + AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets, + false /*invalidateCaches*/); + return assetManager; + } + } - private StringBlock mStringBlocks[] = null; - - private int mNumRefs = 1; - private boolean mOpen = true; - private HashMap<Long, RuntimeException> mRefStacks; - /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the * appropriate asset manager with {@link Resources#getAssets}. Not for * use by applications. - * {@hide} + * @hide */ public AssetManager() { - synchronized (this) { - if (DEBUG_REFS) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); - } - init(false); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); - ensureSystemAssets(); + final ApkAssets[] assets; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + assets = sSystemApkAssets; } - } - private static void ensureSystemAssets() { - synchronized (sSync) { - if (sSystem == null) { - AssetManager system = new AssetManager(true); - system.makeStringBlocks(null); - sSystem = system; - } + mObject = nativeCreate(); + if (DEBUG_REFS) { + mNumRefs = 0; + incRefsLocked(hashCode()); } + + // Always set the framework resources. + setApkAssets(assets, false /*invalidateCaches*/); } - - private AssetManager(boolean isSystem) { + + /** + * Private constructor that doesn't call ensureSystemAssets. + * Used for the creation of system assets. + */ + @SuppressWarnings("unused") + private AssetManager(boolean sentinel) { + mObject = nativeCreate(); if (DEBUG_REFS) { - synchronized (this) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); + mNumRefs = 0; + incRefsLocked(hashCode()); + } + } + + /** + * This must be called from Zygote so that system assets are shared by all applications. + */ + @GuardedBy("sSync") + private static void createSystemAssetsInZygoteLocked() { + if (sSystem != null) { + return; + } + + // Make sure that all IDMAPs are up to date. + nativeVerifySystemIdmaps(); + + try { + final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); + apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/)); + loadStaticRuntimeOverlays(apkAssets); + + sSystemApkAssetsSet = new ArraySet<>(apkAssets); + sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]); + sSystem = new AssetManager(true /*sentinel*/); + sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/); + } catch (IOException e) { + throw new IllegalStateException("Failed to create system AssetManager", e); + } + } + + /** + * Loads the static runtime overlays declared in /data/resource-cache/overlays.list. + * Throws an exception if the file is corrupt or if loading the APKs referenced by the file + * fails. Returns quietly if the overlays.list file doesn't exist. + * @param outApkAssets The list to fill with the loaded ApkAssets. + */ + private static void loadStaticRuntimeOverlays(ArrayList<ApkAssets> outApkAssets) + throws IOException { + final FileInputStream fis; + try { + fis = new FileInputStream("/data/resource-cache/overlays.list"); + } catch (FileNotFoundException e) { + // We might not have any overlays, this is fine. We catch here since ApkAssets + // loading can also fail with the same exception, which we would want to propagate. + Log.i(TAG, "no overlays.list file found"); + return; + } + + try { + // Acquire a lock so that any idmap scanning doesn't impact the current set. + // The order of this try-with-resources block matters. We must release the lock, and + // then close the file streams when exiting the block. + try (final BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + final FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE, true /*shared*/)) { + for (String line; (line = br.readLine()) != null; ) { + final String idmapPath = line.split(" ")[1]; + outApkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + } } + } finally { + // When BufferedReader is closed above, FileInputStream is closed as well. But let's be + // paranoid. + IoUtils.closeQuietly(fis); } - init(true); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); } /** * Return a global shared asset manager that provides access to only * system assets (no application assets). - * {@hide} + * @hide */ public static AssetManager getSystem() { - ensureSystemAssets(); - return sSystem; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + return sSystem; + } } /** * Close this asset manager. */ + @Override public void close() { - synchronized(this) { - //System.out.println("Release: num=" + mNumRefs - // + ", released=" + mReleased); + synchronized (this) { + if (!mOpen) { + return; + } + + mOpen = false; + decRefsLocked(hashCode()); + } + } + + /** + * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)} + * family of methods. + * + * @param apkAssets The new set of paths. + * @param invalidateCaches Whether to invalidate any caches. This should almost always be true. + * Set this to false if you are appending new resources + * (not new configurations). + * @hide + */ + public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) { + Preconditions.checkNotNull(apkAssets, "apkAssets"); + + ApkAssets[] newApkAssets = new ApkAssets[sSystemApkAssets.length + apkAssets.length]; + + // Copy the system assets first. + System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length); + + // Copy the given ApkAssets if they are not already in the system list. + int newLength = sSystemApkAssets.length; + for (ApkAssets apkAsset : apkAssets) { + if (!sSystemApkAssetsSet.contains(apkAsset)) { + newApkAssets[newLength++] = apkAsset; + } + } + + // Truncate if necessary. + if (newLength != newApkAssets.length) { + newApkAssets = Arrays.copyOf(newApkAssets, newLength); + } + + synchronized (this) { + ensureOpenLocked(); + mApkAssets = newApkAssets; + nativeSetApkAssets(mObject, mApkAssets, invalidateCaches); + if (invalidateCaches) { + // Invalidate all caches. + invalidateCachesLocked(-1); + } + } + } + + /** + * Invalidates the caches in this AssetManager according to the bitmask `diff`. + * + * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}. + * @see ActivityInfo.Config + */ + private void invalidateCachesLocked(int diff) { + // TODO(adamlesinski): Currently there are no caches to invalidate in Java code. + } + + /** + * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this + * returns a 0-length array. + * @hide + */ + public @NonNull ApkAssets[] getApkAssets() { + synchronized (this) { if (mOpen) { - mOpen = false; - decRefsLocked(this.hashCode()); + return mApkAssets; + } + } + return sEmptyApkAssets; + } + + /** + * Returns a cookie for use with the other APIs of AssetManager. + * @return 0 if the path was not found, otherwise a positive integer cookie representing + * this path in the AssetManager. + * @hide + */ + public int findCookieForPath(@NonNull String path) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (path.equals(mApkAssets[i].getAssetPath())) { + return i + 1; + } + } + } + return 0; + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPath(String path) { + return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPathAsSharedLibrary(String path) { + return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addOverlayPath(String path) { + return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); + } + + private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureOpenLocked(); + final int count = mApkAssets.length; + + // See if we already have it loaded. + for (int i = 0; i < count; i++) { + if (mApkAssets[i].getAssetPath().equals(path)) { + return i + 1; + } + } + + final ApkAssets assets; + try { + if (overlay) { + // TODO(b/70343104): This hardcoded path will be removed once + // addAssetPathInternal is deleted. + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib); + } + } catch (IOException e) { + return 0; + } + + mApkAssets = Arrays.copyOf(mApkAssets, count + 1); + mApkAssets[count] = assets; + nativeSetApkAssets(mObject, mApkAssets, true); + invalidateCachesLocked(-1); + return count + 1; + } + } + + /** + * Ensures that the native implementation has not been destroyed. + * The AssetManager may have been closed, but references to it still exist + * and therefore the native implementation is not destroyed. + */ + @GuardedBy("this") + private void ensureValidLocked() { + if (mObject == 0) { + throw new RuntimeException("AssetManager has been destroyed"); + } + } + + /** + * Ensures that the AssetManager has not been explicitly closed. If this method passes, + * then this implies that ensureValidLocked() also passes. + */ + @GuardedBy("this") + private void ensureOpenLocked() { + // If mOpen is true, this implies that mObject != 0. + if (!mOpen) { + throw new RuntimeException("AssetManager has been closed"); + } + } + + /** + * Populates {@code outValue} with the data associated a particular + * resource identifier for the current configuration. + * + * @param resId the resource identifier to load + * @param densityDpi the density bucket for which to load the resource + * @param outValue the typed value in which to put the data + * @param resolveRefs {@code true} to resolve references, {@code false} + * to leave them unresolved + * @return {@code true} if the data was loaded into {@code outValue}, + * {@code false} otherwise + */ + boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, + boolean resolveRefs) { + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeGetResourceValue( + mObject, resId, (short) densityDpi, outValue, resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); } + return true; } } @@ -156,8 +486,7 @@ public final class AssetManager implements AutoCloseable { * @param resId the resource identifier to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceText(@StringRes int resId) { + @Nullable CharSequence getResourceText(@StringRes int resId) { synchronized (this) { final TypedValue outValue = mValue; if (getResourceValue(resId, 0, outValue, true)) { @@ -172,15 +501,15 @@ public final class AssetManager implements AutoCloseable { * identifier for the current configuration. * * @param resId the resource identifier to load - * @param bagEntryId + * @param bagEntryId the index into the bag to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { + @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { synchronized (this) { + ensureValidLocked(); final TypedValue outValue = mValue; - final int block = loadResourceBagValue(resId, bagEntryId, outValue, true); - if (block < 0) { + final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue); + if (cookie <= 0) { return null; } @@ -189,52 +518,60 @@ public final class AssetManager implements AutoCloseable { outValue.changingConfigurations); if (outValue.type == TypedValue.TYPE_STRING) { - return mStringBlocks[block].get(outValue.data); + return mApkAssets[cookie - 1].getStringFromPool(outValue.data); } return outValue.coerceToString(); } } + int getResourceArraySize(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArraySize(mObject, resId); + } + } + /** - * Retrieves the string array associated with a particular resource - * identifier for the current configuration. + * Populates `outData` with array elements of `resId`. `outData` is normally + * used with + * {@link TypedArray}. * - * @param resId the resource identifier of the string array - * @return the string array, or {@code null} + * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES} + * long, + * with the indices of the data representing the type, value, asset cookie, + * resource ID, + * configuration change mask, and density of the element. + * + * @param resId The resource ID of an array resource. + * @param outData The array to populate with data. + * @return The length of the array. + * + * @see TypedArray#STYLE_TYPE + * @see TypedArray#STYLE_DATA + * @see TypedArray#STYLE_ASSET_COOKIE + * @see TypedArray#STYLE_RESOURCE_ID + * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS + * @see TypedArray#STYLE_DENSITY */ - @Nullable - final String[] getResourceStringArray(@ArrayRes int resId) { - return getArrayStringResource(resId); + int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) { + Preconditions.checkNotNull(outData, "outData"); + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArray(mObject, resId, outData); + } } /** - * Populates {@code outValue} with the data associated a particular - * resource identifier for the current configuration. + * Retrieves the string array associated with a particular resource + * identifier for the current configuration. * - * @param resId the resource identifier to load - * @param densityDpi the density bucket for which to load the resource - * @param outValue the typed value in which to put the data - * @param resolveRefs {@code true} to resolve references, {@code false} - * to leave them unresolved - * @return {@code true} if the data was loaded into {@code outValue}, - * {@code false} otherwise + * @param resId the resource identifier of the string array + * @return the string array, or {@code null} */ - final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, - boolean resolveRefs) { + @Nullable String[] getResourceStringArray(@ArrayRes int resId) { synchronized (this) { - final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); - if (block < 0) { - return false; - } - - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); - - if (outValue.type == TypedValue.TYPE_STRING) { - outValue.string = mStringBlocks[block].get(outValue.data); - } - return true; + ensureValidLocked(); + return nativeGetResourceStringArray(mObject, resId); } } @@ -244,26 +581,48 @@ public final class AssetManager implements AutoCloseable { * * @param resId the resource id of the string array */ - final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { + @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { synchronized (this) { - final int[] rawInfoArray = getArrayStringInfo(resId); + ensureValidLocked(); + final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId); if (rawInfoArray == null) { return null; } + final int rawInfoArrayLen = rawInfoArray.length; final int infoArrayLen = rawInfoArrayLen / 2; - int block; - int index; final CharSequence[] retArray = new CharSequence[infoArrayLen]; for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) { - block = rawInfoArray[i]; - index = rawInfoArray[i + 1]; - retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null; + int cookie = rawInfoArray[i]; + int index = rawInfoArray[i + 1]; + retArray[j] = (index >= 0 && cookie > 0) + ? mApkAssets[cookie - 1].getStringFromPool(index) : null; } return retArray; } } + @Nullable int[] getResourceIntArray(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceIntArray(mObject, resId); + } + } + + /** + * Get the attributes for a style resource. These are the <item> + * elements in + * a <style> resource. + * @param resId The resource ID of the style + * @return An array of attribute IDs. + */ + @AttrRes int[] getStyleAttributes(@StyleRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetStyleAttributes(mObject, resId); + } + } + /** * Populates {@code outValue} with the data associated with a particular * resource identifier for the current configuration. Resolves theme @@ -277,73 +636,88 @@ public final class AssetManager implements AutoCloseable { * @return {@code true} if the data was loaded into {@code outValue}, * {@code false} otherwise */ - final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, + boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, boolean resolveRefs) { - final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs); - if (block < 0) { - return false; + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue, + resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; } + } - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); + void dumpTheme(long theme, int priority, String tag, String prefix) { + synchronized (this) { + ensureValidLocked(); + nativeThemeDump(mObject, theme, priority, tag, prefix); + } + } - if (outValue.type == TypedValue.TYPE_STRING) { - final StringBlock[] blocks = ensureStringBlocks(); - outValue.string = blocks[block].get(outValue.data); + @Nullable String getResourceName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceName(mObject, resId); } - return true; } - /** - * Ensures the string blocks are loaded. - * - * @return the string blocks - */ - @NonNull - final StringBlock[] ensureStringBlocks() { + @Nullable String getResourcePackageName(@AnyRes int resId) { synchronized (this) { - if (mStringBlocks == null) { - makeStringBlocks(sSystem.mStringBlocks); - } - return mStringBlocks; + ensureValidLocked(); + return nativeGetResourcePackageName(mObject, resId); } } - /*package*/ final void makeStringBlocks(StringBlock[] seed) { - final int seedNum = (seed != null) ? seed.length : 0; - final int num = getStringBlockCount(); - mStringBlocks = new StringBlock[num]; - if (localLOGV) Log.v(TAG, "Making string blocks for " + this - + ": " + num); - for (int i=0; i<num; i++) { - if (i < seedNum) { - mStringBlocks[i] = seed[i]; - } else { - mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); - } + @Nullable String getResourceTypeName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceTypeName(mObject, resId); } } - /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) { + @Nullable String getResourceEntryName(@AnyRes int resId) { synchronized (this) { - // Cookies map to string blocks starting at 1. - return mStringBlocks[cookie - 1].get(id); + ensureValidLocked(); + return nativeGetResourceEntryName(mObject, resId); } } + @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType, + @Nullable String defPackage) { + synchronized (this) { + ensureValidLocked(); + // name is checked in JNI. + return nativeGetResourceIdentifier(mObject, name, defType, defPackage); + } + } + + CharSequence getPooledStringForCookie(int cookie, int id) { + // Cookies map to ApkAssets starting at 1. + return getApkAssets()[cookie - 1].getStringFromPool(id); + } + /** * Open an asset using ACCESS_STREAMING mode. This provides access to * files that have been bundled with an application as assets -- that is, * files placed in to the "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * * @see #open(String, int) * @see #list */ - public final InputStream open(String fileName) throws IOException { + public @NonNull InputStream open(@NonNull String fileName) throws IOException { return open(fileName, ACCESS_STREAMING); } @@ -353,8 +727,7 @@ public final class AssetManager implements AutoCloseable { * with an application as assets -- that is, files placed in to the * "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * @param accessMode Desired access mode for retrieving the data. * * @see #ACCESS_UNKNOWN @@ -364,34 +737,40 @@ public final class AssetManager implements AutoCloseable { * @see #open(String) * @see #list */ - public final InputStream open(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openAsset(fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenAsset(mObject, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset file: " + fileName); } - public final AssetFileDescriptor openFd(String fileName) - throws IOException { + /** + * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides access to files that have been bundled with an application as assets -- that + * is, files placed in to the "assets" directory. + * + * The asset must be uncompressed, or an exception will be thrown. + * + * @param fileName The name of the asset to open. This name can be hierarchical. + * @return An open AssetFileDescriptor. + */ + public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset file: " + fileName); } /** @@ -406,90 +785,121 @@ public final class AssetManager implements AutoCloseable { * * @see #open */ - public native final String[] list(String path) - throws IOException; + public @Nullable String[] list(@NonNull String path) throws IOException { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + return nativeList(mObject, path); + } + } /** - * {@hide} * Open a non-asset file as an asset using ACCESS_STREAMING mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * * @see #open(String) + * @hide */ - public final InputStream openNonAsset(String fileName) throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException { return openNonAsset(0, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset file as an asset using a specific access mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * @param accessMode Desired access mode for retrieving the data. + * + * @see #ACCESS_UNKNOWN + * @see #ACCESS_STREAMING + * @see #ACCESS_RANDOM + * @see #ACCESS_BUFFER * @see #open(String, int) + * @hide */ - public final InputStream openNonAsset(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode) + throws IOException { return openNonAsset(0, fileName, accessMode); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName) + throws IOException { return openNonAsset(cookie, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. * @param accessMode Desired access mode for retrieving the data. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openNonAssetNative(cookie, fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset absolute file: " + fileName); } - public final AssetFileDescriptor openNonAssetFd(String fileName) + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName) throws IOException { return openNonAssetFd(0, fileName); } - - public final AssetFileDescriptor openNonAssetFd(int cookie, - String fileName) throws IOException { + + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param cookie Identifier of the package to be opened. + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openNonAssetFdNative(cookie, - fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = + nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset absolute file: " + fileName); } /** @@ -497,7 +907,7 @@ public final class AssetManager implements AutoCloseable { * * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(String fileName) + public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName) throws IOException { return openXmlResourceParser(0, fileName); } @@ -508,270 +918,265 @@ public final class AssetManager implements AutoCloseable { * @param cookie Identifier of the package to be opened. * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(int cookie, - String fileName) throws IOException { - XmlBlock block = openXmlBlockAsset(cookie, fileName); - XmlResourceParser rp = block.newParser(); - block.close(); - return rp; + public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName) + throws IOException { + try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) { + XmlResourceParser parser = block.newParser(); + // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with + // a valid native pointer, which makes newParser always return non-null. But let's + // be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } } /** - * {@hide} - * Retrieve a non-asset as a compiled XML file. Not for use by - * applications. + * Retrieve a non-asset as a compiled XML file. Not for use by applications. * * @param fileName The name of the file to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException { return openXmlBlockAsset(0, fileName); } /** - * {@hide} * Retrieve a non-asset as a compiled XML file. Not for use by * applications. * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long xmlBlock = openXmlAssetNative(cookie, fileName); - if (xmlBlock != 0) { - XmlBlock res = new XmlBlock(this, xmlBlock); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); + if (xmlBlock == 0) { + throw new FileNotFoundException("Asset XML file: " + fileName); } + final XmlBlock block = new XmlBlock(this, xmlBlock); + incRefsLocked(block.hashCode()); + return block; } - throw new FileNotFoundException("Asset XML file: " + fileName); } - /*package*/ void xmlBlockGone(int id) { + void xmlBlockGone(int id) { synchronized (this) { decRefsLocked(id); } } - /*package*/ final long createTheme() { + void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress, + long outIndicesAddress) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long res = newTheme(); - incRefsLocked(res); - return res; + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes, + parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress, + outIndicesAddress); } } - /*package*/ final void releaseTheme(long theme) { + boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues, + @NonNull int[] outIndices) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); synchronized (this) { - deleteTheme(theme); - decRefsLocked(theme); + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeResolveAttrs(mObject, + themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices); } } + boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs, + @NonNull int[] outValues, @NonNull int[] outIndices) { + Preconditions.checkNotNull(parser, "parser"); + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeRetrieveAttributes( + mObject, parser.mParseState, inAttrs, outValues, outIndices); + } + } + + long createTheme() { + synchronized (this) { + ensureValidLocked(); + long themePtr = nativeThemeCreate(mObject); + incRefsLocked(themePtr); + return themePtr; + } + } + + void releaseTheme(long themePtr) { + synchronized (this) { + nativeThemeDestroy(themePtr); + decRefsLocked(themePtr); + } + } + + void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) { + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeThemeApplyStyle(mObject, themePtr, resId, force); + } + } + + @Override protected void finalize() throws Throwable { - try { - if (DEBUG_REFS && mNumRefs != 0) { - Log.w(TAG, "AssetManager " + this - + " finalized with non-zero refs: " + mNumRefs); - if (mRefStacks != null) { - for (RuntimeException e : mRefStacks.values()) { - Log.w(TAG, "Reference from here", e); - } + if (DEBUG_REFS && mNumRefs != 0) { + Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs); + if (mRefStacks != null) { + for (RuntimeException e : mRefStacks.values()) { + Log.w(TAG, "Reference from here", e); } } - destroy(); - } finally { - super.finalize(); + } + + if (mObject != 0) { + nativeDestroy(mObject); } } - + + /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread + safe and it does not rely on AssetManager once it has been created. It completely owns the + underlying Asset. */ public final class AssetInputStream extends InputStream { + private long mAssetNativePtr; + private long mLength; + private long mMarkPos; + /** * @hide */ public final int getAssetInt() { throw new UnsupportedOperationException(); } + /** * @hide */ public final long getNativeAsset() { - return mAsset; + return mAssetNativePtr; } - private AssetInputStream(long asset) - { - mAsset = asset; - mLength = getAssetLength(asset); + + private AssetInputStream(long assetNativePtr) { + mAssetNativePtr = assetNativePtr; + mLength = nativeAssetGetLength(assetNativePtr); } + + @Override public final int read() throws IOException { - return readAssetChar(mAsset); + ensureOpen(); + return nativeAssetReadChar(mAssetNativePtr); } - public final boolean markSupported() { - return true; - } - public final int available() throws IOException { - long len = getAssetRemainingLength(mAsset); - return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; - } - public final void close() throws IOException { - synchronized (AssetManager.this) { - if (mAsset != 0) { - destroyAsset(mAsset); - mAsset = 0; - decRefsLocked(hashCode()); - } - } - } - public final void mark(int readlimit) { - mMarkPos = seekAsset(mAsset, 0, 0); - } - public final void reset() throws IOException { - seekAsset(mAsset, mMarkPos, -1); - } - public final int read(byte[] b) throws IOException { - return readAsset(mAsset, b, 0, b.length); + + @Override + public final int read(@NonNull byte[] b) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, 0, b.length); } - public final int read(byte[] b, int off, int len) throws IOException { - return readAsset(mAsset, b, off, len); + + @Override + public final int read(@NonNull byte[] b, int off, int len) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, off, len); } + + @Override public final long skip(long n) throws IOException { - long pos = seekAsset(mAsset, 0, 0); - if ((pos+n) > mLength) { - n = mLength-pos; + ensureOpen(); + long pos = nativeAssetSeek(mAssetNativePtr, 0, 0); + if ((pos + n) > mLength) { + n = mLength - pos; } if (n > 0) { - seekAsset(mAsset, n, 0); + nativeAssetSeek(mAssetNativePtr, n, 0); } return n; } - protected void finalize() throws Throwable - { - close(); + @Override + public final int available() throws IOException { + ensureOpen(); + final long len = nativeAssetGetRemainingLength(mAssetNativePtr); + return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len; } - private long mAsset; - private long mLength; - private long mMarkPos; - } - - /** - * Add an additional set of assets to the asset manager. This can be - * either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPath(String path) { - return addAssetPathInternal(path, false); - } - - /** - * Add an application assets to the asset manager and loading it as shared library. - * This can be either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPathAsSharedLibrary(String path) { - return addAssetPathInternal(path, true); - } - - private final int addAssetPathInternal(String path, boolean appAsLib) { - synchronized (this) { - int res = addAssetPathNative(path, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final boolean markSupported() { + return true; } - } - private native final int addAssetPathNative(String path, boolean appAsLib); - - /** - * Add an additional set of assets to the asset manager from an already open - * FileDescriptor. Not for use by applications. - * This does not give full AssetManager functionality for these assets, - * since the origin of the file is not known for purposes of sharing, - * overlay resolution, and other features. However it does allow you - * to do simple access to the contents of the given fd as an apk file. - * Performs a dup of the underlying fd, so you must take care of still closing - * the FileDescriptor yourself (and can do that whenever you want). - * Returns the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public int addAssetFd(FileDescriptor fd, String debugPathName) { - return addAssetFdInternal(fd, debugPathName, false); - } - - private int addAssetFdInternal(FileDescriptor fd, String debugPathName, - boolean appAsLib) { - synchronized (this) { - int res = addAssetFdNative(fd, debugPathName, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void mark(int readlimit) { + ensureOpen(); + mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0); } - } - - private native int addAssetFdNative(FileDescriptor fd, String debugPathName, - boolean appAsLib); - - /** - * Add a set of assets to overlay an already added set of assets. - * - * This is only intended for application resources. System wide resources - * are handled before any Java code is executed. - * - * {@hide} - */ - public final int addOverlayPath(String idmapPath) { - synchronized (this) { - int res = addOverlayPathNative(idmapPath); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void reset() throws IOException { + ensureOpen(); + nativeAssetSeek(mAssetNativePtr, mMarkPos, -1); } - } - /** - * See addOverlayPath. - * - * {@hide} - */ - public native final int addOverlayPathNative(String idmapPath); + @Override + public final void close() throws IOException { + if (mAssetNativePtr != 0) { + nativeAssetDestroy(mAssetNativePtr); + mAssetNativePtr = 0; - /** - * Add multiple sets of assets to the asset manager at once. See - * {@link #addAssetPath(String)} for more information. Returns array of - * cookies for each added asset with 0 indicating failure, or null if - * the input array of paths is null. - * {@hide} - */ - public final int[] addAssetPaths(String[] paths) { - if (paths == null) { - return null; + synchronized (AssetManager.this) { + decRefsLocked(hashCode()); + } + } } - int[] cookies = new int[paths.length]; - for (int i = 0; i < paths.length; i++) { - cookies[i] = addAssetPath(paths[i]); + @Override + protected void finalize() throws Throwable { + close(); } - return cookies; + private void ensureOpen() { + if (mAssetNativePtr == 0) { + throw new IllegalStateException("AssetInputStream is closed"); + } + } } /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to * instantiate a new AssetManager class to see the new data. - * {@hide} + * @hide */ - public native final boolean isUpToDate(); + public boolean isUpToDate() { + for (ApkAssets apkAssets : getApkAssets()) { + if (!apkAssets.isUpToDate()) { + return false; + } + } + return true; + } /** * Get the locales that this asset manager contains data for. @@ -784,7 +1189,12 @@ public final class AssetManager implements AutoCloseable { * are of the form {@code ll_CC} where {@code ll} is a two letter language code, * and {@code CC} is a two letter country code. */ - public native final String[] getLocales(); + public String[] getLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, false /*excludeSystem*/); + } + } /** * Same as getLocales(), except that locales that are only provided by the system (i.e. those @@ -794,131 +1204,58 @@ public final class AssetManager implements AutoCloseable { * assets support Cherokee and French, getLocales() would return * [Cherokee, English, French, German], while getNonSystemLocales() would return * [Cherokee, French]. - * {@hide} + * @hide */ - public native final String[] getNonSystemLocales(); - - /** {@hide} */ - public native final Configuration[] getSizeConfigurations(); + public String[] getNonSystemLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, true /*excludeSystem*/); + } + } /** - * Change the configuation used when retrieving resources. Not for use by - * applications. - * {@hide} + * @hide */ - public native final void setConfiguration(int mcc, int mnc, String locale, - int orientation, int touchscreen, int density, int keyboard, - int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, - int screenLayout, int uiMode, int colorMode, int majorVersion); + Configuration[] getSizeConfigurations() { + synchronized (this) { + ensureValidLocked(); + return nativeGetSizeConfigurations(mObject); + } + } /** - * Retrieve the resource identifier for the given resource name. + * Change the configuration used when retrieving resources. Not for use by + * applications. + * @hide */ - /*package*/ native final int getResourceIdentifier(String name, - String defType, - String defPackage); + public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation, + int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, + int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, + int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) { + synchronized (this) { + ensureValidLocked(); + nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density, + keyboard, keyboardHidden, navigation, screenWidth, screenHeight, + smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode, + colorMode, majorVersion); + } + } - /*package*/ native final String getResourceName(int resid); - /*package*/ native final String getResourcePackageName(int resid); - /*package*/ native final String getResourceTypeName(int resid); - /*package*/ native final String getResourceEntryName(int resid); - - private native final long openAsset(String fileName, int accessMode); - private final native ParcelFileDescriptor openAssetFd(String fileName, - long[] outOffsets) throws IOException; - private native final long openNonAssetNative(int cookie, String fileName, - int accessMode); - private native ParcelFileDescriptor openNonAssetFdNative(int cookie, - String fileName, long[] outOffsets) throws IOException; - private native final void destroyAsset(long asset); - private native final int readAssetChar(long asset); - private native final int readAsset(long asset, byte[] b, int off, int len); - private native final long seekAsset(long asset, long offset, int whence); - private native final long getAssetLength(long asset); - private native final long getAssetRemainingLength(long asset); - - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceValue(int ident, short density, TypedValue outValue, - boolean resolve); - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, - boolean resolve); - /*package*/ static final int STYLE_NUM_ENTRIES = 6; - /*package*/ static final int STYLE_TYPE = 0; - /*package*/ static final int STYLE_DATA = 1; - /*package*/ static final int STYLE_ASSET_COOKIE = 2; - /*package*/ static final int STYLE_RESOURCE_ID = 3; - - /* Offset within typed data array for native changingConfigurations. */ - static final int STYLE_CHANGING_CONFIGURATIONS = 4; - - /*package*/ static final int STYLE_DENSITY = 5; - /*package*/ native static final void applyStyle(long theme, - int defStyleAttr, int defStyleRes, long xmlParser, - int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress); - /*package*/ native static final boolean resolveAttrs(long theme, - int defStyleAttr, int defStyleRes, int[] inValues, - int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final boolean retrieveAttributes( - long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final int getArraySize(int resource); - /*package*/ native final int retrieveArray(int resource, int[] outValues); - private native final int getStringBlockCount(); - private native final long getNativeStringBlock(int block); - - /** - * {@hide} - */ - public native final String getCookieName(int cookie); - - /** - * {@hide} - */ - public native final SparseArray<String> getAssignedPackageIdentifiers(); - - /** - * {@hide} - */ - public native static final int getGlobalAssetCount(); - - /** - * {@hide} - */ - public native static final String getAssetAllocations(); - /** - * {@hide} + * @hide */ - public native static final int getGlobalAssetManagerCount(); - - private native final long newTheme(); - private native final void deleteTheme(long theme); - /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force); - /*package*/ native static final void copyTheme(long dest, long source); - /*package*/ native static final void clearTheme(long theme); - /*package*/ native static final int loadThemeAttributeValue(long theme, int ident, - TypedValue outValue, - boolean resolve); - /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix); - /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme); - - private native final long openXmlAssetNative(int cookie, String fileName); - - private native final String[] getArrayStringResource(int arrayRes); - private native final int[] getArrayStringInfo(int arrayRes); - /*package*/ native final int[] getArrayIntResource(int arrayRes); - /*package*/ native final int[] getStyleAttributes(int themeRes); - - private native final void init(boolean isSystem); - private native final void destroy(); - - private final void incRefsLocked(long id) { + public SparseArray<String> getAssignedPackageIdentifiers() { + synchronized (this) { + ensureValidLocked(); + return nativeGetAssignedPackageIdentifiers(mObject); + } + } + + @GuardedBy("this") + private void incRefsLocked(long id) { if (DEBUG_REFS) { if (mRefStacks == null) { - mRefStacks = new HashMap<Long, RuntimeException>(); + mRefStacks = new HashMap<>(); } RuntimeException ex = new RuntimeException(); ex.fillInStackTrace(); @@ -926,16 +1263,119 @@ public final class AssetManager implements AutoCloseable { } mNumRefs++; } - - private final void decRefsLocked(long id) { + + @GuardedBy("this") + private void decRefsLocked(long id) { if (DEBUG_REFS && mRefStacks != null) { mRefStacks.remove(id); } mNumRefs--; - //System.out.println("Dec streams: mNumRefs=" + mNumRefs - // + " mReleased=" + mReleased); - if (mNumRefs == 0) { - destroy(); + if (mNumRefs == 0 && mObject != 0) { + nativeDestroy(mObject); + mObject = 0; + mApkAssets = sEmptyApkAssets; } } + + // AssetManager setup native methods. + private static native long nativeCreate(); + private static native void nativeDestroy(long ptr); + private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets, + boolean invalidateCaches); + private static native void nativeSetConfiguration(long ptr, int mcc, int mnc, + @Nullable String locale, int orientation, int touchscreen, int density, int keyboard, + int keyboardHidden, int navigation, int screenWidth, int screenHeight, + int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, + int uiMode, int colorMode, int majorVersion); + private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers( + long ptr); + + // File native methods. + private static native @Nullable String[] nativeList(long ptr, @NonNull String path) + throws IOException; + private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode); + private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr, + @NonNull String fileName, long[] outOffsets) throws IOException; + private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName, + int accessMode); + private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie, + @NonNull String fileName, @NonNull long[] outOffsets) throws IOException; + private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName); + + // Primitive resource native methods. + private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density, + @NonNull TypedValue outValue, boolean resolveReferences); + private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId, + @NonNull TypedValue outValue); + + private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr, + @StyleRes int resId); + private static native @Nullable String[] nativeGetResourceStringArray(long ptr, + @ArrayRes int resId); + private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr, + @ArrayRes int resId); + private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId); + private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId); + private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId, + @NonNull int[] outValues); + + // Resource name/ID native methods. + private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name, + @Nullable String defType, @Nullable String defPackage); + private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid); + private static native @Nullable String nativeGetResourcePackageName(long ptr, + @AnyRes int resid); + private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid); + private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid); + private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem); + private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr); + + // Style attribute retrieval native methods. + private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr, + @StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs, + long outValuesAddress, long outIndicesAddress); + private static native boolean nativeResolveAttrs(long ptr, long themePtr, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues, + @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices); + private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr, + @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices); + + // Theme related native methods + private static native long nativeThemeCreate(long ptr); + private static native void nativeThemeDestroy(long themePtr); + private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId, + boolean force); + static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr); + static native void nativeThemeClear(long themePtr); + private static native int nativeThemeGetAttributeValue(long ptr, long themePtr, + @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve); + private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag, + String prefix); + static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr); + + // AssetInputStream related native methods. + private static native void nativeAssetDestroy(long assetPtr); + private static native int nativeAssetReadChar(long assetPtr); + private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len); + private static native long nativeAssetSeek(long assetPtr, long offset, int whence); + private static native long nativeAssetGetLength(long assetPtr); + private static native long nativeAssetGetRemainingLength(long assetPtr); + + private static native void nativeVerifySystemIdmaps(); + + // Global debug native methods. + /** + * @hide + */ + public static native int getGlobalAssetCount(); + + /** + * @hide + */ + public static native String getAssetAllocations(); + + /** + * @hide + */ + public static native int getGlobalAssetManagerCount(); } diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java index eb309799..19b5c45f 100644 --- a/android/content/res/Configuration.java +++ b/android/content/res/Configuration.java @@ -16,15 +16,29 @@ package android.content.res; +import static android.content.ConfigurationProto.COLOR_MODE; import static android.content.ConfigurationProto.DENSITY_DPI; import static android.content.ConfigurationProto.FONT_SCALE; +import static android.content.ConfigurationProto.HARD_KEYBOARD_HIDDEN; +import static android.content.ConfigurationProto.KEYBOARD; +import static android.content.ConfigurationProto.KEYBOARD_HIDDEN; +import static android.content.ConfigurationProto.LOCALES; +import static android.content.ConfigurationProto.MCC; +import static android.content.ConfigurationProto.MNC; +import static android.content.ConfigurationProto.NAVIGATION; +import static android.content.ConfigurationProto.NAVIGATION_HIDDEN; import static android.content.ConfigurationProto.ORIENTATION; import static android.content.ConfigurationProto.SCREEN_HEIGHT_DP; import static android.content.ConfigurationProto.SCREEN_LAYOUT; import static android.content.ConfigurationProto.SCREEN_WIDTH_DP; import static android.content.ConfigurationProto.SMALLEST_SCREEN_WIDTH_DP; +import static android.content.ConfigurationProto.TOUCHSCREEN; import static android.content.ConfigurationProto.UI_MODE; import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; +import static android.content.ResourcesConfigurationProto.CONFIGURATION; +import static android.content.ResourcesConfigurationProto.SCREEN_HEIGHT_PX; +import static android.content.ResourcesConfigurationProto.SCREEN_WIDTH_PX; +import static android.content.ResourcesConfigurationProto.SDK_VERSION; import android.annotation.IntDef; import android.annotation.NonNull; @@ -38,6 +52,7 @@ import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.proto.ProtoOutputStream; import android.view.View; @@ -1076,7 +1091,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { final long token = protoOutputStream.start(fieldId); protoOutputStream.write(FONT_SCALE, fontScale); + protoOutputStream.write(MCC, mcc); + protoOutputStream.write(MNC, mnc); + mLocaleList.writeToProto(protoOutputStream, LOCALES); protoOutputStream.write(SCREEN_LAYOUT, screenLayout); + protoOutputStream.write(COLOR_MODE, colorMode); + protoOutputStream.write(TOUCHSCREEN, touchscreen); + protoOutputStream.write(KEYBOARD, keyboard); + protoOutputStream.write(KEYBOARD_HIDDEN, keyboardHidden); + protoOutputStream.write(HARD_KEYBOARD_HIDDEN, hardKeyboardHidden); + protoOutputStream.write(NAVIGATION, navigation); + protoOutputStream.write(NAVIGATION_HIDDEN, navigationHidden); protoOutputStream.write(ORIENTATION, orientation); protoOutputStream.write(UI_MODE, uiMode); protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp); @@ -1088,6 +1113,36 @@ public final class Configuration implements Parcelable, Comparable<Configuration } /** + * Write full {@link android.content.ResourcesConfigurationProto} to protocol buffer output + * stream. + * + * @param protoOutputStream Stream to write the Configuration object to. + * @param fieldId Field Id of the Configuration as defined in the parent message + * @param metrics Current display information + * @hide + */ + public void writeResConfigToProto(ProtoOutputStream protoOutputStream, long fieldId, + DisplayMetrics metrics) { + final int width, height; + if (metrics.widthPixels >= metrics.heightPixels) { + width = metrics.widthPixels; + height = metrics.heightPixels; + } else { + //noinspection SuspiciousNameCombination + width = metrics.heightPixels; + //noinspection SuspiciousNameCombination + height = metrics.widthPixels; + } + + final long token = protoOutputStream.start(fieldId); + writeToProto(protoOutputStream, CONFIGURATION); + protoOutputStream.write(SDK_VERSION, Build.VERSION.RESOURCES_SDK_INT); + protoOutputStream.write(SCREEN_WIDTH_PX, width); + protoOutputStream.write(SCREEN_HEIGHT_PX, height); + protoOutputStream.end(token); + } + + /** * Convert the UI mode to a human readable format. * @hide */ @@ -1558,12 +1613,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(mnc); fixUpLocaleList(); - final int localeListSize = mLocaleList.size(); - dest.writeInt(localeListSize); - for (int i = 0; i < localeListSize; ++i) { - final Locale l = mLocaleList.get(i); - dest.writeString(l.toLanguageTag()); - } + dest.writeParcelable(mLocaleList, flags); if(userSetLocale) { dest.writeInt(1); @@ -1597,12 +1647,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration mcc = source.readInt(); mnc = source.readInt(); - final int localeListSize = source.readInt(); - final Locale[] localeArray = new Locale[localeListSize]; - for (int i = 0; i < localeListSize; ++i) { - localeArray[i] = Locale.forLanguageTag(source.readString()); - } - mLocaleList = new LocaleList(localeArray); + mLocaleList = source.readParcelable(LocaleList.class.getClassLoader()); locale = mLocaleList.get(0); userSetLocale = (source.readInt()==1); @@ -1925,11 +1970,21 @@ public final class Configuration implements Parcelable, Comparable<Configuration /** * Returns a string representation of the configuration that can be parsed - * by build tools (like AAPT). + * by build tools (like AAPT), without display metrics included * * @hide */ public static String resourceQualifierString(Configuration config) { + return resourceQualifierString(config, null); + } + + /** + * Returns a string representation of the configuration that can be parsed + * by build tools (like AAPT). + * + * @hide + */ + public static String resourceQualifierString(Configuration config, DisplayMetrics metrics) { ArrayList<String> parts = new ArrayList<String>(); if (config.mcc != 0) { @@ -2177,6 +2232,20 @@ public final class Configuration implements Parcelable, Comparable<Configuration break; } + if (metrics != null) { + final int width, height; + if (metrics.widthPixels >= metrics.heightPixels) { + width = metrics.widthPixels; + height = metrics.heightPixels; + } else { + //noinspection SuspiciousNameCombination + width = metrics.heightPixels; + //noinspection SuspiciousNameCombination + height = metrics.widthPixels; + } + parts.add(width + "x" + height); + } + parts.add("v" + Build.VERSION.RESOURCES_SDK_INT); return TextUtils.join("-", parts); } diff --git a/android/content/res/Resources.java b/android/content/res/Resources.java index e173653c..c58cde00 100644 --- a/android/content/res/Resources.java +++ b/android/content/res/Resources.java @@ -590,7 +590,7 @@ public class Resources { */ @NonNull public int[] getIntArray(@ArrayRes int id) throws NotFoundException { - int[] res = mResourcesImpl.getAssets().getArrayIntResource(id); + int[] res = mResourcesImpl.getAssets().getResourceIntArray(id); if (res != null) { return res; } @@ -613,13 +613,13 @@ public class Resources { @NonNull public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException { final ResourcesImpl impl = mResourcesImpl; - int len = impl.getAssets().getArraySize(id); + int len = impl.getAssets().getResourceArraySize(id); if (len < 0) { throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); } TypedArray array = TypedArray.obtain(this, len); - array.mLength = impl.getAssets().retrieveArray(id, array.mData); + array.mLength = impl.getAssets().getResourceArray(id, array.mData); array.mIndices[0] = 0; return array; @@ -847,6 +847,7 @@ public class Resources { * @see #getDrawableForDensity(int, int, Theme) * @deprecated Use {@link #getDrawableForDensity(int, int, Theme)} instead. */ + @Nullable @Deprecated public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException { @@ -864,11 +865,13 @@ public class Resources { * found in {@link DisplayMetrics}. A value of 0 means to use the * density returned from {@link #getConfiguration()}. * This is equivalent to calling {@link #getDrawable(int, Theme)}. - * @param theme The theme used to style the drawable attributes, may be {@code null}. + * @param theme The theme used to style the drawable attributes, may be {@code null} if the + * drawable cannot be decoded. * @return Drawable An object that can be used to draw this resource. * @throws NotFoundException Throws NotFoundException if the given ID does * not exist. */ + @Nullable public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) { final TypedValue value = obtainTempTypedValue(); try { @@ -980,7 +983,7 @@ public class Resources { * or multiple colors that can be selected based on a state. * @deprecated Use {@link #getColorStateList(int, Theme)} instead. */ - @Nullable + @NonNull @Deprecated public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException { final ColorStateList csl = getColorStateList(id, null); @@ -1011,7 +1014,7 @@ public class Resources { * @return A themed ColorStateList object containing either a single solid * color or multiple colors that can be selected based on a state. */ - @Nullable + @NonNull public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme) throws NotFoundException { final TypedValue value = obtainTempTypedValue(); @@ -1024,7 +1027,7 @@ public class Resources { } } - @Nullable + @NonNull ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme) throws NotFoundException { return mResourcesImpl.loadColorStateList(this, value, id, theme); @@ -1033,7 +1036,7 @@ public class Resources { /** * @hide */ - @Nullable + @NonNull public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, @Nullable Theme theme) { return mResourcesImpl.loadComplexColor(this, value, id, theme); } @@ -1139,6 +1142,7 @@ public class Resources { * * @see #getXml */ + @NonNull public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException { return loadXmlResourceParser(id, "layout"); } @@ -1163,6 +1167,7 @@ public class Resources { * * @see #getXml */ + @NonNull public XmlResourceParser getAnimation(@AnimatorRes @AnimRes int id) throws NotFoundException { return loadXmlResourceParser(id, "anim"); } @@ -1188,6 +1193,7 @@ public class Resources { * * @see android.util.AttributeSet */ + @NonNull public XmlResourceParser getXml(@XmlRes int id) throws NotFoundException { return loadXmlResourceParser(id, "xml"); } @@ -1203,8 +1209,8 @@ public class Resources { * @return InputStream Access to the resource data. * * @throws NotFoundException Throws NotFoundException if the given ID does not exist. - * */ + @NonNull public InputStream openRawResource(@RawRes int id) throws NotFoundException { final TypedValue value = obtainTempTypedValue(); try { @@ -1261,6 +1267,7 @@ public class Resources { * * @throws NotFoundException Throws NotFoundException if the given ID does not exist. */ + @NonNull public InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException { return mResourcesImpl.openRawResource(id, value); @@ -1789,8 +1796,7 @@ public class Resources { // out the attributes from the XML file (applying type information // contained in the resources and such). XmlBlock.Parser parser = (XmlBlock.Parser)set; - mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs, - array.mData, array.mIndices); + mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices); array.mXml = parser; diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java index 97cb78bc..8c980677 100644 --- a/android/content/res/ResourcesImpl.java +++ b/android/content/res/ResourcesImpl.java @@ -27,9 +27,11 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; +import android.content.res.AssetManager.AssetInputStream; import android.content.res.Configuration.NativeConfig; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; +import android.graphics.ImageDecoder; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -168,7 +170,6 @@ public class ResourcesImpl { mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo()); - mAssets.ensureStringBlocks(); } public DisplayAdjustments getDisplayAdjustments() { @@ -627,7 +628,7 @@ public class ResourcesImpl { } else if (isColorDrawable) { dr = new ColorDrawable(value.data); } else { - dr = loadDrawableForCookie(wrapper, value, id, density, null); + dr = loadDrawableForCookie(wrapper, value, id, density); } // DrawableContainer' constant state has drawables instances. In order to leave the // constant state intact in the cache, we need to create a new DrawableContainer after @@ -752,10 +753,34 @@ public class ResourcesImpl { } /** + * Loads a Drawable from an encoded image stream, or null. + * + * This call will handle closing ais. + */ + @Nullable + private Drawable decodeImageDrawable(@NonNull AssetInputStream ais, + @NonNull Resources wrapper, @NonNull TypedValue value) { + ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais, + wrapper, value); + try { + return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> { + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + }); + } catch (IOException ioe) { + // This is okay. This may be something that ImageDecoder does not + // support, like SVG. + return null; + } + } + + /** * Loads a drawable from XML or resources stream. + * + * @return Drawable, or null if Drawable cannot be decoded. */ + @Nullable private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value, - int id, int density, @Nullable Resources.Theme theme) { + int id, int density) { if (value.string == null) { throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); @@ -774,22 +799,23 @@ public class ResourcesImpl { } } - // For prelaod tracing. + // For preload tracing. long startTime = 0; int startBitmapCount = 0; long startBitmapSize = 0; - int startDrwableCount = 0; + int startDrawableCount = 0; if (TRACE_FOR_DETAILED_PRELOAD) { startTime = System.nanoTime(); startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps; startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize; - startDrwableCount = sPreloadTracingNumLoadedDrawables; + startDrawableCount = sPreloadTracingNumLoadedDrawables; } if (DEBUG_LOAD) { Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file); } + final Drawable dr; Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); @@ -804,13 +830,13 @@ public class ResourcesImpl { if (file.endsWith(".xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable"); - dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme); + dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null); rp.close(); } else { final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); - dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); - is.close(); + AssetInputStream ais = (AssetInputStream) is; + dr = decodeImageDrawable(ais, wrapper, value); } } finally { stack.pop(); @@ -834,7 +860,7 @@ public class ResourcesImpl { final long loadedBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize; final int loadedDrawables = - sPreloadTracingNumLoadedDrawables - startDrwableCount; + sPreloadTracingNumLoadedDrawables - startDrawableCount; sPreloadTracingNumLoadedDrawables++; @@ -910,6 +936,7 @@ public class ResourcesImpl { * first try to load CSL from the cache. If not found, try to get from the constant state. * Last, parse the XML and generate the CSL. */ + @Nullable private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme, TypedValue value, int id) { final long key = (((long) value.assetCookie) << 32) | value.data; @@ -985,7 +1012,7 @@ public class ResourcesImpl { return complexColor; } - @Nullable + @NonNull ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id, Resources.Theme theme) throws NotFoundException { @@ -1043,9 +1070,10 @@ public class ResourcesImpl { * We deferred the parser creation to this function b/c we need to differentiate b/t gradient * and selector tag. * - * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content. + * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content, or + * {@code null} if the XML file is neither. */ - @Nullable + @NonNull private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme) { if (value.string == null) { @@ -1274,8 +1302,7 @@ public class ResourcesImpl { void applyStyle(int resId, boolean force) { synchronized (mKey) { - AssetManager.applyThemeStyle(mTheme, resId, force); - + mAssets.applyStyleToTheme(mTheme, resId, force); mThemeResId = resId; mKey.append(resId, force); } @@ -1284,7 +1311,7 @@ public class ResourcesImpl { void setTo(ThemeImpl other) { synchronized (mKey) { synchronized (other.mKey) { - AssetManager.copyTheme(mTheme, other.mTheme); + AssetManager.nativeThemeCopy(mTheme, other.mTheme); mThemeResId = other.mThemeResId; mKey.setTo(other.getKey()); @@ -1307,12 +1334,10 @@ public class ResourcesImpl { // out the attributes from the XML file (applying type information // contained in the resources and such). final XmlBlock.Parser parser = (XmlBlock.Parser) set; - AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, - parser != null ? parser.mParseState : 0, - attrs, attrs.length, array.mDataAddress, array.mIndicesAddress); + mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs, + array.mDataAddress, array.mIndicesAddress); array.mTheme = wrapper; array.mXml = parser; - return array; } } @@ -1329,7 +1354,7 @@ public class ResourcesImpl { } final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); - AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); + mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); array.mTheme = wrapper; array.mXml = null; return array; @@ -1349,14 +1374,14 @@ public class ResourcesImpl { @Config int getChangingConfigurations() { synchronized (mKey) { final @NativeConfig int nativeChangingConfig = - AssetManager.getThemeChangingConfigurations(mTheme); + AssetManager.nativeThemeGetChangingConfigurations(mTheme); return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); } } public void dump(int priority, String tag, String prefix) { synchronized (mKey) { - AssetManager.dumpTheme(mTheme, priority, tag, prefix); + mAssets.dumpTheme(mTheme, priority, tag, prefix); } } @@ -1385,13 +1410,13 @@ public class ResourcesImpl { */ void rebase() { synchronized (mKey) { - AssetManager.clearTheme(mTheme); + AssetManager.nativeThemeClear(mTheme); // Reapply the same styles in the same order. for (int i = 0; i < mKey.mCount; i++) { final int resId = mKey.mResId[i]; final boolean force = mKey.mForce[i]; - AssetManager.applyThemeStyle(mTheme, resId, force); + mAssets.applyStyleToTheme(mTheme, resId, force); } } } diff --git a/android/content/res/TypedArray.java b/android/content/res/TypedArray.java index f33c7516..cbb3c6df 100644 --- a/android/content/res/TypedArray.java +++ b/android/content/res/TypedArray.java @@ -61,6 +61,15 @@ public class TypedArray { return attrs; } + // STYLE_ prefixed constants are offsets within the typed data array. + static final int STYLE_NUM_ENTRIES = 6; + static final int STYLE_TYPE = 0; + static final int STYLE_DATA = 1; + static final int STYLE_ASSET_COOKIE = 2; + static final int STYLE_RESOURCE_ID = 3; + static final int STYLE_CHANGING_CONFIGURATIONS = 4; + static final int STYLE_DENSITY = 5; + private final Resources mResources; private DisplayMetrics mMetrics; private AssetManager mAssets; @@ -78,7 +87,7 @@ public class TypedArray { private void resize(int len) { mLength = len; - final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES; + final int dataLen = len * STYLE_NUM_ENTRIES; final int indicesLen = len + 1; final VMRuntime runtime = VMRuntime.getRuntime(); if (mDataAddress == 0 || mData.length < dataLen) { @@ -166,9 +175,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -203,9 +212,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -242,14 +251,13 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_STRING) { - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]).toString(); + return mXml.getPooledString(data[index + STYLE_DATA]).toString(); } } return null; @@ -274,11 +282,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); if ((changingConfigs & ~allowedChangingConfigs) != 0) { return null; } @@ -320,14 +328,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA] != 0; + return data[index + STYLE_DATA] != 0; } final TypedValue v = mValue; @@ -359,14 +367,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -396,16 +404,16 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FLOAT) { - return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); + return Float.intBitsToFloat(data[index + STYLE_DATA]); } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -446,15 +454,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; if (getValueAt(index, value)) { @@ -498,7 +506,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -533,7 +541,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -564,15 +572,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -612,15 +620,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimension( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -661,15 +668,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelOffset( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -711,15 +717,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -755,16 +760,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -795,15 +799,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } return defValue; @@ -833,15 +836,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FRACTION) { - return TypedValue.complexToFraction( - data[index+AssetManager.STYLE_DATA], base, pbase); + return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -874,10 +876,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) { - final int resid = data[index+AssetManager.STYLE_RESOURCE_ID]; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) { + final int resid = data[index + STYLE_RESOURCE_ID]; if (resid != 0) { return resid; } @@ -902,10 +904,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { - return data[index + AssetManager.STYLE_DATA]; + if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { + return data[index + STYLE_DATA]; } return defValue; } @@ -939,7 +941,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -975,7 +977,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -1006,7 +1008,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return mResources.getTextArray(value.resourceId); } return null; @@ -1027,7 +1029,7 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue); + return getValueAt(index * STYLE_NUM_ENTRIES, outValue); } /** @@ -1043,8 +1045,8 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; - return mData[index + AssetManager.STYLE_TYPE]; + index *= STYLE_NUM_ENTRIES; + return mData[index + STYLE_TYPE]; } /** @@ -1063,9 +1065,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL; } @@ -1084,11 +1086,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL - || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; + || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; } /** @@ -1109,7 +1111,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return value; } return null; @@ -1181,16 +1183,16 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { + final int index = i * STYLE_NUM_ENTRIES; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { // Not an attribute, ignore. continue; } // Null the entry so that we can safely call getZzz(). - data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL; + data[index + STYLE_TYPE] = TypedValue.TYPE_NULL; - final int attr = data[index + AssetManager.STYLE_DATA]; + final int attr = data[index + STYLE_DATA]; if (attr == 0) { // Useless data, ignore. continue; @@ -1231,45 +1233,44 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - final int type = data[index + AssetManager.STYLE_TYPE]; + final int index = i * STYLE_NUM_ENTRIES; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { continue; } changingConfig |= ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); } return changingConfig; } private boolean getValueAt(int index, TypedValue outValue) { final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return false; } outValue.type = type; - outValue.data = data[index+AssetManager.STYLE_DATA]; - outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; - outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID]; + outValue.data = data[index + STYLE_DATA]; + outValue.assetCookie = data[index + STYLE_ASSET_COOKIE]; + outValue.resourceId = data[index + STYLE_RESOURCE_ID]; outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); - outValue.density = data[index+AssetManager.STYLE_DENSITY]; + data[index + STYLE_CHANGING_CONFIGURATIONS]); + outValue.density = data[index + STYLE_DENSITY]; outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null; return true; } private CharSequence loadStringValueAt(int index) { final int[] data = mData; - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { if (mXml != null) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]); + return mXml.getPooledString(data[index + STYLE_DATA]); } return null; } - return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]); + return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]); } /** @hide */ diff --git a/android/content/res/XmlBlock.java b/android/content/res/XmlBlock.java index e6b95741..d4ccffb8 100644 --- a/android/content/res/XmlBlock.java +++ b/android/content/res/XmlBlock.java @@ -16,6 +16,7 @@ package android.content.res; +import android.annotation.Nullable; import android.util.TypedValue; import com.android.internal.util.XmlUtils; @@ -33,7 +34,7 @@ import java.io.Reader; * * {@hide} */ -final class XmlBlock { +final class XmlBlock implements AutoCloseable { private static final boolean DEBUG=false; public XmlBlock(byte[] data) { @@ -48,6 +49,7 @@ final class XmlBlock { mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } + @Override public void close() { synchronized (this) { if (mOpen) { @@ -478,13 +480,13 @@ final class XmlBlock { * are doing! The given native object must exist for the entire lifetime * of this newly creating XmlBlock. */ - XmlBlock(AssetManager assets, long xmlBlock) { + XmlBlock(@Nullable AssetManager assets, long xmlBlock) { mAssets = assets; mNative = xmlBlock; mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false); } - private final AssetManager mAssets; + private @Nullable final AssetManager mAssets; private final long mNative; /*package*/ final StringBlock mStrings; private boolean mOpen = true; diff --git a/android/content/res/XmlResourceParser.java b/android/content/res/XmlResourceParser.java index 6be9b9eb..86f4ba61 100644 --- a/android/content/res/XmlResourceParser.java +++ b/android/content/res/XmlResourceParser.java @@ -27,6 +27,8 @@ import org.xmlpull.v1.XmlPullParser; * it is done reading the resource. */ public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable { + String getAttributeNamespace (int index); + /** * Close this parser. Calls on the interface are no longer valid after this call. */ |