Add API 31 sources
Test: None
Change-Id: Ie45894f7a232b2a15e2439b2527ca1813f334cc5
diff --git a/android/content/AbstractThreadedSyncAdapter.java b/android/content/AbstractThreadedSyncAdapter.java
new file mode 100644
index 0000000..a086a30
--- /dev/null
+++ b/android/content/AbstractThreadedSyncAdapter.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2009 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 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;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
+ * If a sync operation is already in progress when a sync request is received, an error will be
+ * returned to the new request and the existing request will be allowed to continue.
+ * However if there is no sync in progress then a thread will be spawned and {@link #onPerformSync}
+ * will be invoked on that thread.
+ * <p>
+ * Syncs can be cancelled at any time by the framework. For example a sync that was not
+ * user-initiated and lasts longer than 30 minutes will be considered timed-out and cancelled.
+ * Similarly the framework will attempt to determine whether or not an adapter is making progress
+ * by monitoring its network activity over the course of a minute. If the network traffic over this
+ * window is close enough to zero the sync will be cancelled. You can also request the sync be
+ * cancelled via {@link ContentResolver#cancelSync(Account, String)} or
+ * {@link ContentResolver#cancelSync(SyncRequest)}.
+ * <p>
+ * A sync is cancelled by issuing a {@link Thread#interrupt()} on the syncing thread. <strong>Either
+ * your code in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)}
+ * must check {@link Thread#interrupted()}, or you you must override one of
+ * {@link #onSyncCanceled(Thread)}/{@link #onSyncCanceled()}</strong> (depending on whether or not
+ * your adapter supports syncing of multiple accounts in parallel). If your adapter does not
+ * respect the cancel issued by the framework you run the risk of your app's entire process being
+ * killed.
+ * <p>
+ * In order to be a sync adapter one must extend this class, provide implementations for the
+ * abstract methods and write a service that returns the result of {@link #getSyncAdapterBinder()}
+ * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked
+ * with an intent with action <code>android.content.SyncAdapter</code>. This service
+ * must specify the following intent filter and metadata tags in its AndroidManifest.xml file
+ * <pre>
+ * <intent-filter>
+ * <action android:name="android.content.SyncAdapter" />
+ * </intent-filter>
+ * <meta-data android:name="android.content.SyncAdapter"
+ * android:resource="@xml/syncadapter" />
+ * </pre>
+ * The <code>android:resource</code> attribute must point to a resource that looks like:
+ * <pre>
+ * <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:contentAuthority="authority"
+ * android:accountType="accountType"
+ * android:userVisible="true|false"
+ * android:supportsUploading="true|false"
+ * android:allowParallelSyncs="true|false"
+ * android:isAlwaysSyncable="true|false"
+ * android:syncAdapterSettingsAction="ACTION_OF_SETTINGS_ACTIVITY"
+ * />
+ * </pre>
+ * <ul>
+ * <li>The <code>android:contentAuthority</code> and <code>android:accountType</code> attributes
+ * indicate which content authority and for which account types this sync adapter serves.
+ * <li><code>android:userVisible</code> defaults to true and controls whether or not this sync
+ * adapter shows up in the Sync Settings screen.
+ * <li><code>android:supportsUploading</code> defaults
+ * to true and if true an upload-only sync will be requested for all syncadapters associated
+ * with an authority whenever that authority's content provider does a
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
+ * with syncToNetwork set to true.
+ * <li><code>android:allowParallelSyncs</code> defaults to false and if true indicates that
+ * the sync adapter can handle syncs for multiple accounts at the same time. Otherwise
+ * the SyncManager will wait until the sync adapter is not in use before requesting that
+ * it sync an account's data.
+ * <li><code>android:isAlwaysSyncable</code> defaults to false and if true tells the SyncManager
+ * to initialize the isSyncable state to 1 for that sync adapter for each account that is added.
+ * <li><code>android:syncAdapterSettingsAction</code> defaults to null and if supplied it
+ * specifies an Intent action of an activity that can be used to adjust the sync adapter's
+ * sync settings. The activity must live in the same package as the sync adapter.
+ * </ul>
+ */
+public abstract class AbstractThreadedSyncAdapter {
+ private static final String TAG = "SyncAdapter";
+
+ /**
+ * Kernel event log tag. Also listed in data/etc/event-log-tags.
+ * @deprecated Private constant. May go away in the next release.
+ */
+ @Deprecated
+ public static final int LOG_SYNC_DETAILS = 2743;
+
+ private static final boolean ENABLE_LOG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final AtomicInteger mNumSyncStarts;
+ private final ISyncAdapterImpl mISyncAdapterImpl;
+
+ // all accesses to this member variable must be synchronized on mSyncThreadLock
+ private final HashMap<Account, SyncThread> mSyncThreads = new HashMap<Account, SyncThread>();
+ private final Object mSyncThreadLock = new Object();
+
+ private final boolean mAutoInitialize;
+ private boolean mAllowParallelSyncs;
+
+ /**
+ * Creates an {@link AbstractThreadedSyncAdapter}.
+ * @param context the {@link android.content.Context} that this is running within.
+ * @param autoInitialize if true then sync requests that have
+ * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
+ * {@link AbstractThreadedSyncAdapter} by calling
+ * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
+ * is currently set to <0.
+ */
+ public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
+ this(context, autoInitialize, false /* allowParallelSyncs */);
+ }
+
+ /**
+ * Creates an {@link AbstractThreadedSyncAdapter}.
+ * @param context the {@link android.content.Context} that this is running within.
+ * @param autoInitialize if true then sync requests that have
+ * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
+ * {@link AbstractThreadedSyncAdapter} by calling
+ * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
+ * is currently set to <0.
+ * @param allowParallelSyncs if true then allow syncs for different accounts to run
+ * at the same time, each in their own thread. This must be consistent with the setting
+ * in the SyncAdapter's configuration file.
+ */
+ public AbstractThreadedSyncAdapter(Context context,
+ boolean autoInitialize, boolean allowParallelSyncs) {
+ mContext = context;
+ mISyncAdapterImpl = new ISyncAdapterImpl();
+ mNumSyncStarts = new AtomicInteger(0);
+ mAutoInitialize = autoInitialize;
+ mAllowParallelSyncs = allowParallelSyncs;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ private Account toSyncKey(Account account) {
+ if (mAllowParallelSyncs) {
+ return account;
+ } else {
+ return null;
+ }
+ }
+
+ 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) {
+ if (extras != null) {
+ extras.size(); // Unparcel so its toString() will show the contents.
+ }
+ Log.d(TAG, "startSync() start " + authority + " " + account + " " + extras);
+ }
+ try {
+ final SyncContext syncContextClient = new SyncContext(syncContext);
+
+ boolean alreadyInProgress;
+ // synchronize to make sure that mSyncThreads doesn't change between when we
+ // check it and when we use it
+ final Account threadsKey = toSyncKey(account);
+ synchronized (mSyncThreadLock) {
+ if (!mSyncThreads.containsKey(threadsKey)) {
+ if (mAutoInitialize
+ && extras != null
+ && extras.getBoolean(
+ ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
+ try {
+ if (ContentResolver.getIsSyncable(account, authority) < 0) {
+ ContentResolver.setIsSyncable(account, authority, 1);
+ }
+ } finally {
+ syncContextClient.onFinished(new SyncResult());
+ }
+ return;
+ }
+ SyncThread syncThread = new SyncThread(
+ "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
+ syncContextClient, authority, account, extras);
+ mSyncThreads.put(threadsKey, syncThread);
+ syncThread.start();
+ alreadyInProgress = false;
+ } else {
+ if (ENABLE_LOG) {
+ Log.d(TAG, " alreadyInProgress");
+ }
+ alreadyInProgress = true;
+ }
+ }
+
+ // do this outside since we don't want to call back into the syncContext while
+ // holding the synchronization lock
+ if (alreadyInProgress) {
+ syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
+ }
+ } catch (RuntimeException | Error th) {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "startSync() caught exception", th);
+ }
+ throw th;
+ } finally {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "startSync() finishing");
+ }
+ }
+ }
+
+ @Override
+ public void cancelSync(ISyncContext syncContext) {
+ try {
+ // synchronize to make sure that mSyncThreads doesn't change between when we
+ // check it and when we use it
+ SyncThread info = null;
+ synchronized (mSyncThreadLock) {
+ for (SyncThread current : mSyncThreads.values()) {
+ if (current.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
+ info = current;
+ break;
+ }
+ }
+ }
+ if (info != null) {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "cancelSync() " + info.mAuthority + " " + info.mAccount);
+ }
+ if (mAllowParallelSyncs) {
+ onSyncCanceled(info);
+ } else {
+ onSyncCanceled();
+ }
+ } else {
+ if (ENABLE_LOG) {
+ Log.w(TAG, "cancelSync() unknown context");
+ }
+ }
+ } catch (RuntimeException | Error th) {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "cancelSync() caught exception", th);
+ }
+ throw th;
+ } finally {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "cancelSync() finishing");
+ }
+ }
+ }
+ }
+
+ /**
+ * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
+ * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
+ * this thread in order to cancel the sync.
+ */
+ private class SyncThread extends Thread {
+ private final SyncContext mSyncContext;
+ private final String mAuthority;
+ private final Account mAccount;
+ private final Bundle mExtras;
+ private final Account mThreadsKey;
+
+ private SyncThread(String name, SyncContext syncContext, String authority,
+ Account account, Bundle extras) {
+ super(name);
+ mSyncContext = syncContext;
+ mAuthority = authority;
+ mAccount = account;
+ mExtras = extras;
+ mThreadsKey = toSyncKey(account);
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ if (ENABLE_LOG) {
+ Log.d(TAG, "Thread started");
+ }
+
+ // Trace this sync instance. Note, conceptually this should be in
+ // SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique
+ // threads in order to track overlapping operations, so we'll do it here for now.
+ Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority);
+
+ SyncResult syncResult = new SyncResult();
+ ContentProviderClient provider = null;
+ try {
+ if (isCanceled()) {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "Already canceled");
+ }
+ return;
+ }
+ if (ENABLE_LOG) {
+ Log.d(TAG, "Calling onPerformSync...");
+ }
+
+ provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
+ if (provider != null) {
+ AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
+ mAuthority, provider, syncResult);
+ } else {
+ syncResult.databaseError = true;
+ }
+
+ if (ENABLE_LOG) {
+ Log.d(TAG, "onPerformSync done");
+ }
+
+ } catch (SecurityException e) {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "SecurityException", e);
+ }
+ AbstractThreadedSyncAdapter.this.onSecurityException(mAccount, mExtras,
+ mAuthority, syncResult);
+ syncResult.databaseError = true;
+ } catch (RuntimeException | Error th) {
+ if (ENABLE_LOG) {
+ Log.d(TAG, "caught exception", th);
+ }
+ throw th;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
+
+ if (provider != null) {
+ provider.release();
+ }
+ if (!isCanceled()) {
+ mSyncContext.onFinished(syncResult);
+ }
+ // synchronize so that the assignment will be seen by other threads
+ // that also synchronize accesses to mSyncThreads
+ synchronized (mSyncThreadLock) {
+ mSyncThreads.remove(mThreadsKey);
+ }
+
+ if (ENABLE_LOG) {
+ Log.d(TAG, "Thread finished");
+ }
+ }
+ }
+
+ private boolean isCanceled() {
+ return Thread.currentThread().isInterrupted();
+ }
+ }
+
+ /**
+ * @return a reference to the IBinder of the SyncAdapter service.
+ */
+ public final IBinder getSyncAdapterBinder() {
+ return mISyncAdapterImpl.asBinder();
+ }
+
+ /**
+ * 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.
+ *
+ * @param account the account that should be synced
+ * @param extras SyncAdapter-specific parameters
+ * @param authority the authority of this sync request
+ * @param provider a ContentProviderClient that points to the ContentProvider for this
+ * authority
+ * @param syncResult SyncAdapter-specific parameters
+ */
+ public abstract void onPerformSync(Account account, Bundle extras,
+ String authority, ContentProviderClient provider, SyncResult syncResult);
+
+ /**
+ * Report that there was a security exception when opening the content provider
+ * prior to calling {@link #onPerformSync}. This will be treated as a sync
+ * database failure.
+ *
+ * @param account the account that attempted to sync
+ * @param extras SyncAdapter-specific parameters
+ * @param authority the authority of the failed sync request
+ * @param syncResult SyncAdapter-specific parameters
+ */
+ public void onSecurityException(Account account, Bundle extras,
+ String authority, SyncResult syncResult) {
+ }
+
+ /**
+ * Indicates that a sync operation has been canceled. This will be invoked on a separate
+ * thread than the sync thread and so you must consider the multi-threaded implications
+ * of the work that you do in this method.
+ * <p>
+ * This will only be invoked when the SyncAdapter indicates that it doesn't support
+ * parallel syncs.
+ */
+ public void onSyncCanceled() {
+ final SyncThread syncThread;
+ synchronized (mSyncThreadLock) {
+ syncThread = mSyncThreads.get(null);
+ }
+ if (syncThread != null) {
+ syncThread.interrupt();
+ }
+ }
+
+ /**
+ * Indicates that a sync operation has been canceled. This will be invoked on a separate
+ * thread than the sync thread and so you must consider the multi-threaded implications
+ * of the work that you do in this method.
+ * <p>
+ * This will only be invoked when the SyncAdapter indicates that it does support
+ * parallel syncs.
+ * @param thread the Thread of the sync that is to be canceled.
+ */
+ public void onSyncCanceled(Thread thread) {
+ thread.interrupt();
+ }
+}
diff --git a/android/content/ActivityNotFoundException.java b/android/content/ActivityNotFoundException.java
new file mode 100644
index 0000000..16149bb
--- /dev/null
+++ b/android/content/ActivityNotFoundException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * This exception is thrown when a call to {@link Context#startActivity} or
+ * one of its variants fails because an Activity can not be found to execute
+ * the given Intent.
+ */
+public class ActivityNotFoundException extends RuntimeException
+{
+ public ActivityNotFoundException()
+ {
+ }
+
+ public ActivityNotFoundException(String name)
+ {
+ super(name);
+ }
+};
+
diff --git a/android/content/ApexEnvironment.java b/android/content/ApexEnvironment.java
new file mode 100644
index 0000000..b4cc3c2
--- /dev/null
+++ b/android/content/ApexEnvironment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.NonNull;
+import android.annotation.SystemApi;
+import android.os.Environment;
+import android.os.UserHandle;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * Provides information about the environment for a particular APEX.
+ *
+ * @hide
+ */
+@SystemApi
+public class ApexEnvironment {
+
+ private static final String APEX_DATA = "apexdata";
+
+ /**
+ * Returns an ApexEnvironment instance for the APEX with the provided {@code apexModuleName}.
+ *
+ * <p>To preserve the safety and integrity of APEX modules, you must only obtain the
+ * ApexEnvironment for your specific APEX, and you <em>must never</em> attempt to obtain an
+ * ApexEnvironment for another APEX. Any coordination between APEXs must be performed through
+ * well-defined interfaces; attempting to directly read or write raw files belonging to another
+ * APEX will violate the hermetic storage requirements placed upon each module.
+ */
+ @NonNull
+ public static ApexEnvironment getApexEnvironment(@NonNull String apexModuleName) {
+ Objects.requireNonNull(apexModuleName, "apexModuleName cannot be null");
+ //TODO(b/141148175): Check that apexModuleName is an actual APEX name
+ return new ApexEnvironment(apexModuleName);
+ }
+
+ private final String mApexModuleName;
+
+ private ApexEnvironment(String apexModuleName) {
+ mApexModuleName = apexModuleName;
+ }
+
+ /**
+ * Returns the data directory for the APEX in device-encrypted, non-user-specific storage.
+ *
+ * <p>This directory is automatically created by the system for installed APEXes, and its
+ * contents will be rolled back if the APEX is rolled back.
+ */
+ @NonNull
+ public File getDeviceProtectedDataDir() {
+ return Environment.buildPath(
+ Environment.getDataMiscDirectory(), APEX_DATA, mApexModuleName);
+ }
+
+ /**
+ * Returns the data directory for the APEX in device-encrypted, user-specific storage for the
+ * specified {@code user}.
+ *
+ * <p>This directory is automatically created by the system for each user and for each installed
+ * APEX, and its contents will be rolled back if the APEX is rolled back.
+ */
+ @NonNull
+ public File getDeviceProtectedDataDirForUser(@NonNull UserHandle user) {
+ return Environment.buildPath(
+ Environment.getDataMiscDeDirectory(user.getIdentifier()), APEX_DATA,
+ mApexModuleName);
+ }
+
+ /**
+ * Returns the data directory for the APEX in credential-encrypted, user-specific storage for
+ * the specified {@code user}.
+ *
+ * <p>This directory is automatically created by the system for each user and for each installed
+ * APEX, and its contents will be rolled back if the APEX is rolled back.
+ */
+ @NonNull
+ public File getCredentialProtectedDataDirForUser(@NonNull UserHandle user) {
+ return Environment.buildPath(
+ Environment.getDataMiscCeDirectory(user.getIdentifier()), APEX_DATA,
+ mApexModuleName);
+ }
+}
diff --git a/android/content/AsyncQueryHandler.java b/android/content/AsyncQueryHandler.java
new file mode 100644
index 0000000..07da99d
--- /dev/null
+++ b/android/content/AsyncQueryHandler.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2007 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.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A helper class to help make handling asynchronous {@link ContentResolver}
+ * queries easier.
+ */
+public abstract class AsyncQueryHandler extends Handler {
+ private static final String TAG = "AsyncQuery";
+ private static final boolean localLOGV = false;
+
+ private static final int EVENT_ARG_QUERY = 1;
+ private static final int EVENT_ARG_INSERT = 2;
+ private static final int EVENT_ARG_UPDATE = 3;
+ private static final int EVENT_ARG_DELETE = 4;
+
+ /* package */ final WeakReference<ContentResolver> mResolver;
+
+ private static Looper sLooper = null;
+
+ private Handler mWorkerThreadHandler;
+
+ protected static final class WorkerArgs {
+ public Uri uri;
+ public Handler handler;
+ public String[] projection;
+ public String selection;
+ public String[] selectionArgs;
+ public String orderBy;
+ public Object result;
+ public Object cookie;
+ public ContentValues values;
+ }
+
+ protected class WorkerHandler extends Handler {
+ public WorkerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final ContentResolver resolver = mResolver.get();
+ if (resolver == null) return;
+
+ WorkerArgs args = (WorkerArgs) msg.obj;
+
+ int token = msg.what;
+ int event = msg.arg1;
+
+ switch (event) {
+ case EVENT_ARG_QUERY:
+ Cursor cursor;
+ try {
+ cursor = resolver.query(args.uri, args.projection,
+ args.selection, args.selectionArgs,
+ args.orderBy);
+ // Calling getCount() causes the cursor window to be filled,
+ // which will make the first access on the main thread a lot faster.
+ if (cursor != null) {
+ cursor.getCount();
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);
+ cursor = null;
+ }
+
+ args.result = cursor;
+ break;
+
+ case EVENT_ARG_INSERT:
+ args.result = resolver.insert(args.uri, args.values);
+ break;
+
+ case EVENT_ARG_UPDATE:
+ args.result = resolver.update(args.uri, args.values, args.selection,
+ args.selectionArgs);
+ break;
+
+ case EVENT_ARG_DELETE:
+ args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
+ break;
+ }
+
+ // passing the original token value back to the caller
+ // on top of the event values in arg1.
+ Message reply = args.handler.obtainMessage(token);
+ reply.obj = args;
+ reply.arg1 = msg.arg1;
+
+ if (localLOGV) {
+ Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
+ + ", reply.what=" + reply.what);
+ }
+
+ reply.sendToTarget();
+ }
+ }
+
+ public AsyncQueryHandler(ContentResolver cr) {
+ super();
+ mResolver = new WeakReference<ContentResolver>(cr);
+ synchronized (AsyncQueryHandler.class) {
+ if (sLooper == null) {
+ HandlerThread thread = new HandlerThread("AsyncQueryWorker");
+ thread.start();
+
+ sLooper = thread.getLooper();
+ }
+ }
+ mWorkerThreadHandler = createHandler(sLooper);
+ }
+
+ protected Handler createHandler(Looper looper) {
+ return new WorkerHandler(looper);
+ }
+
+ /**
+ * This method begins an asynchronous query. When the query is done
+ * {@link #onQueryComplete} is called.
+ *
+ * @param token A token passed into {@link #onQueryComplete} to identify
+ * the query.
+ * @param cookie An object that gets passed into {@link #onQueryComplete}
+ * @param uri The URI, using the content:// scheme, for the content to
+ * retrieve.
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is discouraged to prevent reading data
+ * from storage that isn't going to be used.
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null will
+ * return all rows for the given URI.
+ * @param selectionArgs You may include ?s in selection, which will be
+ * replaced by the values from selectionArgs, in the order that they
+ * appear in the selection. The values will be bound as Strings.
+ * @param orderBy How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use the
+ * default sort order, which may be unordered.
+ */
+ public void startQuery(int token, Object cookie, Uri uri,
+ String[] projection, String selection, String[] selectionArgs,
+ String orderBy) {
+ // Use the token as what so cancelOperations works properly
+ Message msg = mWorkerThreadHandler.obtainMessage(token);
+ msg.arg1 = EVENT_ARG_QUERY;
+
+ WorkerArgs args = new WorkerArgs();
+ args.handler = this;
+ args.uri = uri;
+ args.projection = projection;
+ args.selection = selection;
+ args.selectionArgs = selectionArgs;
+ args.orderBy = orderBy;
+ args.cookie = cookie;
+ msg.obj = args;
+
+ mWorkerThreadHandler.sendMessage(msg);
+ }
+
+ /**
+ * Attempts to cancel operation that has not already started. Note that
+ * there is no guarantee that the operation will be canceled. They still may
+ * result in a call to on[Query/Insert/Update/Delete]Complete after this
+ * call has completed.
+ *
+ * @param token The token representing the operation to be canceled.
+ * If multiple operations have the same token they will all be canceled.
+ */
+ public final void cancelOperation(int token) {
+ mWorkerThreadHandler.removeMessages(token);
+ }
+
+ /**
+ * This method begins an asynchronous insert. When the insert operation is
+ * done {@link #onInsertComplete} is called.
+ *
+ * @param token A token passed into {@link #onInsertComplete} to identify
+ * the insert operation.
+ * @param cookie An object that gets passed into {@link #onInsertComplete}
+ * @param uri the Uri passed to the insert operation.
+ * @param initialValues the ContentValues parameter passed to the insert operation.
+ */
+ public final void startInsert(int token, Object cookie, Uri uri,
+ ContentValues initialValues) {
+ // Use the token as what so cancelOperations works properly
+ Message msg = mWorkerThreadHandler.obtainMessage(token);
+ msg.arg1 = EVENT_ARG_INSERT;
+
+ WorkerArgs args = new WorkerArgs();
+ args.handler = this;
+ args.uri = uri;
+ args.cookie = cookie;
+ args.values = initialValues;
+ msg.obj = args;
+
+ mWorkerThreadHandler.sendMessage(msg);
+ }
+
+ /**
+ * This method begins an asynchronous update. When the update operation is
+ * done {@link #onUpdateComplete} is called.
+ *
+ * @param token A token passed into {@link #onUpdateComplete} to identify
+ * the update operation.
+ * @param cookie An object that gets passed into {@link #onUpdateComplete}
+ * @param uri the Uri passed to the update operation.
+ * @param values the ContentValues parameter passed to the update operation.
+ */
+ public final void startUpdate(int token, Object cookie, Uri uri,
+ ContentValues values, String selection, String[] selectionArgs) {
+ // Use the token as what so cancelOperations works properly
+ Message msg = mWorkerThreadHandler.obtainMessage(token);
+ msg.arg1 = EVENT_ARG_UPDATE;
+
+ WorkerArgs args = new WorkerArgs();
+ args.handler = this;
+ args.uri = uri;
+ args.cookie = cookie;
+ args.values = values;
+ args.selection = selection;
+ args.selectionArgs = selectionArgs;
+ msg.obj = args;
+
+ mWorkerThreadHandler.sendMessage(msg);
+ }
+
+ /**
+ * This method begins an asynchronous delete. When the delete operation is
+ * done {@link #onDeleteComplete} is called.
+ *
+ * @param token A token passed into {@link #onDeleteComplete} to identify
+ * the delete operation.
+ * @param cookie An object that gets passed into {@link #onDeleteComplete}
+ * @param uri the Uri passed to the delete operation.
+ * @param selection the where clause.
+ */
+ public final void startDelete(int token, Object cookie, Uri uri,
+ String selection, String[] selectionArgs) {
+ // Use the token as what so cancelOperations works properly
+ Message msg = mWorkerThreadHandler.obtainMessage(token);
+ msg.arg1 = EVENT_ARG_DELETE;
+
+ WorkerArgs args = new WorkerArgs();
+ args.handler = this;
+ args.uri = uri;
+ args.cookie = cookie;
+ args.selection = selection;
+ args.selectionArgs = selectionArgs;
+ msg.obj = args;
+
+ mWorkerThreadHandler.sendMessage(msg);
+ }
+
+ /**
+ * Called when an asynchronous query is completed.
+ *
+ * @param token the token to identify the query, passed in from
+ * {@link #startQuery}.
+ * @param cookie the cookie object passed in from {@link #startQuery}.
+ * @param cursor The cursor holding the results from the query.
+ */
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ // Empty
+ }
+
+ /**
+ * Called when an asynchronous insert is completed.
+ *
+ * @param token the token to identify the query, passed in from
+ * {@link #startInsert}.
+ * @param cookie the cookie object that's passed in from
+ * {@link #startInsert}.
+ * @param uri the uri returned from the insert operation.
+ */
+ protected void onInsertComplete(int token, Object cookie, Uri uri) {
+ // Empty
+ }
+
+ /**
+ * Called when an asynchronous update is completed.
+ *
+ * @param token the token to identify the query, passed in from
+ * {@link #startUpdate}.
+ * @param cookie the cookie object that's passed in from
+ * {@link #startUpdate}.
+ * @param result the result returned from the update operation
+ */
+ protected void onUpdateComplete(int token, Object cookie, int result) {
+ // Empty
+ }
+
+ /**
+ * Called when an asynchronous delete is completed.
+ *
+ * @param token the token to identify the query, passed in from
+ * {@link #startDelete}.
+ * @param cookie the cookie object that's passed in from
+ * {@link #startDelete}.
+ * @param result the result returned from the delete operation
+ */
+ protected void onDeleteComplete(int token, Object cookie, int result) {
+ // Empty
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ WorkerArgs args = (WorkerArgs) msg.obj;
+
+ if (localLOGV) {
+ Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
+ + ", msg.arg1=" + msg.arg1);
+ }
+
+ int token = msg.what;
+ int event = msg.arg1;
+
+ // pass token back to caller on each callback.
+ switch (event) {
+ case EVENT_ARG_QUERY:
+ onQueryComplete(token, args.cookie, (Cursor) args.result);
+ break;
+
+ case EVENT_ARG_INSERT:
+ onInsertComplete(token, args.cookie, (Uri) args.result);
+ break;
+
+ case EVENT_ARG_UPDATE:
+ onUpdateComplete(token, args.cookie, (Integer) args.result);
+ break;
+
+ case EVENT_ARG_DELETE:
+ onDeleteComplete(token, args.cookie, (Integer) args.result);
+ break;
+ }
+ }
+}
diff --git a/android/content/AsyncTaskLoader.java b/android/content/AsyncTaskLoader.java
new file mode 100644
index 0000000..14c3387
--- /dev/null
+++ b/android/content/AsyncTaskLoader.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2010 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.compat.annotation.UnsupportedAppUsage;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.OperationCanceledException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * Abstract Loader that provides an {@link AsyncTask} to do the work. See
+ * {@link Loader} and {@link android.app.LoaderManager} for more details.
+ *
+ * <p>Here is an example implementation of an AsyncTaskLoader subclass that
+ * loads the currently installed applications from the package manager. This
+ * implementation takes care of retrieving the application labels and sorting
+ * its result set from them, monitoring for changes to the installed
+ * applications, and rebuilding the list when a change in configuration requires
+ * this (such as a locale change).
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ * loader}
+ *
+ * <p>An example implementation of a fragment that uses the above loader to show
+ * the currently installed applications in a list is below.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ * fragment}
+ *
+ * @param <D> the data type to be loaded.
+ *
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.AsyncTaskLoader}
+ */
+@Deprecated
+public abstract class AsyncTaskLoader<D> extends Loader<D> {
+ static final String TAG = "AsyncTaskLoader";
+ static final boolean DEBUG = false;
+
+ final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
+ private final CountDownLatch mDone = new CountDownLatch(1);
+
+ // Set to true to indicate that the task has been posted to a handler for
+ // execution at a later time. Used to throttle updates.
+ boolean waiting;
+
+ /* Runs on a worker thread */
+ @Override
+ protected D doInBackground(Void... params) {
+ if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
+ try {
+ D data = AsyncTaskLoader.this.onLoadInBackground();
+ if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
+ return data;
+ } catch (OperationCanceledException ex) {
+ if (!isCancelled()) {
+ // onLoadInBackground threw a canceled exception spuriously.
+ // This is problematic because it means that the LoaderManager did not
+ // cancel the Loader itself and still expects to receive a result.
+ // Additionally, the Loader's own state will not have been updated to
+ // reflect the fact that the task was being canceled.
+ // So we treat this case as an unhandled exception.
+ throw ex;
+ }
+ if (DEBUG) Log.v(TAG, this + " <<< doInBackground (was canceled)", ex);
+ return null;
+ }
+ }
+
+ /* Runs on the UI thread */
+ @Override
+ protected void onPostExecute(D data) {
+ if (DEBUG) Log.v(TAG, this + " onPostExecute");
+ try {
+ AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
+ } finally {
+ mDone.countDown();
+ }
+ }
+
+ /* Runs on the UI thread */
+ @Override
+ protected void onCancelled(D data) {
+ if (DEBUG) Log.v(TAG, this + " onCancelled");
+ try {
+ AsyncTaskLoader.this.dispatchOnCancelled(this, data);
+ } finally {
+ mDone.countDown();
+ }
+ }
+
+ /* Runs on the UI thread, when the waiting task is posted to a handler.
+ * This method is only executed when task execution was deferred (waiting was true). */
+ @Override
+ public void run() {
+ waiting = false;
+ AsyncTaskLoader.this.executePendingTask();
+ }
+
+ /* Used for testing purposes to wait for the task to complete. */
+ public void waitForLoader() {
+ try {
+ mDone.await();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ @UnsupportedAppUsage
+ private final Executor mExecutor;
+
+ volatile LoadTask mTask;
+ volatile LoadTask mCancellingTask;
+
+ long mUpdateThrottle;
+ long mLastLoadCompleteTime = -10000;
+ Handler mHandler;
+
+ public AsyncTaskLoader(Context context) {
+ this(context, AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ /** {@hide} */
+ public AsyncTaskLoader(Context context, Executor executor) {
+ super(context);
+ mExecutor = executor;
+ }
+
+ /**
+ * Set amount to throttle updates by. This is the minimum time from
+ * when the last {@link #loadInBackground()} call has completed until
+ * a new load is scheduled.
+ *
+ * @param delayMS Amount of delay, in milliseconds.
+ */
+ public void setUpdateThrottle(long delayMS) {
+ mUpdateThrottle = delayMS;
+ if (delayMS != 0) {
+ mHandler = new Handler();
+ }
+ }
+
+ @Override
+ protected void onForceLoad() {
+ super.onForceLoad();
+ cancelLoad();
+ mTask = new LoadTask();
+ if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
+ executePendingTask();
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask);
+ if (mTask != null) {
+ if (!mStarted) {
+ mContentChanged = true;
+ }
+ if (mCancellingTask != null) {
+ // There was a pending task already waiting for a previous
+ // one being canceled; just drop it.
+ if (DEBUG) Log.v(TAG,
+ "cancelLoad: still waiting for cancelled task; dropping next");
+ if (mTask.waiting) {
+ mTask.waiting = false;
+ mHandler.removeCallbacks(mTask);
+ }
+ mTask = null;
+ return false;
+ } else if (mTask.waiting) {
+ // There is a task, but it is waiting for the time it should
+ // execute. We can just toss it.
+ if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
+ mTask.waiting = false;
+ mHandler.removeCallbacks(mTask);
+ mTask = null;
+ return false;
+ } else {
+ boolean cancelled = mTask.cancel(false);
+ if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
+ if (cancelled) {
+ mCancellingTask = mTask;
+ cancelLoadInBackground();
+ }
+ mTask = null;
+ return cancelled;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called if the task was canceled before it was completed. Gives the class a chance
+ * to clean up post-cancellation and to properly dispose of the result.
+ *
+ * @param data The value that was returned by {@link #loadInBackground}, or null
+ * if the task threw {@link OperationCanceledException}.
+ */
+ public void onCanceled(D data) {
+ }
+
+ void executePendingTask() {
+ if (mCancellingTask == null && mTask != null) {
+ if (mTask.waiting) {
+ mTask.waiting = false;
+ mHandler.removeCallbacks(mTask);
+ }
+ if (mUpdateThrottle > 0) {
+ long now = SystemClock.uptimeMillis();
+ if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
+ // Not yet time to do another load.
+ if (DEBUG) Log.v(TAG, "Waiting until "
+ + (mLastLoadCompleteTime+mUpdateThrottle)
+ + " to execute: " + mTask);
+ mTask.waiting = true;
+ mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
+ return;
+ }
+ }
+ if (DEBUG) Log.v(TAG, "Executing: " + mTask);
+ mTask.executeOnExecutor(mExecutor, (Void[]) null);
+ }
+ }
+
+ void dispatchOnCancelled(LoadTask task, D data) {
+ onCanceled(data);
+ if (mCancellingTask == task) {
+ if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
+ rollbackContentChanged();
+ mLastLoadCompleteTime = SystemClock.uptimeMillis();
+ mCancellingTask = null;
+ if (DEBUG) Log.v(TAG, "Delivering cancellation");
+ deliverCancellation();
+ executePendingTask();
+ }
+ }
+
+ void dispatchOnLoadComplete(LoadTask task, D data) {
+ if (mTask != task) {
+ if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
+ dispatchOnCancelled(task, data);
+ } else {
+ if (isAbandoned()) {
+ // This cursor has been abandoned; just cancel the new data.
+ onCanceled(data);
+ } else {
+ commitContentChanged();
+ mLastLoadCompleteTime = SystemClock.uptimeMillis();
+ mTask = null;
+ if (DEBUG) Log.v(TAG, "Delivering result");
+ deliverResult(data);
+ }
+ }
+ }
+
+ /**
+ * Called on a worker thread to perform the actual load and to return
+ * the result of the load operation.
+ *
+ * Implementations should not deliver the result directly, but should return them
+ * from this method, which will eventually end up calling {@link #deliverResult} on
+ * the UI thread. If implementations need to process the results on the UI thread
+ * they may override {@link #deliverResult} and do so there.
+ *
+ * To support cancellation, this method should periodically check the value of
+ * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
+ * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
+ * directly instead of polling {@link #isLoadInBackgroundCanceled}.
+ *
+ * When the load is canceled, this method may either return normally or throw
+ * {@link OperationCanceledException}. In either case, the {@link Loader} will
+ * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
+ * result object, if any.
+ *
+ * @return The result of the load operation.
+ *
+ * @throws OperationCanceledException if the load is canceled during execution.
+ *
+ * @see #isLoadInBackgroundCanceled
+ * @see #cancelLoadInBackground
+ * @see #onCanceled
+ */
+ public abstract D loadInBackground();
+
+ /**
+ * Calls {@link #loadInBackground()}.
+ *
+ * This method is reserved for use by the loader framework.
+ * Subclasses should override {@link #loadInBackground} instead of this method.
+ *
+ * @return The result of the load operation.
+ *
+ * @throws OperationCanceledException if the load is canceled during execution.
+ *
+ * @see #loadInBackground
+ */
+ protected D onLoadInBackground() {
+ return loadInBackground();
+ }
+
+ /**
+ * Called on the main thread to abort a load in progress.
+ *
+ * Override this method to abort the current invocation of {@link #loadInBackground}
+ * that is running in the background on a worker thread.
+ *
+ * This method should do nothing if {@link #loadInBackground} has not started
+ * running or if it has already finished.
+ *
+ * @see #loadInBackground
+ */
+ public void cancelLoadInBackground() {
+ }
+
+ /**
+ * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
+ *
+ * @return True if the current invocation of {@link #loadInBackground} is being canceled.
+ *
+ * @see #loadInBackground
+ */
+ public boolean isLoadInBackgroundCanceled() {
+ return mCancellingTask != null;
+ }
+
+ /**
+ * Locks the current thread until the loader completes the current load
+ * operation. Returns immediately if there is no load operation running.
+ * Should not be called from the UI thread: calling it from the UI
+ * thread would cause a deadlock.
+ * <p>
+ * Use for testing only. <b>Never</b> call this from a UI thread.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void waitForLoader() {
+ LoadTask task = mTask;
+ if (task != null) {
+ task.waitForLoader();
+ }
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ if (mTask != null) {
+ writer.print(prefix); writer.print("mTask="); writer.print(mTask);
+ writer.print(" waiting="); writer.println(mTask.waiting);
+ }
+ if (mCancellingTask != null) {
+ writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
+ writer.print(" waiting="); writer.println(mCancellingTask.waiting);
+ }
+ if (mUpdateThrottle != 0) {
+ writer.print(prefix); writer.print("mUpdateThrottle=");
+ TimeUtils.formatDuration(mUpdateThrottle, writer);
+ writer.print(" mLastLoadCompleteTime=");
+ TimeUtils.formatDuration(mLastLoadCompleteTime,
+ SystemClock.uptimeMillis(), writer);
+ writer.println();
+ }
+ }
+}
diff --git a/android/content/Attributable.java b/android/content/Attributable.java
new file mode 100644
index 0000000..ce0c227
--- /dev/null
+++ b/android/content/Attributable.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.NonNull;
+import android.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Marker interface for a class which can have an {@link AttributionSource}
+ * assigned to it; these are typically {@link android.os.Parcelable} classes
+ * which need to be updated after crossing Binder transaction boundaries.
+ *
+ * @hide
+ */
+public interface Attributable {
+ void setAttributionSource(@NonNull AttributionSource attributionSource);
+
+ static @Nullable <T extends Attributable> T setAttributionSource(
+ @Nullable T attributable,
+ @NonNull AttributionSource attributionSource) {
+ if (attributable != null) {
+ attributable.setAttributionSource(attributionSource);
+ }
+ return attributable;
+ }
+
+ static @Nullable <T extends Attributable> List<T> setAttributionSource(
+ @Nullable List<T> attributableList,
+ @NonNull AttributionSource attributionSource) {
+ if (attributableList != null) {
+ final int size = attributableList.size();
+ for (int i = 0; i < size; i++) {
+ setAttributionSource(attributableList.get(i), attributionSource);
+ }
+ }
+ return attributableList;
+ }
+}
diff --git a/android/content/AttributionSource.java b/android/content/AttributionSource.java
new file mode 100644
index 0000000..bdb7900
--- /dev/null
+++ b/android/content/AttributionSource.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2021 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.Immutable;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents a source to which access to permission protected data should be
+ * attributed. Attribution sources can be chained to represent cases where the protected
+ * data would flow through several applications. For example, app A may ask app B for
+ * contacts and in turn app B may ask app C for contacts. In this case, the attribution
+ * chain would be A -> B -> C and the data flow would be C -> B -> A. There are two
+ * main benefits of using the attribution source mechanism: avoid doing explicit permission
+ * checks on behalf of the calling app if you are accessing private data on their behalf
+ * to send back; avoid double data access blaming which happens as you check the calling
+ * app's permissions and when you access the data behind these permissions (for runtime
+ * permissions). Also if not explicitly blaming the caller the data access would be
+ * counted towards your app vs to the previous app where yours was just a proxy.
+ * <p>
+ * Every {@link Context} has an attribution source and you can get it via {@link
+ * Context#getAttributionSource()} representing itself, which is a chain of one. You
+ * can attribute work to another app, or more precisely to a chain of apps, through
+ * which the data you would be accessing would flow, via {@link Context#createContext(
+ * ContextParams)} plus specifying an attribution source for the next app to receive
+ * the protected data you are accessing via {@link AttributionSource.Builder#setNext(
+ * AttributionSource)}. Creating this attribution chain ensures that the datasource would
+ * check whether every app in the attribution chain has permission to access the data
+ * before releasing it. The datasource will also record appropriately that this data was
+ * accessed by the apps in the sequence if the data is behind a sensitive permission
+ * (e.g. dangerous). Again, this is useful if you are accessing the data on behalf of another
+ * app, for example a speech recognizer using the mic so it can provide recognition to
+ * a calling app.
+ * <p>
+ * You can create an attribution chain of you and any other app without any verification
+ * as this is something already available via the {@link android.app.AppOpsManager} APIs.
+ * This is supported to handle cases where you don't have access to the caller's attribution
+ * source and you can directly use the {@link AttributionSource.Builder} APIs. However,
+ * if the data flows through more than two apps (more than you access the data for the
+ * caller) you need to have a handle to the {@link AttributionSource} for the calling app's
+ * context in order to create an attribution context. This means you either need to have an
+ * API for the other app to send you its attribution source or use a platform API that pipes
+ * the callers attribution source.
+ * <p>
+ * You cannot forge an attribution chain without the participation of every app in the
+ * attribution chain (aside of the special case mentioned above). To create an attribution
+ * source that is trusted you need to create an attribution context that points to an
+ * attribution source that was explicitly created by the app that it refers to, recursively.
+ * <p>
+ * Since creating an attribution context leads to all permissions for apps in the attribution
+ * chain being checked, you need to expect getting a security exception when accessing
+ * permission protected APIs since some app in the chain may not have the permission.
+ */
+@Immutable
+public final class AttributionSource implements Parcelable {
+ private static final String DESCRIPTOR = "android.content.AttributionSource";
+
+ private static final Binder sDefaultToken = new Binder(DESCRIPTOR);
+
+ private final @NonNull AttributionSourceState mAttributionSourceState;
+
+ private @Nullable AttributionSource mNextCached;
+ private @Nullable Set<String> mRenouncedPermissionsCached;
+
+ /** @hide */
+ @TestApi
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag) {
+ this(uid, packageName, attributionTag, sDefaultToken);
+ }
+
+ /** @hide */
+ @TestApi
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token) {
+ this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+ /*next*/ null);
+ }
+
+ /** @hide */
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
+ }
+
+ /** @hide */
+ @TestApi
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
+ @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, (renouncedPermissions != null)
+ ? renouncedPermissions.toArray(new String[0]) : null, next);
+ }
+
+ /** @hide */
+ public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
+ this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
+ current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
+ }
+
+ AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+ }
+
+ AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @NonNull IBinder token, @Nullable String[] renouncedPermissions,
+ @Nullable AttributionSource next) {
+ mAttributionSourceState = new AttributionSourceState();
+ mAttributionSourceState.uid = uid;
+ mAttributionSourceState.token = token;
+ mAttributionSourceState.packageName = packageName;
+ mAttributionSourceState.attributionTag = attributionTag;
+ mAttributionSourceState.renouncedPermissions = renouncedPermissions;
+ mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
+ {next.mAttributionSourceState} : new AttributionSourceState[0];
+ }
+
+ AttributionSource(@NonNull Parcel in) {
+ this(AttributionSourceState.CREATOR.createFromParcel(in));
+
+ // Since we just unpacked this object as part of it transiting a Binder
+ // call, this is the perfect time to enforce that its UID can be trusted
+ enforceCallingUid();
+ }
+
+ /** @hide */
+ public AttributionSource(@NonNull AttributionSourceState attributionSourceState) {
+ mAttributionSourceState = attributionSourceState;
+ }
+
+ /** @hide */
+ public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
+ return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ mAttributionSourceState.renouncedPermissions, next);
+ }
+
+ /** @hide */
+ public AttributionSource withPackageName(@Nullable String packageName) {
+ return new AttributionSource(getUid(), packageName, getAttributionTag(),
+ mAttributionSourceState.renouncedPermissions, getNext());
+ }
+
+ /** @hide */
+ public AttributionSource withToken(@NonNull Binder token) {
+ return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ token, mAttributionSourceState.renouncedPermissions, getNext());
+ }
+
+ /** @hide */
+ public @NonNull AttributionSourceState asState() {
+ return mAttributionSourceState;
+ }
+
+ /** @hide */
+ public @NonNull ScopedParcelState asScopedParcelState() {
+ return new ScopedParcelState(this);
+ }
+
+ /** @hide */
+ public static AttributionSource myAttributionSource() {
+ return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
+ /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+ }
+
+ /**
+ * This is a scoped object that exposes the content of an attribution source
+ * as a parcel. This is useful when passing one to native and avoid custom
+ * conversion logic from Java to native state that needs to be kept in sync
+ * as attribution source evolves. This way we use the same logic for passing
+ * to native as the ones for passing in an IPC - in both cases this is the
+ * same auto generated code.
+ *
+ * @hide
+ */
+ public static class ScopedParcelState implements AutoCloseable {
+ private final Parcel mParcel;
+
+ public @NonNull Parcel getParcel() {
+ return mParcel;
+ }
+
+ public ScopedParcelState(AttributionSource attributionSource) {
+ mParcel = Parcel.obtain();
+ attributionSource.writeToParcel(mParcel, 0);
+ mParcel.setDataPosition(0);
+ }
+
+ public void close() {
+ mParcel.recycle();
+ }
+ }
+
+ /**
+ * If you are handling an IPC and you don't trust the caller you need to validate
+ * whether the attribution source is one for the calling app to prevent the caller
+ * to pass you a source from another app without including themselves in the
+ * attribution chain.
+ *
+ * @throws SecurityException if the attribution source cannot be trusted to be
+ * from the caller.
+ */
+ public void enforceCallingUid() {
+ if (!checkCallingUid()) {
+ throw new SecurityException("Calling uid: " + Binder.getCallingUid()
+ + " doesn't match source uid: " + mAttributionSourceState.uid);
+ }
+ // No need to check package as app ops manager does it already.
+ }
+
+ /**
+ * If you are handling an IPC and you don't trust the caller you need to validate
+ * whether the attribution source is one for the calling app to prevent the caller
+ * to pass you a source from another app without including themselves in the
+ * attribution chain.
+ *f
+ * @return if the attribution source cannot be trusted to be from the caller.
+ */
+ public boolean checkCallingUid() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID
+ && callingUid != Process.SYSTEM_UID
+ && callingUid != mAttributionSourceState.uid) {
+ return false;
+ }
+ // No need to check package as app ops manager does it already.
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ if (Build.IS_DEBUGGABLE) {
+ return "AttributionSource { " +
+ "uid = " + mAttributionSourceState.uid + ", " +
+ "packageName = " + mAttributionSourceState.packageName + ", " +
+ "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
+ "token = " + mAttributionSourceState.token + ", " +
+ "next = " + (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0
+ ? mAttributionSourceState.next[0] : null) +
+ " }";
+ }
+ return super.toString();
+ }
+
+ /**
+ * @return The next UID that would receive the permission protected data.
+ *
+ * @hide
+ */
+ public int getNextUid() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].uid;
+ }
+ return Process.INVALID_UID;
+ }
+
+ /**
+ * @return The next package that would receive the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable String getNextPackageName() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].packageName;
+ }
+ return null;
+ }
+
+ /**
+ * @return The next package's attribution tag that would receive
+ * the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable String getNextAttributionTag() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].attributionTag;
+ }
+ return null;
+ }
+
+ /**
+ * @return The next package's token that would receive
+ * the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable IBinder getNextToken() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].token;
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether this attribution source can be trusted. That is whether
+ * the app it refers to created it and provided to the attribution chain.
+ *
+ * @param context Context handle.
+ * @return Whether this is a trusted source.
+ */
+ public boolean isTrusted(@NonNull Context context) {
+ return mAttributionSourceState.token != null
+ && context.getSystemService(PermissionManager.class)
+ .isRegisteredAttributionSource(this);
+ }
+
+ /**
+ * Permissions that should be considered revoked regardless if granted.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ @NonNull
+ public Set<String> getRenouncedPermissions() {
+ if (mRenouncedPermissionsCached == null) {
+ if (mAttributionSourceState.renouncedPermissions != null) {
+ mRenouncedPermissionsCached = new ArraySet<>(
+ mAttributionSourceState.renouncedPermissions);
+ } else {
+ mRenouncedPermissionsCached = Collections.emptySet();
+ }
+ }
+ return mRenouncedPermissionsCached;
+ }
+
+ /**
+ * The UID that is accessing the permission protected data.
+ */
+ public int getUid() {
+ return mAttributionSourceState.uid;
+ }
+
+ /**
+ * The package that is accessing the permission protected data.
+ */
+ public @Nullable String getPackageName() {
+ return mAttributionSourceState.packageName;
+ }
+
+ /**
+ * The attribution tag of the app accessing the permission protected data.
+ */
+ public @Nullable String getAttributionTag() {
+ return mAttributionSourceState.attributionTag;
+ }
+
+ /**
+ * Unique token for that source.
+ *
+ * @hide
+ */
+ public @NonNull IBinder getToken() {
+ return mAttributionSourceState.token;
+ }
+
+ /**
+ * The next app to receive the permission protected data.
+ */
+ public @Nullable AttributionSource getNext() {
+ if (mNextCached == null && mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
+ }
+ return mNextCached;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AttributionSource that = (AttributionSource) o;
+ return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+ && Objects.equals(mAttributionSourceState.packageName,
+ that.mAttributionSourceState.packageName)
+ && Objects.equals(mAttributionSourceState.attributionTag,
+ that.mAttributionSourceState.attributionTag)
+ && Objects.equals(mAttributionSourceState.token,
+ that.mAttributionSourceState.token)
+ && Arrays.equals(mAttributionSourceState.renouncedPermissions,
+ that.mAttributionSourceState.renouncedPermissions)
+ && Objects.equals(getNext(), that.getNext());
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mAttributionSourceState.uid;
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions);
+ _hash = 31 * _hash + Objects.hashCode(getNext());
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mAttributionSourceState.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() { return 0; }
+
+ public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR
+ = new Parcelable.Creator<AttributionSource>() {
+ @Override
+ public AttributionSource[] newArray(int size) {
+ return new AttributionSource[size];
+ }
+
+ @Override
+ public AttributionSource createFromParcel(@NonNull Parcel in) {
+ return new AttributionSource(in);
+ }
+ };
+
+ /**
+ * A builder for {@link AttributionSource}
+ */
+ public static final class Builder {
+ private @NonNull final AttributionSourceState mAttributionSourceState =
+ new AttributionSourceState();
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param uid
+ * The UID that is accessing the permission protected data.
+ */
+ public Builder(int uid) {
+ mAttributionSourceState.uid = uid;
+ }
+
+ /**
+ * The package that is accessing the permission protected data.
+ */
+ public @NonNull Builder setPackageName(@Nullable String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAttributionSourceState.packageName = value;
+ return this;
+ }
+
+ /**
+ * The attribution tag of the app accessing the permission protected data.
+ */
+ public @NonNull Builder setAttributionTag(@Nullable String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mAttributionSourceState.attributionTag = value;
+ return this;
+ }
+
+ /**
+ * Sets permissions which have been voluntarily "renounced" by the
+ * calling app.
+ * <p>
+ * Interactions performed through services obtained from the created
+ * Context will ideally be treated as if these "renounced" permissions
+ * have not actually been granted to the app, regardless of their actual
+ * grant status.
+ * <p>
+ * This is designed for use by separate logical components within an app
+ * which have no intention of interacting with data or services that are
+ * protected by the renounced permissions.
+ * <p>
+ * Note that only {@link PermissionInfo#PROTECTION_DANGEROUS}
+ * permissions are supported by this mechanism. Additionally, this
+ * mechanism only applies to calls made through services obtained via
+ * {@link Context#getSystemService}; it has no effect on static or raw
+ * Binder calls.
+ *
+ * @param renouncedPermissions The set of permissions to treat as
+ * renounced, which is as if not granted.
+ * @return This builder.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mAttributionSourceState.renouncedPermissions = (value != null)
+ ? value.toArray(new String[0]) : null;
+ return this;
+ }
+
+ /**
+ * The next app to receive the permission protected data.
+ */
+ public @NonNull Builder setNext(@Nullable AttributionSource value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
+ {value.mAttributionSourceState} : mAttributionSourceState.next;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull AttributionSource build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mAttributionSourceState.packageName = null;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mAttributionSourceState.attributionTag = null;
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mAttributionSourceState.renouncedPermissions = null;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mAttributionSourceState.next = null;
+ }
+
+ mAttributionSourceState.token = sDefaultToken;
+
+ if (mAttributionSourceState.next == null) {
+ // The NDK aidl backend doesn't support null parcelable arrays.
+ mAttributionSourceState.next = new AttributionSourceState[0];
+ }
+ return new AttributionSource(mAttributionSourceState);
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/android/content/AutofillOptions.java b/android/content/AutofillOptions.java
new file mode 100644
index 0000000..0581ed5
--- /dev/null
+++ b/android/content/AutofillOptions.java
@@ -0,0 +1,223 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillClient;
+
+import java.io.PrintWriter;
+
+/**
+ * Autofill options for a given package.
+ *
+ * <p>This object is created by the Autofill System Service and passed back to the app when the
+ * application is created.
+ *
+ * @hide
+ */
+@TestApi
+public final class AutofillOptions implements Parcelable {
+
+ private static final String TAG = AutofillOptions.class.getSimpleName();
+
+ /**
+ * Logging level for {@code logcat} statements.
+ */
+ public final int loggingLevel;
+
+ /**
+ * Whether compatibility mode is enabled for the package.
+ */
+ public final boolean compatModeEnabled;
+
+ /**
+ * Whether package is allowlisted for augmented autofill.
+ */
+ public boolean augmentedAutofillEnabled;
+
+ /**
+ * List of allowlisted activities.
+ */
+ @Nullable
+ @SuppressLint("NullableCollection")
+ public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill;
+
+ /**
+ * The package disable expiration by autofill service.
+ */
+ public long appDisabledExpiration;
+
+ /**
+ * The disabled Activities of the package. key is component name string, value is when they
+ * will be enabled.
+ */
+ @SuppressLint("NullableCollection")
+ @Nullable
+ public ArrayMap<String, Long> disabledActivities;
+
+ public AutofillOptions(int loggingLevel, boolean compatModeEnabled) {
+ this.loggingLevel = loggingLevel;
+ this.compatModeEnabled = compatModeEnabled;
+ }
+
+ /**
+ * Returns whether activity is allowlisted for augmented autofill.
+ */
+ public boolean isAugmentedAutofillEnabled(@NonNull Context context) {
+ if (!augmentedAutofillEnabled) return false;
+
+ final AutofillClient autofillClient = context.getAutofillClient();
+ if (autofillClient == null) return false;
+
+ final ComponentName component = autofillClient.autofillClientGetComponentName();
+ return whitelistedActivitiesForAugmentedAutofill == null
+ || whitelistedActivitiesForAugmentedAutofill.contains(component);
+ }
+
+ /**
+ * Returns if autofill is disabled by service to the given activity.
+ *
+ * @hide
+ */
+ public boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
+ final long elapsedTime = SystemClock.elapsedRealtime();
+ final String component = componentName.flattenToString();
+ // Check app first.
+ if (appDisabledExpiration >= elapsedTime) return true;
+
+ // Then check activities.
+ if (disabledActivities != null) {
+ final Long expiration = disabledActivities.get(component);
+ if (expiration != null) {
+ if (expiration >= elapsedTime) return true;
+ disabledActivities.remove(component);
+ }
+ }
+ appDisabledExpiration = 0;
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public static AutofillOptions forWhitelistingItself() {
+ final ActivityThread at = ActivityThread.currentActivityThread();
+ if (at == null) {
+ throw new IllegalStateException("No ActivityThread");
+ }
+
+ final String packageName = at.getApplication().getPackageName();
+
+ if (!"android.autofillservice.cts".equals(packageName)) {
+ Log.e(TAG, "forWhitelistingItself(): called by " + packageName);
+ throw new SecurityException("Thou shall not pass!");
+ }
+
+ final AutofillOptions options = new AutofillOptions(
+ AutofillManager.FLAG_ADD_CLIENT_VERBOSE, /* compatModeAllowed= */ true);
+ options.augmentedAutofillEnabled = true;
+ // Always log, as it's used by test only
+ Log.i(TAG, "forWhitelistingItself(" + packageName + "): " + options);
+
+ return options;
+ }
+
+ @Override
+ public String toString() {
+ return "AutofillOptions [loggingLevel=" + loggingLevel + ", compatMode=" + compatModeEnabled
+ + ", augmentedAutofillEnabled=" + augmentedAutofillEnabled
+ + ", appDisabledExpiration=" + appDisabledExpiration + "]";
+ }
+
+ /** @hide */
+ public void dumpShort(@NonNull PrintWriter pw) {
+ pw.print("logLvl="); pw.print(loggingLevel);
+ pw.print(", compatMode="); pw.print(compatModeEnabled);
+ pw.print(", augmented="); pw.print(augmentedAutofillEnabled);
+ if (whitelistedActivitiesForAugmentedAutofill != null) {
+ pw.print(", whitelistedActivitiesForAugmentedAutofill=");
+ pw.print(whitelistedActivitiesForAugmentedAutofill);
+ }
+ pw.print(", appDisabledExpiration="); pw.print(appDisabledExpiration);
+ if (disabledActivities != null) {
+ pw.print(", disabledActivities=");
+ pw.print(disabledActivities);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(loggingLevel);
+ parcel.writeBoolean(compatModeEnabled);
+ parcel.writeBoolean(augmentedAutofillEnabled);
+ parcel.writeArraySet(whitelistedActivitiesForAugmentedAutofill);
+ parcel.writeLong(appDisabledExpiration);
+ final int size = disabledActivities != null ? disabledActivities.size() : 0;
+ parcel.writeInt(size);
+ if (size > 0) {
+ for (int i = 0; i < size; i++) {
+ final String key = disabledActivities.keyAt(i);
+ parcel.writeString(key);
+ parcel.writeLong(disabledActivities.get(key));
+ }
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<AutofillOptions> CREATOR =
+ new Parcelable.Creator<AutofillOptions>() {
+
+ @Override
+ public AutofillOptions createFromParcel(Parcel parcel) {
+ final int loggingLevel = parcel.readInt();
+ final boolean compatMode = parcel.readBoolean();
+ final AutofillOptions options = new AutofillOptions(loggingLevel, compatMode);
+ options.augmentedAutofillEnabled = parcel.readBoolean();
+ options.whitelistedActivitiesForAugmentedAutofill =
+ (ArraySet<ComponentName>) parcel.readArraySet(null);
+ options.appDisabledExpiration = parcel.readLong();
+ final int size = parcel.readInt();
+ if (size > 0) {
+ options.disabledActivities = new ArrayMap<>();
+ for (int i = 0; i < size; i++) {
+ options.disabledActivities.put(parcel.readString(), parcel.readLong());
+ }
+ }
+ return options;
+ }
+
+ @Override
+ public AutofillOptions[] newArray(int size) {
+ return new AutofillOptions[size];
+ }
+ };
+}
diff --git a/android/content/BroadcastReceiver.java b/android/content/BroadcastReceiver.java
new file mode 100644
index 0000000..1d4d30d
--- /dev/null
+++ b/android/content/BroadcastReceiver.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2006 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.NonNull;
+import android.annotation.SystemApi;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.IActivityManager;
+import android.app.QueuedWork;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Base class for code that receives and handles broadcast intents sent by
+ * {@link android.content.Context#sendBroadcast(Intent)}.
+ *
+ * <p>You can either dynamically register an instance of this class with
+ * {@link Context#registerReceiver Context.registerReceiver()}
+ * or statically declare an implementation with the
+ * {@link android.R.styleable#AndroidManifestReceiver <receiver>}
+ * tag in your <code>AndroidManifest.xml</code>.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using BroadcastReceiver, read the
+ * <a href="{@docRoot}guide/components/broadcasts.html">Broadcasts</a> developer guide.</p></div>
+ *
+ */
+public abstract class BroadcastReceiver {
+ @UnsupportedAppUsage
+ private PendingResult mPendingResult;
+ private boolean mDebugUnregister;
+
+ /**
+ * State for a result that is pending for a broadcast receiver. Returned
+ * by {@link BroadcastReceiver#goAsync() goAsync()}
+ * while in {@link BroadcastReceiver#onReceive BroadcastReceiver.onReceive()}.
+ * This allows you to return from onReceive() without having the broadcast
+ * terminate; you must call {@link #finish()} once you are done with the
+ * broadcast. This allows you to process the broadcast off of the main
+ * thread of your app.
+ *
+ * <p>Note on threading: the state inside of this class is not itself
+ * thread-safe, however you can use it from any thread if you properly
+ * sure that you do not have races. Typically this means you will hand
+ * the entire object to another thread, which will be solely responsible
+ * for setting any results and finally calling {@link #finish()}.
+ */
+ public static class PendingResult {
+ /** @hide */
+ public static final int TYPE_COMPONENT = 0;
+ /** @hide */
+ public static final int TYPE_REGISTERED = 1;
+ /** @hide */
+ public static final int TYPE_UNREGISTERED = 2;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final int mType;
+ @UnsupportedAppUsage
+ final boolean mOrderedHint;
+ @UnsupportedAppUsage
+ final boolean mInitialStickyHint;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final IBinder mToken;
+ @UnsupportedAppUsage
+ final int mSendingUser;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final int mFlags;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ int mResultCode;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ String mResultData;
+ @UnsupportedAppUsage
+ Bundle mResultExtras;
+ @UnsupportedAppUsage
+ boolean mAbortBroadcast;
+ @UnsupportedAppUsage
+ boolean mFinished;
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
+ boolean ordered, boolean sticky, IBinder token, int userId, int flags) {
+ mResultCode = resultCode;
+ mResultData = resultData;
+ mResultExtras = resultExtras;
+ mType = type;
+ mOrderedHint = ordered;
+ mInitialStickyHint = sticky;
+ mToken = token;
+ mSendingUser = userId;
+ mFlags = flags;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#setResultCode(int)
+ * BroadcastReceiver.setResultCode(int)} for
+ * asynchronous broadcast handling.
+ */
+ public final void setResultCode(int code) {
+ checkSynchronousHint();
+ mResultCode = code;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#getResultCode()
+ * BroadcastReceiver.getResultCode()} for
+ * asynchronous broadcast handling.
+ */
+ public final int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#setResultData(String)
+ * BroadcastReceiver.setResultData(String)} for
+ * asynchronous broadcast handling.
+ */
+ public final void setResultData(String data) {
+ checkSynchronousHint();
+ mResultData = data;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#getResultData()
+ * BroadcastReceiver.getResultData()} for
+ * asynchronous broadcast handling.
+ */
+ public final String getResultData() {
+ return mResultData;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#setResultExtras(Bundle)
+ * BroadcastReceiver.setResultExtras(Bundle)} for
+ * asynchronous broadcast handling.
+ */
+ public final void setResultExtras(Bundle extras) {
+ checkSynchronousHint();
+ mResultExtras = extras;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#getResultExtras(boolean)
+ * BroadcastReceiver.getResultExtras(boolean)} for
+ * asynchronous broadcast handling.
+ */
+ public final Bundle getResultExtras(boolean makeMap) {
+ Bundle e = mResultExtras;
+ if (!makeMap) return e;
+ if (e == null) mResultExtras = e = new Bundle();
+ return e;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#setResult(int, String, Bundle)
+ * BroadcastReceiver.setResult(int, String, Bundle)} for
+ * asynchronous broadcast handling.
+ */
+ public final void setResult(int code, String data, Bundle extras) {
+ checkSynchronousHint();
+ mResultCode = code;
+ mResultData = data;
+ mResultExtras = extras;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#getAbortBroadcast()
+ * BroadcastReceiver.getAbortBroadcast()} for
+ * asynchronous broadcast handling.
+ */
+ public final boolean getAbortBroadcast() {
+ return mAbortBroadcast;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#abortBroadcast()
+ * BroadcastReceiver.abortBroadcast()} for
+ * asynchronous broadcast handling.
+ */
+ public final void abortBroadcast() {
+ checkSynchronousHint();
+ mAbortBroadcast = true;
+ }
+
+ /**
+ * Version of {@link BroadcastReceiver#clearAbortBroadcast()
+ * BroadcastReceiver.clearAbortBroadcast()} for
+ * asynchronous broadcast handling.
+ */
+ public final void clearAbortBroadcast() {
+ mAbortBroadcast = false;
+ }
+
+ /**
+ * Finish the broadcast. The current result will be sent and the
+ * next broadcast will proceed.
+ */
+ public final void finish() {
+ if (mType == TYPE_COMPONENT) {
+ final IActivityManager mgr = ActivityManager.getService();
+ if (QueuedWork.hasPendingWork()) {
+ // If this is a broadcast component, we need to make sure any
+ // queued work is complete before telling AM we are done, so
+ // we don't have our process killed before that. We now know
+ // there is pending work; put another piece of work at the end
+ // of the list to finish the broadcast, so we don't block this
+ // thread (which may be the main thread) to have it finished.
+ //
+ // Note that we don't need to use QueuedWork.addFinisher() with the
+ // runnable, since we know the AM is waiting for us until the
+ // executor gets to it.
+ QueuedWork.queue(new Runnable() {
+ @Override public void run() {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing broadcast after work to component " + mToken);
+ sendFinished(mgr);
+ }
+ }, false);
+ } else {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing broadcast to component " + mToken);
+ sendFinished(mgr);
+ }
+ } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing broadcast to " + mToken);
+ final IActivityManager mgr = ActivityManager.getService();
+ sendFinished(mgr);
+ }
+ }
+
+ /** @hide */
+ public void setExtrasClassLoader(ClassLoader cl) {
+ if (mResultExtras != null) {
+ mResultExtras.setClassLoader(cl);
+ }
+ }
+
+ /** @hide */
+ public void sendFinished(IActivityManager am) {
+ synchronized (this) {
+ if (mFinished) {
+ throw new IllegalStateException("Broadcast already finished");
+ }
+ mFinished = true;
+
+ try {
+ if (mResultExtras != null) {
+ mResultExtras.setAllowFds(false);
+ }
+ if (mOrderedHint) {
+ am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
+ mAbortBroadcast, mFlags);
+ } else {
+ // This broadcast was sent to a component; it is not ordered,
+ // but we still need to tell the activity manager we are done.
+ am.finishReceiver(mToken, 0, null, null, false, mFlags);
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ /** @hide */
+ public int getSendingUserId() {
+ return mSendingUser;
+ }
+
+ void checkSynchronousHint() {
+ // Note that we don't assert when receiving the initial sticky value,
+ // since that may have come from an ordered broadcast. We'll catch
+ // them later when the real broadcast happens again.
+ if (mOrderedHint || mInitialStickyHint) {
+ return;
+ }
+ RuntimeException e = new RuntimeException(
+ "BroadcastReceiver trying to return result during a non-ordered broadcast");
+ e.fillInStackTrace();
+ Log.e("BroadcastReceiver", e.getMessage(), e);
+ }
+ }
+
+ public BroadcastReceiver() {
+ }
+
+ /**
+ * This method is called when the BroadcastReceiver is receiving an Intent
+ * broadcast. During this time you can use the other methods on
+ * BroadcastReceiver to view/modify the current result values. This method
+ * is always called within the main thread of its process, unless you
+ * explicitly asked for it to be scheduled on a different thread using
+ * {@link android.content.Context#registerReceiver(BroadcastReceiver,
+ * IntentFilter, String, android.os.Handler)}. When it runs on the main
+ * thread you should
+ * never perform long-running operations in it (there is a timeout of
+ * 10 seconds that the system allows before considering the receiver to
+ * be blocked and a candidate to be killed). You cannot launch a popup dialog
+ * in your implementation of onReceive().
+ *
+ * <p><b>If this BroadcastReceiver was launched through a <receiver> tag,
+ * then the object is no longer alive after returning from this
+ * function.</b> This means you should not perform any operations that
+ * return a result to you asynchronously. If you need to perform any follow up
+ * background work, schedule a {@link android.app.job.JobService} with
+ * {@link android.app.job.JobScheduler}.
+ *
+ * If you wish to interact with a service that is already running and previously
+ * bound using {@link android.content.Context#bindService(Intent, ServiceConnection, int) bindService()},
+ * you can use {@link #peekService}.
+ *
+ * <p>The Intent filters used in {@link android.content.Context#registerReceiver}
+ * and in application manifests are <em>not</em> guaranteed to be exclusive. They
+ * are hints to the operating system about how to find suitable recipients. It is
+ * possible for senders to force delivery to specific recipients, bypassing filter
+ * resolution. For this reason, {@link #onReceive(Context, Intent) onReceive()}
+ * implementations should respond only to known actions, ignoring any unexpected
+ * Intents that they may receive.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ public abstract void onReceive(Context context, Intent intent);
+
+ /**
+ * This can be called by an application in {@link #onReceive} to allow
+ * it to keep the broadcast active after returning from that function.
+ * This does <em>not</em> change the expectation of being relatively
+ * responsive to the broadcast, but does allow
+ * the implementation to move work related to it over to another thread
+ * to avoid glitching the main UI thread due to disk IO.
+ *
+ * <p>As a general rule, broadcast receivers are allowed to run for up to 10 seconds
+ * before they system will consider them non-responsive and ANR the app. Since these usually
+ * execute on the app's main thread, they are already bound by the ~5 second time limit
+ * of various operations that can happen there (not to mention just avoiding UI jank), so
+ * the receive limit is generally not of concern. However, once you use {@code goAsync}, though
+ * able to be off the main thread, the broadcast execution limit still applies, and that
+ * includes the time spent between calling this method and ultimately
+ * {@link PendingResult#finish() PendingResult.finish()}.</p>
+ *
+ * <p>If you are taking advantage of this method to have more time to execute, it is useful
+ * to know that the available time can be longer in certain situations. In particular, if
+ * the broadcast you are receiving is not a foreground broadcast (that is, the sender has not
+ * used {@link Intent#FLAG_RECEIVER_FOREGROUND}), then more time is allowed for the receivers
+ * to run, allowing them to execute for 30 seconds or even a bit more. This is something that
+ * receivers should rarely take advantage of (long work should be punted to another system
+ * facility such as {@link android.app.job.JobScheduler}, {@link android.app.Service}, or
+ * see especially {@link android.support.v4.app.JobIntentService}), but can be useful in
+ * certain rare cases where it is necessary to do some work as soon as the broadcast is
+ * delivered. Keep in mind that the work you do here will block further broadcasts until
+ * it completes, so taking advantage of this at all excessively can be counter-productive
+ * and cause later events to be received more slowly.</p>
+ *
+ * @return Returns a {@link PendingResult} representing the result of
+ * the active broadcast. The BroadcastRecord itself is no longer active;
+ * all data and other interaction must go through {@link PendingResult}
+ * APIs. The {@link PendingResult#finish PendingResult.finish()} method
+ * must be called once processing of the broadcast is done.
+ */
+ public final PendingResult goAsync() {
+ PendingResult res = mPendingResult;
+ mPendingResult = null;
+ return res;
+ }
+
+ /**
+ * Provide a binder to an already-bound service. This method is synchronous
+ * and will not start the target service if it is not present, so it is safe
+ * to call from {@link #onReceive}.
+ *
+ * For peekService() to return a non null {@link android.os.IBinder} interface
+ * the service must have published it before. In other words some component
+ * must have called {@link android.content.Context#bindService(Intent, ServiceConnection, int)} on it.
+ *
+ * @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)}
+ * @param service Identifies the already-bound service you wish to use. See
+ * {@link android.content.Context#bindService(Intent, ServiceConnection, int)}
+ * for more information.
+ */
+ public IBinder peekService(Context myContext, Intent service) {
+ IActivityManager am = ActivityManager.getService();
+ IBinder binder = null;
+ try {
+ service.prepareToLeaveProcess(myContext);
+ binder = am.peekService(service, service.resolveTypeIfNeeded(
+ myContext.getContentResolver()), myContext.getOpPackageName());
+ } catch (RemoteException e) {
+ }
+ return binder;
+ }
+
+ /**
+ * Change the current result code of this broadcast; only works with
+ * broadcasts sent through
+ * {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}. Often uses the
+ * Activity {@link android.app.Activity#RESULT_CANCELED} and
+ * {@link android.app.Activity#RESULT_OK} constants, though the
+ * actual meaning of this value is ultimately up to the broadcaster.
+ *
+ * <p class="note">This method does not work with non-ordered broadcasts such
+ * as those sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}</p>
+ *
+ * @param code The new result code.
+ *
+ * @see #setResult(int, String, Bundle)
+ */
+ public final void setResultCode(int code) {
+ checkSynchronousHint();
+ mPendingResult.mResultCode = code;
+ }
+
+ /**
+ * Retrieve the current result code, as set by the previous receiver.
+ *
+ * @return int The current result code.
+ */
+ public final int getResultCode() {
+ return mPendingResult != null ? mPendingResult.mResultCode : 0;
+ }
+
+ /**
+ * Change the current result data of this broadcast; only works with
+ * broadcasts sent through
+ * {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}. This is an arbitrary
+ * string whose interpretation is up to the broadcaster.
+ *
+ * <p><strong>This method does not work with non-ordered broadcasts such
+ * as those sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}</strong></p>
+ *
+ * @param data The new result data; may be null.
+ *
+ * @see #setResult(int, String, Bundle)
+ */
+ public final void setResultData(String data) {
+ checkSynchronousHint();
+ mPendingResult.mResultData = data;
+ }
+
+ /**
+ * Retrieve the current result data, as set by the previous receiver.
+ * Often this is null.
+ *
+ * @return String The current result data; may be null.
+ */
+ public final String getResultData() {
+ return mPendingResult != null ? mPendingResult.mResultData : null;
+ }
+
+ /**
+ * Change the current result extras of this broadcast; only works with
+ * broadcasts sent through
+ * {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}. This is a Bundle
+ * holding arbitrary data, whose interpretation is up to the
+ * broadcaster. Can be set to null. Calling this method completely
+ * replaces the current map (if any).
+ *
+ * <p><strong>This method does not work with non-ordered broadcasts such
+ * as those sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}</strong></p>
+ *
+ * @param extras The new extra data map; may be null.
+ *
+ * @see #setResult(int, String, Bundle)
+ */
+ public final void setResultExtras(Bundle extras) {
+ checkSynchronousHint();
+ mPendingResult.mResultExtras = extras;
+ }
+
+ /**
+ * Retrieve the current result extra data, as set by the previous receiver.
+ * Any changes you make to the returned Map will be propagated to the next
+ * receiver.
+ *
+ * @param makeMap If true then a new empty Map will be made for you if the
+ * current Map is null; if false you should be prepared to
+ * receive a null Map.
+ *
+ * @return Map The current extras map.
+ */
+ public final Bundle getResultExtras(boolean makeMap) {
+ if (mPendingResult == null) {
+ return null;
+ }
+ Bundle e = mPendingResult.mResultExtras;
+ if (!makeMap) return e;
+ if (e == null) mPendingResult.mResultExtras = e = new Bundle();
+ return e;
+ }
+
+ /**
+ * Change all of the result data returned from this broadcasts; only works
+ * with broadcasts sent through
+ * {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}. All current result data is replaced
+ * by the value given to this method.
+ *
+ * <p><strong>This method does not work with non-ordered broadcasts such
+ * as those sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}</strong></p>
+ *
+ * @param code The new result code. Often uses the
+ * Activity {@link android.app.Activity#RESULT_CANCELED} and
+ * {@link android.app.Activity#RESULT_OK} constants, though the
+ * actual meaning of this value is ultimately up to the broadcaster.
+ * @param data The new result data. This is an arbitrary
+ * string whose interpretation is up to the broadcaster; may be null.
+ * @param extras The new extra data map. This is a Bundle
+ * holding arbitrary data, whose interpretation is up to the
+ * broadcaster. Can be set to null. This completely
+ * replaces the current map (if any).
+ */
+ public final void setResult(int code, String data, Bundle extras) {
+ checkSynchronousHint();
+ mPendingResult.mResultCode = code;
+ mPendingResult.mResultData = data;
+ mPendingResult.mResultExtras = extras;
+ }
+
+ /**
+ * Returns the flag indicating whether or not this receiver should
+ * abort the current broadcast.
+ *
+ * @return True if the broadcast should be aborted.
+ */
+ public final boolean getAbortBroadcast() {
+ return mPendingResult != null ? mPendingResult.mAbortBroadcast : false;
+ }
+
+ /**
+ * Sets the flag indicating that this receiver should abort the
+ * current broadcast; only works with broadcasts sent through
+ * {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}. This will prevent
+ * any other broadcast receivers from receiving the broadcast. It will still
+ * call {@link #onReceive} of the BroadcastReceiver that the caller of
+ * {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast} passed in.
+ *
+ * <p><strong>This method does not work with non-ordered broadcasts such
+ * as those sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}</strong></p>
+ */
+ public final void abortBroadcast() {
+ checkSynchronousHint();
+ mPendingResult.mAbortBroadcast = true;
+ }
+
+ /**
+ * Clears the flag indicating that this receiver should abort the current
+ * broadcast.
+ */
+ public final void clearAbortBroadcast() {
+ if (mPendingResult != null) {
+ mPendingResult.mAbortBroadcast = false;
+ }
+ }
+
+ /**
+ * Returns true if the receiver is currently processing an ordered
+ * broadcast.
+ */
+ public final boolean isOrderedBroadcast() {
+ return mPendingResult != null ? mPendingResult.mOrderedHint : false;
+ }
+
+ /**
+ * Returns true if the receiver is currently processing the initial
+ * value of a sticky broadcast -- that is, the value that was last
+ * broadcast and is currently held in the sticky cache, so this is
+ * not directly the result of a broadcast right now.
+ */
+ public final boolean isInitialStickyBroadcast() {
+ return mPendingResult != null ? mPendingResult.mInitialStickyHint : false;
+ }
+
+ /**
+ * For internal use, sets the hint about whether this BroadcastReceiver is
+ * running in ordered mode.
+ */
+ public final void setOrderedHint(boolean isOrdered) {
+ // Accidentally left in the SDK.
+ }
+
+ /**
+ * For internal use to set the result data that is active. @hide
+ */
+ @UnsupportedAppUsage
+ public final void setPendingResult(PendingResult result) {
+ mPendingResult = result;
+ }
+
+ /**
+ * For internal use to set the result data that is active. @hide
+ */
+ @UnsupportedAppUsage
+ public final PendingResult getPendingResult() {
+ return mPendingResult;
+ }
+
+ /**
+ * Returns the user that the broadcast was sent to.
+ *
+ * <p>It can be used in a receiver registered by
+ * {@link Context#registerReceiverForAllUsers Context.registerReceiverForAllUsers()}
+ * to determine on which user the broadcast was sent.
+ *
+ * @hide
+ */
+ @SystemApi
+ public final @NonNull UserHandle getSendingUser() {
+ return UserHandle.of(getSendingUserId());
+ }
+
+ /** @hide */
+ public int getSendingUserId() {
+ return mPendingResult.mSendingUser;
+ }
+
+ /**
+ * Control inclusion of debugging help for mismatched
+ * calls to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ * If called with true, before given to registerReceiver(), then the
+ * callstack of the following {@link Context#unregisterReceiver(BroadcastReceiver)
+ * Context.unregisterReceiver()} call is retained, to be printed if a later
+ * incorrect unregister call is made. Note that doing this requires retaining
+ * information about the BroadcastReceiver for the lifetime of the app,
+ * resulting in a leak -- this should only be used for debugging.
+ */
+ public final void setDebugUnregister(boolean debug) {
+ mDebugUnregister = debug;
+ }
+
+ /**
+ * Return the last value given to {@link #setDebugUnregister}.
+ */
+ public final boolean getDebugUnregister() {
+ return mDebugUnregister;
+ }
+
+ void checkSynchronousHint() {
+ if (mPendingResult == null) {
+ throw new IllegalStateException("Call while result is not pending");
+ }
+
+ // Note that we don't assert when receiving the initial sticky value,
+ // since that may have come from an ordered broadcast. We'll catch
+ // them later when the real broadcast happens again.
+ if (mPendingResult.mOrderedHint || mPendingResult.mInitialStickyHint) {
+ return;
+ }
+ RuntimeException e = new RuntimeException(
+ "BroadcastReceiver trying to return result during a non-ordered broadcast");
+ e.fillInStackTrace();
+ Log.e("BroadcastReceiver", e.getMessage(), e);
+ }
+}
+
diff --git a/android/content/ClipData.java b/android/content/ClipData.java
new file mode 100644
index 0000000..0bc459a
--- /dev/null
+++ b/android/content/ClipData.java
@@ -0,0 +1,1278 @@
+/**
+ * Copyright (c) 2010, 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 static android.content.ContentProvider.maybeAddUserId;
+import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
+import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.content.ContentResolver.SCHEME_FILE;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.StrictMode;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.URLSpan;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextLinks;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Representation of a clipped data on the clipboard.
+ *
+ * <p>ClipData is a complex type containing one or more Item instances,
+ * each of which can hold one or more representations of an item of data.
+ * For display to the user, it also has a label.</p>
+ *
+ * <p>A ClipData contains a {@link ClipDescription}, which describes
+ * important meta-data about the clip. In particular, its
+ * {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)}
+ * must return correct MIME type(s) describing the data in the clip. For help
+ * in correctly constructing a clip with the correct MIME type, use
+ * {@link #newPlainText(CharSequence, CharSequence)},
+ * {@link #newUri(ContentResolver, CharSequence, Uri)}, and
+ * {@link #newIntent(CharSequence, Intent)}.
+ *
+ * <p>Each Item instance can be one of three main classes of data: a simple
+ * CharSequence of text, a single Intent object, or a Uri. See {@link Item}
+ * for more details.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using the clipboard framework, read the
+ * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <a name="ImplementingPaste"></a>
+ * <h3>Implementing Paste or Drop</h3>
+ *
+ * <p>To implement a paste or drop of a ClipData object into an application,
+ * the application must correctly interpret the data for its use. If the {@link Item}
+ * it contains is simple text or an Intent, there is little to be done: text
+ * can only be interpreted as text, and an Intent will typically be used for
+ * creating shortcuts (such as placing icons on the home screen) or other
+ * actions.
+ *
+ * <p>If all you want is the textual representation of the clipped data, you
+ * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
+ * In this case there is generally no need to worry about the MIME types
+ * reported by {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)},
+ * since any clip item can always be converted to a string.
+ *
+ * <p>More complicated exchanges will be done through URIs, in particular
+ * "content:" URIs. A content URI allows the recipient of a ClipData item
+ * to interact closely with the ContentProvider holding the data in order to
+ * negotiate the transfer of that data. The clip must also be filled in with
+ * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Uri)}
+ * will take care of correctly doing this.
+ *
+ * <p>For example, here is the paste function of a simple NotePad application.
+ * When retrieving the data from the clipboard, it can do either two things:
+ * if the clipboard contains a URI reference to an existing note, it copies
+ * the entire structure of the note into a new note; otherwise, it simply
+ * coerces the clip into text and uses that as the new note's contents.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
+ * paste}
+ *
+ * <p>In many cases an application can paste various types of streams of data. For
+ * example, an e-mail application may want to allow the user to paste an image
+ * or other binary data as an attachment. This is accomplished through the
+ * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
+ * methods. These allow a client to discover the type(s) of data that a particular
+ * content URI can make available as a stream and retrieve the stream of data.
+ *
+ * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
+ * itself uses this to try to retrieve a URI clip as a stream of text:
+ *
+ * {@sample frameworks/base/core/java/android/content/ClipData.java coerceToText}
+ *
+ * <a name="ImplementingCopy"></a>
+ * <h3>Implementing Copy or Drag</h3>
+ *
+ * <p>To be the source of a clip, the application must construct a ClipData
+ * object that any recipient can interpret best for their context. If the clip
+ * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
+ * containing the appropriate data type can be constructed and used.
+ *
+ * <p>More complicated data types require the implementation of support in
+ * a ContentProvider for describing and generating the data for the recipient.
+ * A common scenario is one where an application places on the clipboard the
+ * content: URI of an object that the user has copied, with the data at that
+ * URI consisting of a complicated structure that only other applications with
+ * direct knowledge of the structure can use.
+ *
+ * <p>For applications that do not have intrinsic knowledge of the data structure,
+ * the content provider holding it can make the data available as an arbitrary
+ * number of types of data streams. This is done by implementing the
+ * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
+ * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
+ * methods.
+ *
+ * <p>Going back to our simple NotePad application, this is the implementation
+ * it may have to convert a single note URI (consisting of a title and the note
+ * text) into a stream of plain text data.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
+ * stream}
+ *
+ * <p>The copy operation in our NotePad application is now just a simple matter
+ * of making a clip containing the URI of the note being copied:
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
+ * copy}
+ *
+ * <p>Note if a paste operation needs this clip as text (for example to paste
+ * into an editor), then {@link Item#coerceToText(Context)} will ask the content
+ * provider for the clip URI as text and successfully paste the entire note.
+ */
+public class ClipData implements Parcelable {
+ static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
+ ClipDescription.MIMETYPE_TEXT_PLAIN };
+ static final String[] MIMETYPES_TEXT_HTML = new String[] {
+ ClipDescription.MIMETYPE_TEXT_HTML };
+ static final String[] MIMETYPES_TEXT_URILIST = new String[] {
+ ClipDescription.MIMETYPE_TEXT_URILIST };
+ static final String[] MIMETYPES_TEXT_INTENT = new String[] {
+ ClipDescription.MIMETYPE_TEXT_INTENT };
+
+ final ClipDescription mClipDescription;
+
+ final Bitmap mIcon;
+
+ final ArrayList<Item> mItems;
+
+ // This is false by default unless the ClipData is obtained via
+ // {@link #copyForTransferWithActivityInfo}.
+ private boolean mParcelItemActivityInfos;
+
+ /**
+ * Description of a single item in a ClipData.
+ *
+ * <p>The types than an individual item can currently contain are:</p>
+ *
+ * <ul>
+ * <li> Text: a basic string of text. This is actually a CharSequence,
+ * so it can be formatted text supported by corresponding Android built-in
+ * style spans. (Custom application spans are not supported and will be
+ * stripped when transporting through the clipboard.)
+ * <li> Intent: an arbitrary Intent object. A typical use is the shortcut
+ * to create when pasting a clipped item on to the home screen.
+ * <li> Uri: a URI reference. This may be any URI (such as an http: URI
+ * representing a bookmark), however it is often a content: URI. Using
+ * content provider references as clips like this allows an application to
+ * share complex or large clips through the standard content provider
+ * facilities.
+ * </ul>
+ */
+ public static class Item {
+ final CharSequence mText;
+ final String mHtmlText;
+ final Intent mIntent;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ Uri mUri;
+ private TextLinks mTextLinks;
+ // Additional activity info resolved by the system. This is only parceled with the ClipData
+ // if the data is obtained from {@link #copyForTransferWithActivityInfo}
+ private ActivityInfo mActivityInfo;
+
+
+ /** @hide */
+ public Item(Item other) {
+ mText = other.mText;
+ mHtmlText = other.mHtmlText;
+ mIntent = other.mIntent;
+ mUri = other.mUri;
+ mActivityInfo = other.mActivityInfo;
+ mTextLinks = other.mTextLinks;
+ }
+
+ /**
+ * Create an Item consisting of a single block of (possibly styled) text.
+ */
+ public Item(CharSequence text) {
+ mText = text;
+ mHtmlText = null;
+ mIntent = null;
+ mUri = null;
+ }
+
+ /**
+ * Create an Item consisting of a single block of (possibly styled) text,
+ * with an alternative HTML formatted representation. You <em>must</em>
+ * supply a plain text representation in addition to HTML text; coercion
+ * will not be done from HTML formatted text into plain text.
+ * <p><strong>Warning:</strong> Use content: URI for sharing large clip data.
+ * ClipData.Item doesn't accept an HTML text if it's larger than 800KB.
+ * </p>
+ */
+ public Item(CharSequence text, String htmlText) {
+ mText = text;
+ mHtmlText = htmlText;
+ mIntent = null;
+ mUri = null;
+ }
+
+ /**
+ * Create an Item consisting of an arbitrary Intent.
+ */
+ public Item(Intent intent) {
+ mText = null;
+ mHtmlText = null;
+ mIntent = intent;
+ mUri = null;
+ }
+
+ /**
+ * Create an Item consisting of an arbitrary URI.
+ */
+ public Item(Uri uri) {
+ mText = null;
+ mHtmlText = null;
+ mIntent = null;
+ mUri = uri;
+ }
+
+ /**
+ * Create a complex Item, containing multiple representations of
+ * text, Intent, and/or URI.
+ */
+ public Item(CharSequence text, Intent intent, Uri uri) {
+ mText = text;
+ mHtmlText = null;
+ mIntent = intent;
+ mUri = uri;
+ }
+
+ /**
+ * Create a complex Item, containing multiple representations of
+ * text, HTML text, Intent, and/or URI. If providing HTML text, you
+ * <em>must</em> supply a plain text representation as well; coercion
+ * will not be done from HTML formatted text into plain text.
+ */
+ public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
+ if (htmlText != null && text == null) {
+ throw new IllegalArgumentException(
+ "Plain text must be supplied if HTML text is supplied");
+ }
+ mText = text;
+ mHtmlText = htmlText;
+ mIntent = intent;
+ mUri = uri;
+ }
+
+ /**
+ * Retrieve the raw text contained in this Item.
+ */
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Retrieve the raw HTML text contained in this Item.
+ */
+ public String getHtmlText() {
+ return mHtmlText;
+ }
+
+ /**
+ * Retrieve the raw Intent contained in this Item.
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Retrieve the raw URI contained in this Item.
+ */
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Retrieve the activity info contained in this Item.
+ * @hide
+ */
+ public ActivityInfo getActivityInfo() {
+ return mActivityInfo;
+ }
+
+ /**
+ * Updates the activity info for in this Item.
+ * @hide
+ */
+ public void setActivityInfo(ActivityInfo info) {
+ mActivityInfo = info;
+ }
+
+ /**
+ * Returns the results of text classification run on the raw text contained in this item,
+ * if it was performed, and if any entities were found in the text. Classification is
+ * generally only performed on the first item in clip data, and only if the text is below a
+ * certain length.
+ *
+ * <p>Returns {@code null} if classification was not performed, or if no entities were
+ * found in the text.
+ *
+ * @see ClipDescription#getConfidenceScore(String)
+ */
+ @Nullable
+ public TextLinks getTextLinks() {
+ return mTextLinks;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTextLinks(TextLinks textLinks) {
+ mTextLinks = textLinks;
+ }
+
+ /**
+ * Turn this item into text, regardless of the type of data it
+ * actually contains.
+ *
+ * <p>The algorithm for deciding what text to return is:
+ * <ul>
+ * <li> If {@link #getText} is non-null, return that.
+ * <li> If {@link #getUri} is non-null, try to retrieve its data
+ * as a text stream from its content provider. If this succeeds, copy
+ * the text into a String and return it. If it is not a content: URI or
+ * the content provider does not supply a text representation, return
+ * the raw URI as a string.
+ * <li> If {@link #getIntent} is non-null, convert that to an intent:
+ * URI and return it.
+ * <li> Otherwise, return an empty string.
+ * </ul>
+ *
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's textual representation.
+ */
+//BEGIN_INCLUDE(coerceToText)
+ public CharSequence coerceToText(Context context) {
+ // If this Item has an explicit textual value, simply return that.
+ CharSequence text = getText();
+ if (text != null) {
+ return text;
+ }
+
+ // If this Item has a URI value, try using that.
+ Uri uri = getUri();
+ if (uri != null) {
+ // First see if the URI can be opened as a plain text stream
+ // (of any sub-type). If so, this is the best textual
+ // representation for it.
+ final ContentResolver resolver = context.getContentResolver();
+ AssetFileDescriptor descr = null;
+ FileInputStream stream = null;
+ InputStreamReader reader = null;
+ try {
+ try {
+ // Ask for a stream of the desired type.
+ descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
+ } catch (SecurityException e) {
+ Log.w("ClipData", "Failure opening stream", e);
+ } catch (FileNotFoundException|RuntimeException e) {
+ // Unable to open content URI as text... not really an
+ // error, just something to ignore.
+ }
+ if (descr != null) {
+ try {
+ stream = descr.createInputStream();
+ reader = new InputStreamReader(stream, "UTF-8");
+
+ // Got it... copy the stream into a local string and return it.
+ final StringBuilder builder = new StringBuilder(128);
+ char[] buffer = new char[8192];
+ int len;
+ while ((len=reader.read(buffer)) > 0) {
+ builder.append(buffer, 0, len);
+ }
+ return builder.toString();
+ } catch (IOException e) {
+ // Something bad has happened.
+ Log.w("ClipData", "Failure loading text", e);
+ return e.toString();
+ }
+ }
+ } finally {
+ IoUtils.closeQuietly(descr);
+ IoUtils.closeQuietly(stream);
+ IoUtils.closeQuietly(reader);
+ }
+
+ // If we couldn't open the URI as a stream, use the URI itself as a textual
+ // representation (but not for "content", "android.resource" or "file" schemes).
+ final String scheme = uri.getScheme();
+ if (SCHEME_CONTENT.equals(scheme)
+ || SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || SCHEME_FILE.equals(scheme)) {
+ return "";
+ }
+ return uri.toString();
+ }
+
+ // Finally, if all we have is an Intent, then we can just turn that
+ // into text. Not the most user-friendly thing, but it's something.
+ Intent intent = getIntent();
+ if (intent != null) {
+ return intent.toUri(Intent.URI_INTENT_SCHEME);
+ }
+
+ // Shouldn't get here, but just in case...
+ return "";
+ }
+//END_INCLUDE(coerceToText)
+
+ /**
+ * Like {@link #coerceToHtmlText(Context)}, but any text that would
+ * be returned as HTML formatting will be returned as text with
+ * style spans.
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's textual representation.
+ */
+ public CharSequence coerceToStyledText(Context context) {
+ CharSequence text = getText();
+ if (text instanceof Spanned) {
+ return text;
+ }
+ String htmlText = getHtmlText();
+ if (htmlText != null) {
+ try {
+ CharSequence newText = Html.fromHtml(htmlText);
+ if (newText != null) {
+ return newText;
+ }
+ } catch (RuntimeException e) {
+ // If anything bad happens, we'll fall back on the plain text.
+ }
+ }
+
+ if (text != null) {
+ return text;
+ }
+ return coerceToHtmlOrStyledText(context, true);
+ }
+
+ /**
+ * Turn this item into HTML text, regardless of the type of data it
+ * actually contains.
+ *
+ * <p>The algorithm for deciding what text to return is:
+ * <ul>
+ * <li> If {@link #getHtmlText} is non-null, return that.
+ * <li> If {@link #getText} is non-null, return that, converting to
+ * valid HTML text. If this text contains style spans,
+ * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to
+ * convert them to HTML formatting.
+ * <li> If {@link #getUri} is non-null, try to retrieve its data
+ * as a text stream from its content provider. If the provider can
+ * supply text/html data, that will be preferred and returned as-is.
+ * Otherwise, any text/* data will be returned and escaped to HTML.
+ * If it is not a content: URI or the content provider does not supply
+ * a text representation, HTML text containing a link to the URI
+ * will be returned.
+ * <li> If {@link #getIntent} is non-null, convert that to an intent:
+ * URI and return as an HTML link.
+ * <li> Otherwise, return an empty string.
+ * </ul>
+ *
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's representation as HTML text.
+ */
+ public String coerceToHtmlText(Context context) {
+ // If the item has an explicit HTML value, simply return that.
+ String htmlText = getHtmlText();
+ if (htmlText != null) {
+ return htmlText;
+ }
+
+ // If this Item has a plain text value, return it as HTML.
+ CharSequence text = getText();
+ if (text != null) {
+ if (text instanceof Spanned) {
+ return Html.toHtml((Spanned)text);
+ }
+ return Html.escapeHtml(text);
+ }
+
+ text = coerceToHtmlOrStyledText(context, false);
+ return text != null ? text.toString() : null;
+ }
+
+ private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) {
+ // If this Item has a URI value, try using that.
+ if (mUri != null) {
+
+ // Check to see what data representations the content
+ // provider supports. We would like HTML text, but if that
+ // is not possible we'll live with plan text.
+ String[] types = null;
+ try {
+ types = context.getContentResolver().getStreamTypes(mUri, "text/*");
+ } catch (SecurityException e) {
+ // No read permission for mUri, assume empty stream types list.
+ }
+ boolean hasHtml = false;
+ boolean hasText = false;
+ if (types != null) {
+ for (String type : types) {
+ if ("text/html".equals(type)) {
+ hasHtml = true;
+ } else if (type.startsWith("text/")) {
+ hasText = true;
+ }
+ }
+ }
+
+ // If the provider can serve data we can use, open and load it.
+ if (hasHtml || hasText) {
+ FileInputStream stream = null;
+ try {
+ // Ask for a stream of the desired type.
+ AssetFileDescriptor descr = context.getContentResolver()
+ .openTypedAssetFileDescriptor(mUri,
+ hasHtml ? "text/html" : "text/plain", null);
+ stream = descr.createInputStream();
+ InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+ // Got it... copy the stream into a local string and return it.
+ StringBuilder builder = new StringBuilder(128);
+ char[] buffer = new char[8192];
+ int len;
+ while ((len=reader.read(buffer)) > 0) {
+ builder.append(buffer, 0, len);
+ }
+ String text = builder.toString();
+ if (hasHtml) {
+ if (styled) {
+ // We loaded HTML formatted text and the caller
+ // want styled text, convert it.
+ try {
+ CharSequence newText = Html.fromHtml(text);
+ return newText != null ? newText : text;
+ } catch (RuntimeException e) {
+ return text;
+ }
+ } else {
+ // We loaded HTML formatted text and that is what
+ // the caller wants, just return it.
+ return text.toString();
+ }
+ }
+ if (styled) {
+ // We loaded plain text and the caller wants styled
+ // text, that is all we have so return it.
+ return text;
+ } else {
+ // We loaded plain text and the caller wants HTML
+ // text, escape it for HTML.
+ return Html.escapeHtml(text);
+ }
+
+ } catch (SecurityException e) {
+ Log.w("ClipData", "Failure opening stream", e);
+
+ } catch (FileNotFoundException e) {
+ // Unable to open content URI as text... not really an
+ // error, just something to ignore.
+
+ } catch (IOException e) {
+ // Something bad has happened.
+ Log.w("ClipData", "Failure loading text", e);
+ return Html.escapeHtml(e.toString());
+
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // If we couldn't open the URI as a stream, use the URI itself as a textual
+ // representation (but not for "content", "android.resource" or "file" schemes).
+ final String scheme = mUri.getScheme();
+ if (SCHEME_CONTENT.equals(scheme)
+ || SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || SCHEME_FILE.equals(scheme)) {
+ return "";
+ }
+
+ if (styled) {
+ return uriToStyledText(mUri.toString());
+ } else {
+ return uriToHtml(mUri.toString());
+ }
+ }
+
+ // Finally, if all we have is an Intent, then we can just turn that
+ // into text. Not the most user-friendly thing, but it's something.
+ if (mIntent != null) {
+ if (styled) {
+ return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME));
+ } else {
+ return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME));
+ }
+ }
+
+ // Shouldn't get here, but just in case...
+ return "";
+ }
+
+ private String uriToHtml(String uri) {
+ StringBuilder builder = new StringBuilder(256);
+ builder.append("<a href=\"");
+ builder.append(Html.escapeHtml(uri));
+ builder.append("\">");
+ builder.append(Html.escapeHtml(uri));
+ builder.append("</a>");
+ return builder.toString();
+ }
+
+ private CharSequence uriToStyledText(String uri) {
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ builder.append(uri);
+ builder.setSpan(new URLSpan(uri), 0, builder.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("ClipData.Item { ");
+ toShortString(b, true);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /**
+ * Appends this item to the given builder.
+ * @param redactContent If true, redacts common forms of PII; otherwise appends full
+ * details.
+ * @hide
+ */
+ public void toShortString(StringBuilder b, boolean redactContent) {
+ boolean first = true;
+ if (mHtmlText != null) {
+ first = false;
+ if (redactContent) {
+ b.append("H(").append(mHtmlText.length()).append(')');
+ } else {
+ b.append("H:").append(mHtmlText);
+ }
+ }
+ if (mText != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ if (redactContent) {
+ b.append("T(").append(mText.length()).append(')');
+ } else {
+ b.append("T:").append(mText);
+ }
+ }
+ if (mUri != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ if (redactContent) {
+ b.append("U(").append(mUri.getScheme()).append(')');
+ } else {
+ b.append("U:").append(mUri);
+ }
+ }
+ if (mIntent != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("I:");
+ mIntent.toShortString(b, redactContent, true, true, true);
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ if (mHtmlText != null) {
+ proto.write(ClipDataProto.Item.HTML_TEXT, mHtmlText);
+ } else if (mText != null) {
+ proto.write(ClipDataProto.Item.TEXT, mText.toString());
+ } else if (mUri != null) {
+ proto.write(ClipDataProto.Item.URI, mUri.toString());
+ } else if (mIntent != null) {
+ mIntent.dumpDebug(proto, ClipDataProto.Item.INTENT, true, true, true, true);
+ } else {
+ proto.write(ClipDataProto.Item.NOTHING, true);
+ }
+
+ proto.end(token);
+ }
+ }
+
+ /**
+ * Create a new clip.
+ *
+ * @param label Label to show to the user describing this clip.
+ * @param mimeTypes An array of MIME types this data is available as.
+ * @param item The contents of the first item in the clip.
+ */
+ public ClipData(CharSequence label, String[] mimeTypes, Item item) {
+ mClipDescription = new ClipDescription(label, mimeTypes);
+ if (item == null) {
+ throw new NullPointerException("item is null");
+ }
+ mIcon = null;
+ mItems = new ArrayList<Item>();
+ mItems.add(item);
+ mClipDescription.setIsStyledText(isStyledText());
+ }
+
+ /**
+ * Create a new clip.
+ *
+ * @param description The ClipDescription describing the clip contents.
+ * @param item The contents of the first item in the clip.
+ */
+ public ClipData(ClipDescription description, Item item) {
+ mClipDescription = description;
+ if (item == null) {
+ throw new NullPointerException("item is null");
+ }
+ mIcon = null;
+ mItems = new ArrayList<Item>();
+ mItems.add(item);
+ mClipDescription.setIsStyledText(isStyledText());
+ }
+
+ /**
+ * Create a new clip.
+ *
+ * @param description The ClipDescription describing the clip contents.
+ * @param items The items in the clip. Not that a defensive copy of this
+ * list is not made, so caution should be taken to ensure the
+ * list is not available for further modification.
+ * @hide
+ */
+ public ClipData(ClipDescription description, ArrayList<Item> items) {
+ mClipDescription = description;
+ if (items == null) {
+ throw new NullPointerException("item is null");
+ }
+ mIcon = null;
+ mItems = items;
+ }
+
+ /**
+ * Create a new clip that is a copy of another clip. This does a deep-copy
+ * of all items in the clip.
+ *
+ * @param other The existing ClipData that is to be copied.
+ */
+ public ClipData(ClipData other) {
+ mClipDescription = other.mClipDescription;
+ mIcon = other.mIcon;
+ mItems = new ArrayList<Item>(other.mItems);
+ }
+
+ /**
+ * Returns a copy of the ClipData which will parcel the Item's activity infos.
+ * @hide
+ */
+ public ClipData copyForTransferWithActivityInfo() {
+ ClipData copy = new ClipData(this);
+ copy.mParcelItemActivityInfos = true;
+ return copy;
+ }
+
+ /**
+ * Returns whether this clip data will parcel the Item's activity infos.
+ * @hide
+ */
+ public boolean willParcelWithActivityInfo() {
+ return mParcelItemActivityInfos;
+ }
+
+ /**
+ * Create a new ClipData holding data of the type
+ * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
+ *
+ * @param label User-visible label for the clip data.
+ * @param text The actual text in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newPlainText(CharSequence label, CharSequence text) {
+ Item item = new Item(text);
+ return new ClipData(label, MIMETYPES_TEXT_PLAIN, item);
+ }
+
+ /**
+ * Create a new ClipData holding data of the type
+ * {@link ClipDescription#MIMETYPE_TEXT_HTML}.
+ *
+ * @param label User-visible label for the clip data.
+ * @param text The text of clip as plain text, for receivers that don't
+ * handle HTML. This is required.
+ * @param htmlText The actual HTML text in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newHtmlText(CharSequence label, CharSequence text,
+ String htmlText) {
+ Item item = new Item(text, htmlText);
+ return new ClipData(label, MIMETYPES_TEXT_HTML, item);
+ }
+
+ /**
+ * Create a new ClipData holding an Intent with MIME type
+ * {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
+ *
+ * @param label User-visible label for the clip data.
+ * @param intent The actual Intent in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newIntent(CharSequence label, Intent intent) {
+ Item item = new Item(intent);
+ return new ClipData(label, MIMETYPES_TEXT_INTENT, item);
+ }
+
+ /**
+ * Create a new ClipData holding a URI. If the URI is a content: URI,
+ * this will query the content provider for the MIME type of its data and
+ * use that as the MIME type. Otherwise, it will use the MIME type
+ * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
+ *
+ * @param resolver ContentResolver used to get information about the URI.
+ * @param label User-visible label for the clip data.
+ * @param uri The URI in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newUri(ContentResolver resolver, CharSequence label,
+ Uri uri) {
+ Item item = new Item(uri);
+ String[] mimeTypes = getMimeTypes(resolver, uri);
+ return new ClipData(label, mimeTypes, item);
+ }
+
+ /**
+ * Finds all applicable MIME types for a given URI.
+ *
+ * @param resolver ContentResolver used to get information about the URI.
+ * @param uri The URI.
+ * @return Returns an array of MIME types.
+ */
+ private static String[] getMimeTypes(ContentResolver resolver, Uri uri) {
+ String[] mimeTypes = null;
+ if (SCHEME_CONTENT.equals(uri.getScheme())) {
+ String realType = resolver.getType(uri);
+ mimeTypes = resolver.getStreamTypes(uri, "*/*");
+ if (realType != null) {
+ if (mimeTypes == null) {
+ mimeTypes = new String[] { realType };
+ } else if (!ArrayUtils.contains(mimeTypes, realType)) {
+ String[] tmp = new String[mimeTypes.length + 1];
+ tmp[0] = realType;
+ System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length);
+ mimeTypes = tmp;
+ }
+ }
+ }
+ if (mimeTypes == null) {
+ mimeTypes = MIMETYPES_TEXT_URILIST;
+ }
+ return mimeTypes;
+ }
+
+ /**
+ * Create a new ClipData holding an URI with MIME type
+ * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
+ * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing
+ * is inferred about the URI -- if it is a content: URI holding a bitmap,
+ * the reported type will still be uri-list. Use this with care!
+ *
+ * @param label User-visible label for the clip data.
+ * @param uri The URI in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newRawUri(CharSequence label, Uri uri) {
+ Item item = new Item(uri);
+ return new ClipData(label, MIMETYPES_TEXT_URILIST, item);
+ }
+
+ /**
+ * Return the {@link ClipDescription} associated with this data, describing
+ * what it contains.
+ */
+ public ClipDescription getDescription() {
+ return mClipDescription;
+ }
+
+ /**
+ * Add a new Item to the overall ClipData container.
+ * <p> This method will <em>not</em> update the list of available MIME types in the
+ * {@link ClipDescription}. It should be used only when adding items which do not add new
+ * MIME types to this clip. If this is not the case, use {@link #addItem(ContentResolver, Item)}
+ * or call {@link #ClipData(CharSequence, String[], Item)} with a complete list of MIME types.
+ * @param item Item to be added.
+ */
+ public void addItem(Item item) {
+ if (item == null) {
+ throw new NullPointerException("item is null");
+ }
+ mItems.add(item);
+ if (mItems.size() == 1) {
+ mClipDescription.setIsStyledText(isStyledText());
+ }
+ }
+
+ /**
+ * Add a new Item to the overall ClipData container.
+ * <p> Unlike {@link #addItem(Item)}, this method will update the list of available MIME types
+ * in the {@link ClipDescription}.
+ * @param resolver ContentResolver used to get information about the URI possibly contained in
+ * the item.
+ * @param item Item to be added.
+ */
+ public void addItem(ContentResolver resolver, Item item) {
+ addItem(item);
+
+ if (item.getHtmlText() != null) {
+ mClipDescription.addMimeTypes(MIMETYPES_TEXT_HTML);
+ } else if (item.getText() != null) {
+ mClipDescription.addMimeTypes(MIMETYPES_TEXT_PLAIN);
+ }
+
+ if (item.getIntent() != null) {
+ mClipDescription.addMimeTypes(MIMETYPES_TEXT_INTENT);
+ }
+
+ if (item.getUri() != null) {
+ mClipDescription.addMimeTypes(getMimeTypes(resolver, item.getUri()));
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Return the number of items in the clip data.
+ */
+ public int getItemCount() {
+ return mItems.size();
+ }
+
+ /**
+ * Return a single item inside of the clip data. The index can range
+ * from 0 to {@link #getItemCount()}-1.
+ */
+ public Item getItemAt(int index) {
+ return mItems.get(index);
+ }
+
+ /** @hide */
+ public void setItemAt(int index, Item item) {
+ mItems.set(index, item);
+ }
+
+ /**
+ * Prepare this {@link ClipData} to leave an app process.
+ *
+ * @hide
+ */
+ public void prepareToLeaveProcess(boolean leavingPackage) {
+ // Assume that callers are going to be granting permissions
+ prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+
+ /**
+ * Prepare this {@link ClipData} to leave an app process.
+ *
+ * @hide
+ */
+ public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ item.mIntent.prepareToLeaveProcess(leavingPackage);
+ }
+ if (item.mUri != null && leavingPackage) {
+ if (StrictMode.vmFileUriExposureEnabled()) {
+ item.mUri.checkFileUriExposed("ClipData.Item.getUri()");
+ }
+ if (StrictMode.vmContentUriWithoutPermissionEnabled()) {
+ item.mUri.checkContentUriWithoutPermission("ClipData.Item.getUri()",
+ intentFlags);
+ }
+ }
+ }
+ }
+
+ /** {@hide} */
+ public void prepareToEnterProcess(AttributionSource source) {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ // We can't recursively claim that this data is from a protected
+ // component, since it may have been filled in by a malicious app
+ item.mIntent.prepareToEnterProcess(false, source);
+ }
+ }
+ }
+
+ /** @hide */
+ public void fixUris(int contentUserHint) {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ item.mIntent.fixUris(contentUserHint);
+ }
+ if (item.mUri != null) {
+ item.mUri = maybeAddUserId(item.mUri, contentUserHint);
+ }
+ }
+ }
+
+ /**
+ * Only fixing the data field of the intents
+ * @hide
+ */
+ public void fixUrisLight(int contentUserHint) {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ Uri data = item.mIntent.getData();
+ if (data != null) {
+ item.mIntent.setData(maybeAddUserId(data, contentUserHint));
+ }
+ }
+ if (item.mUri != null) {
+ item.mUri = maybeAddUserId(item.mUri, contentUserHint);
+ }
+ }
+ }
+
+ private boolean isStyledText() {
+ if (mItems.isEmpty()) {
+ return false;
+ }
+ final CharSequence text = mItems.get(0).getText();
+ if (text instanceof Spanned) {
+ Spanned spanned = (Spanned) text;
+ if (TextUtils.hasStyleSpan(spanned)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("ClipData { ");
+ toShortString(b, true);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /**
+ * Appends this clip to the given builder.
+ * @param redactContent If true, redacts common forms of PII; otherwise appends full details.
+ * @hide
+ */
+ public void toShortString(StringBuilder b, boolean redactContent) {
+ boolean first;
+ if (mClipDescription != null) {
+ first = !mClipDescription.toShortString(b, redactContent);
+ } else {
+ first = true;
+ }
+ if (mIcon != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("I:");
+ b.append(mIcon.getWidth());
+ b.append('x');
+ b.append(mIcon.getHeight());
+ }
+ if (mItems.size() != 1) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append(mItems.size()).append(" items:");
+ }
+ for (int i = 0; i < mItems.size(); i++) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append('{');
+ mItems.get(i).toShortString(b, redactContent);
+ b.append('}');
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ if (mClipDescription != null) {
+ mClipDescription.dumpDebug(proto, ClipDataProto.DESCRIPTION);
+ }
+ if (mIcon != null) {
+ final long iToken = proto.start(ClipDataProto.ICON);
+ proto.write(ClipDataProto.Icon.WIDTH, mIcon.getWidth());
+ proto.write(ClipDataProto.Icon.HEIGHT, mIcon.getHeight());
+ proto.end(iToken);
+ }
+ for (int i = 0; i < mItems.size(); i++) {
+ mItems.get(i).dumpDebug(proto, ClipDataProto.ITEMS);
+ }
+
+ proto.end(token);
+ }
+
+ /** @hide */
+ public void collectUris(List<Uri> out) {
+ for (int i = 0; i < mItems.size(); ++i) {
+ ClipData.Item item = getItemAt(i);
+
+ if (item.getUri() != null) {
+ out.add(item.getUri());
+ }
+
+ Intent intent = item.getIntent();
+ if (intent != null) {
+ if (intent.getData() != null) {
+ out.add(intent.getData());
+ }
+ if (intent.getClipData() != null) {
+ intent.getClipData().collectUris(out);
+ }
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mClipDescription.writeToParcel(dest, flags);
+ if (mIcon != null) {
+ dest.writeInt(1);
+ mIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ final int N = mItems.size();
+ dest.writeInt(N);
+ for (int i=0; i<N; i++) {
+ Item item = mItems.get(i);
+ TextUtils.writeToParcel(item.mText, dest, flags);
+ dest.writeString8(item.mHtmlText);
+ dest.writeTypedObject(item.mIntent, flags);
+ dest.writeTypedObject(item.mUri, flags);
+ dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags);
+ dest.writeTypedObject(item.mTextLinks, flags);
+ }
+ }
+
+ ClipData(Parcel in) {
+ mClipDescription = new ClipDescription(in);
+ if (in.readInt() != 0) {
+ mIcon = Bitmap.CREATOR.createFromParcel(in);
+ } else {
+ mIcon = null;
+ }
+ mItems = new ArrayList<>();
+ final int N = in.readInt();
+ for (int i=0; i<N; i++) {
+ CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ String htmlText = in.readString8();
+ Intent intent = in.readTypedObject(Intent.CREATOR);
+ Uri uri = in.readTypedObject(Uri.CREATOR);
+ ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
+ TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
+ Item item = new Item(text, htmlText, intent, uri);
+ item.setActivityInfo(info);
+ item.setTextLinks(textLinks);
+ mItems.add(item);
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR =
+ new Parcelable.Creator<ClipData>() {
+
+ @Override
+ public ClipData createFromParcel(Parcel source) {
+ return new ClipData(source);
+ }
+
+ @Override
+ public ClipData[] newArray(int size) {
+ return new ClipData[size];
+ }
+ };
+}
diff --git a/android/content/ClipDescription.java b/android/content/ClipDescription.java
new file mode 100644
index 0000000..f49362e
--- /dev/null
+++ b/android/content/ClipDescription.java
@@ -0,0 +1,585 @@
+/**
+ * Copyright (c) 2010, 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.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Meta-data describing the contents of a {@link ClipData}. Provides enough
+ * information to know if you can handle the ClipData, but not the data
+ * itself.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using the clipboard framework, read the
+ * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
+ * developer guide.</p>
+ * </div>
+ */
+public class ClipDescription implements Parcelable {
+ /**
+ * The MIME type for a clip holding plain text.
+ */
+ public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
+
+ /**
+ * The MIME type for a clip holding HTML text.
+ */
+ public static final String MIMETYPE_TEXT_HTML = "text/html";
+
+ /**
+ * The MIME type for a clip holding one or more URIs. This should be
+ * used for URIs that are meaningful to a user (such as an http: URI).
+ * It should <em>not</em> be used for a content: URI that references some
+ * other piece of data; in that case the MIME type should be the type
+ * of the referenced data.
+ */
+ public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
+
+ /**
+ * The MIME type for a clip holding an Intent.
+ */
+ public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
+
+ /**
+ * The MIME type for an activity. The ClipData must include intents with required extras
+ * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
+ * {@link #EXTRA_ACTIVITY_OPTIONS}.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity";
+
+ /**
+ * The MIME type for a shortcut. The ClipData must include intents with required extras
+ * {@link Intent#EXTRA_SHORTCUT_ID}, {@link Intent#EXTRA_PACKAGE_NAME} and
+ * {@link Intent#EXTRA_USER}, and an optional {@link #EXTRA_ACTIVITY_OPTIONS}.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut";
+
+ /**
+ * The MIME type for a task. The ClipData must include an intent with a required extra
+ * {@link Intent#EXTRA_TASK_ID} of the task to launch.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_TASK = "application/vnd.android.task";
+
+ /**
+ * The MIME type for data whose type is otherwise unknown.
+ * <p>
+ * Per RFC 2046, the "application" media type is to be used for discrete
+ * data which do not fit in any of the other categories, and the
+ * "octet-stream" subtype is used to indicate that a body contains arbitrary
+ * binary data.
+ */
+ public static final String MIMETYPE_UNKNOWN = "application/octet-stream";
+
+ /**
+ * The pending intent for the activity to launch.
+ * <p>
+ * Type: PendingIntent
+ * </p>
+ * @hide
+ */
+ public static final String EXTRA_PENDING_INTENT = "android.intent.extra.PENDING_INTENT";
+
+ /**
+ * The activity options bundle to use when launching an activity.
+ * <p>
+ * Type: Bundle
+ * </p>
+ * @hide
+ */
+ public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value =
+ { CLASSIFICATION_NOT_COMPLETE, CLASSIFICATION_NOT_PERFORMED, CLASSIFICATION_COMPLETE})
+ @interface ClassificationStatus {}
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification has not been
+ * completed on the associated clip. This will be always be the case if the clip has not been
+ * copied to clipboard, or if there is no associated clip.
+ */
+ public static final int CLASSIFICATION_NOT_COMPLETE = 1;
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification was not and will
+ * not be performed on the associated clip. This may be the case if the clip does not contain
+ * text in its first item, or if the text is too long.
+ */
+ public static final int CLASSIFICATION_NOT_PERFORMED = 2;
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification has been
+ * completed.
+ */
+ public static final int CLASSIFICATION_COMPLETE = 3;
+
+ final CharSequence mLabel;
+ private final ArrayList<String> mMimeTypes;
+ private PersistableBundle mExtras;
+ private long mTimeStamp;
+ private boolean mIsStyledText;
+ private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+ private int mClassificationStatus = CLASSIFICATION_NOT_COMPLETE;
+
+ /**
+ * Create a new clip.
+ *
+ * @param label Label to show to the user describing this clip.
+ * @param mimeTypes An array of MIME types this data is available as.
+ */
+ public ClipDescription(CharSequence label, String[] mimeTypes) {
+ if (mimeTypes == null) {
+ throw new NullPointerException("mimeTypes is null");
+ }
+ mLabel = label;
+ mMimeTypes = new ArrayList<String>(Arrays.asList(mimeTypes));
+ }
+
+ /**
+ * Create a copy of a ClipDescription.
+ */
+ public ClipDescription(ClipDescription o) {
+ mLabel = o.mLabel;
+ mMimeTypes = new ArrayList<String>(o.mMimeTypes);
+ mTimeStamp = o.mTimeStamp;
+ }
+
+ /**
+ * Helper to compare two MIME types, where one may be a pattern.
+ * @param concreteType A fully-specified MIME type.
+ * @param desiredType A desired MIME type that may be a pattern such as */*.
+ * @return Returns true if the two MIME types match.
+ */
+ public static boolean compareMimeTypes(String concreteType, String desiredType) {
+ final int typeLength = desiredType.length();
+ if (typeLength == 3 && desiredType.equals("*/*")) {
+ return true;
+ }
+
+ final int slashpos = desiredType.indexOf('/');
+ if (slashpos > 0) {
+ if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
+ if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
+ return true;
+ }
+ } else if (desiredType.equals(concreteType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Used for setting the timestamp at which the associated {@link ClipData} is copied to
+ * global clipboard.
+ *
+ * @param timeStamp at which the associated {@link ClipData} is copied to clipboard in
+ * {@link System#currentTimeMillis()} time base.
+ * @hide
+ */
+ public void setTimestamp(long timeStamp) {
+ mTimeStamp = timeStamp;
+ }
+
+ /**
+ * Return the timestamp at which the associated {@link ClipData} is copied to global clipboard
+ * in the {@link System#currentTimeMillis()} time base.
+ *
+ * @return timestamp at which the associated {@link ClipData} is copied to global clipboard
+ * or {@code 0} if it is not copied to clipboard.
+ */
+ public long getTimestamp() {
+ return mTimeStamp;
+ }
+
+ /**
+ * Return the label for this clip.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Check whether the clip description contains the given MIME type.
+ *
+ * @param mimeType The desired MIME type. May be a pattern.
+ * @return Returns true if one of the MIME types in the clip description
+ * matches the desired MIME type, else false.
+ */
+ public boolean hasMimeType(String mimeType) {
+ final int size = mMimeTypes.size();
+ for (int i=0; i<size; i++) {
+ if (compareMimeTypes(mMimeTypes.get(i), mimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the clip description contains any of the given MIME types.
+ *
+ * @param targetMimeTypes The target MIME types. May use patterns.
+ * @return Returns true if at least one of the MIME types in the clip description matches at
+ * least one of the target MIME types, else false.
+ *
+ * @hide
+ */
+ public boolean hasMimeType(@NonNull String[] targetMimeTypes) {
+ for (String targetMimeType : targetMimeTypes) {
+ if (hasMimeType(targetMimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Filter the clip description MIME types by the given MIME type. Returns
+ * all MIME types in the clip that match the given MIME type.
+ *
+ * @param mimeType The desired MIME type. May be a pattern.
+ * @return Returns an array of all matching MIME types. If there are no
+ * matching MIME types, null is returned.
+ */
+ public String[] filterMimeTypes(String mimeType) {
+ ArrayList<String> array = null;
+ final int size = mMimeTypes.size();
+ for (int i=0; i<size; i++) {
+ if (compareMimeTypes(mMimeTypes.get(i), mimeType)) {
+ if (array == null) {
+ array = new ArrayList<String>();
+ }
+ array.add(mMimeTypes.get(i));
+ }
+ }
+ if (array == null) {
+ return null;
+ }
+ String[] rawArray = new String[array.size()];
+ array.toArray(rawArray);
+ return rawArray;
+ }
+
+ /**
+ * Return the number of MIME types the clip is available in.
+ */
+ public int getMimeTypeCount() {
+ return mMimeTypes.size();
+ }
+
+ /**
+ * Return one of the possible clip MIME types.
+ */
+ public String getMimeType(int index) {
+ return mMimeTypes.get(index);
+ }
+
+ /**
+ * Add MIME types to the clip description.
+ */
+ void addMimeTypes(String[] mimeTypes) {
+ for (int i=0; i!=mimeTypes.length; i++) {
+ final String mimeType = mimeTypes[i];
+ if (!mMimeTypes.contains(mimeType)) {
+ mMimeTypes.add(mimeType);
+ }
+ }
+ }
+
+ /**
+ * Retrieve extended data from the clip description.
+ *
+ * @return the bundle containing extended data previously set with
+ * {@link #setExtras(PersistableBundle)}, or null if no extras have been set.
+ *
+ * @see #setExtras(PersistableBundle)
+ */
+ public PersistableBundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Add extended data to the clip description.
+ *
+ * @see #getExtras()
+ */
+ public void setExtras(PersistableBundle extras) {
+ mExtras = new PersistableBundle(extras);
+ }
+
+ /** @hide */
+ public void validate() {
+ if (mMimeTypes == null) {
+ throw new NullPointerException("null mime types");
+ }
+ final int size = mMimeTypes.size();
+ if (size <= 0) {
+ throw new IllegalArgumentException("must have at least 1 mime type");
+ }
+ for (int i=0; i<size; i++) {
+ if (mMimeTypes.get(i) == null) {
+ throw new NullPointerException("mime type at " + i + " is null");
+ }
+ }
+ }
+
+ /**
+ * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e.
+ * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link
+ * android.text.style.ParagraphStyle ParagraphStyle}, or {@link
+ * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if
+ * there is no associated clip data.
+ */
+ public boolean isStyledText() {
+ return mIsStyledText;
+ }
+
+ /**
+ * Sets whether the associated {@link ClipData} contains styled text in its first item. This
+ * should be called when this description is associated with clip data or when the first item
+ * is added to the associated clip data.
+ */
+ void setIsStyledText(boolean isStyledText) {
+ mIsStyledText = isStyledText;
+ }
+
+ /**
+ * Sets the current status of text classification for the associated clip.
+ *
+ * @hide
+ */
+ public void setClassificationStatus(@ClassificationStatus int status) {
+ mClassificationStatus = status;
+ }
+
+ /**
+ * Returns a score indicating confidence that an instance of the given entity is present in the
+ * first item of the clip data, if that item is plain text and text classification has been
+ * performed. The value ranges from 0 (low confidence) to 1 (high confidence). 0 indicates that
+ * the entity was not found in the classified text.
+ *
+ * <p>Entities should be as defined in the {@link TextClassifier} class, such as
+ * {@link TextClassifier#TYPE_ADDRESS}, {@link TextClassifier#TYPE_URL}, or
+ * {@link TextClassifier#TYPE_EMAIL}.
+ *
+ * <p>If the result is positive for any entity, the full classification result as a
+ * {@link TextLinks} object may be obtained using the {@link ClipData.Item#getTextLinks()}
+ * method.
+ *
+ * @throws IllegalStateException if {@link #getClassificationStatus()} is not
+ * {@link #CLASSIFICATION_COMPLETE}
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@NonNull @TextClassifier.EntityType String entity) {
+ if (mClassificationStatus != CLASSIFICATION_COMPLETE) {
+ throw new IllegalStateException("Classification not complete");
+ }
+ return mEntityConfidence.getOrDefault(entity, 0f);
+ }
+
+ /**
+ * Returns {@link #CLASSIFICATION_COMPLETE} if text classification has been performed on the
+ * associated {@link ClipData}. If this is the case then {@link #getConfidenceScore} may be used
+ * to retrieve information about entities within the text. Otherwise, returns
+ * {@link #CLASSIFICATION_NOT_COMPLETE} if classification has not yet returned results, or
+ * {@link #CLASSIFICATION_NOT_PERFORMED} if classification was not attempted (e.g. because the
+ * text was too long).
+ */
+ public @ClassificationStatus int getClassificationStatus() {
+ return mClassificationStatus;
+ }
+
+ /**
+ * @hide
+ */
+ public void setConfidenceScores(Map<String, Float> confidences) {
+ mEntityConfidence.clear();
+ mEntityConfidence.putAll(confidences);
+ mClassificationStatus = CLASSIFICATION_COMPLETE;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("ClipDescription { ");
+ toShortString(b, true);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /**
+ * Appends this description to the given builder.
+ * @param redactContent If true, redacts common forms of PII; otherwise appends full details.
+ * @hide
+ */
+ public boolean toShortString(StringBuilder b, boolean redactContent) {
+ boolean first = !toShortStringTypesOnly(b);
+ if (mLabel != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ if (redactContent) {
+ b.append("hasLabel(").append(mLabel.length()).append(')');
+ } else {
+ b.append('"').append(mLabel).append('"');
+ }
+ }
+ if (mExtras != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ if (redactContent) {
+ if (mExtras.isParcelled()) {
+ // We don't want this toString function to trigger un-parcelling.
+ b.append("hasExtras");
+ } else {
+ b.append("hasExtras(").append(mExtras.size()).append(')');
+ }
+ } else {
+ b.append(mExtras.toString());
+ }
+ }
+ if (mTimeStamp > 0) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append('<');
+ b.append(TimeUtils.logTimeOfDay(mTimeStamp));
+ b.append('>');
+ }
+ return !first;
+ }
+
+ /** @hide */
+ public boolean toShortStringTypesOnly(StringBuilder b) {
+ boolean first = true;
+ final int size = mMimeTypes.size();
+ for (int i=0; i<size; i++) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append(mMimeTypes.get(i));
+ }
+ return !first;
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ final int size = mMimeTypes.size();
+ for (int i = 0; i < size; i++) {
+ proto.write(ClipDescriptionProto.MIME_TYPES, mMimeTypes.get(i));
+ }
+
+ if (mLabel != null) {
+ proto.write(ClipDescriptionProto.LABEL, mLabel.toString());
+ }
+ if (mExtras != null) {
+ mExtras.dumpDebug(proto, ClipDescriptionProto.EXTRAS);
+ }
+ if (mTimeStamp > 0) {
+ proto.write(ClipDescriptionProto.TIMESTAMP_MS, mTimeStamp);
+ }
+
+ proto.end(token);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ dest.writeStringList(mMimeTypes);
+ dest.writePersistableBundle(mExtras);
+ dest.writeLong(mTimeStamp);
+ dest.writeBoolean(mIsStyledText);
+ dest.writeInt(mClassificationStatus);
+ dest.writeBundle(confidencesToBundle());
+ }
+
+ private Bundle confidencesToBundle() {
+ Bundle bundle = new Bundle();
+ int size = mEntityConfidence.size();
+ for (int i = 0; i < size; i++) {
+ bundle.putFloat(mEntityConfidence.keyAt(i), mEntityConfidence.valueAt(i));
+ }
+ return bundle;
+ }
+
+ private void readBundleToConfidences(Bundle bundle) {
+ for (String key : bundle.keySet()) {
+ mEntityConfidence.put(key, bundle.getFloat(key));
+ }
+ }
+
+ ClipDescription(Parcel in) {
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mMimeTypes = in.createStringArrayList();
+ mExtras = in.readPersistableBundle();
+ mTimeStamp = in.readLong();
+ mIsStyledText = in.readBoolean();
+ mClassificationStatus = in.readInt();
+ readBundleToConfidences(in.readBundle());
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
+ new Parcelable.Creator<ClipDescription>() {
+
+ public ClipDescription createFromParcel(Parcel source) {
+ return new ClipDescription(source);
+ }
+
+ public ClipDescription[] newArray(int size) {
+ return new ClipDescription[size];
+ }
+ };
+}
diff --git a/android/content/ClipboardManager.java b/android/content/ClipboardManager.java
new file mode 100644
index 0000000..f833175
--- /dev/null
+++ b/android/content/ClipboardManager.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+
+public class ClipboardManager extends android.text.ClipboardManager {
+
+ /**
+ * Defines a listener callback that is invoked when the primary clip on the clipboard changes.
+ * Objects that want to register a listener call
+ * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener)
+ * addPrimaryClipChangedListener()} with an
+ * object that implements OnPrimaryClipChangedListener.
+ *
+ */
+ public interface OnPrimaryClipChangedListener {
+
+ /**
+ * Callback that is invoked by {@link android.content.ClipboardManager} when the primary
+ * clip changes.
+ */
+ void onPrimaryClipChanged();
+ }
+
+ /** {@hide} */
+ public ClipboardManager(Context context, Handler handler) { }
+
+ /**
+ * Sets the current primary clip on the clipboard. This is the clip that
+ * is involved in normal cut and paste operations.
+ *
+ * @param clip The clipped data item to set.
+ * @see #getPrimaryClip()
+ * @see #clearPrimaryClip()
+ */
+ public void setPrimaryClip(@NonNull ClipData clip) { }
+
+ /**
+ * Clears any current primary clip on the clipboard.
+ *
+ * @see #setPrimaryClip(ClipData)
+ */
+ public void clearPrimaryClip() { }
+
+ /**
+ * Returns the current primary clip on the clipboard.
+ *
+ * <em>If the application is not the default IME or does not have input focus this return
+ * {@code null}.</em>
+ *
+ * @see #setPrimaryClip(ClipData)
+ */
+ public @Nullable ClipData getPrimaryClip() {
+ return null;
+ }
+
+ /**
+ * Returns a description of the current primary clip on the clipboard
+ * but not a copy of its data.
+ *
+ * <em>If the application is not the default IME or does not have input focus this return
+ * {@code null}.</em>
+ *
+ * @see #setPrimaryClip(ClipData)
+ */
+ public @Nullable ClipDescription getPrimaryClipDescription() {
+ return null;
+ }
+
+ /**
+ * Returns true if there is currently a primary clip on the clipboard.
+ *
+ * <em>If the application is not the default IME or the does not have input focus this will
+ * return {@code false}.</em>
+ */
+ public boolean hasPrimaryClip() {
+ return false;
+ }
+
+ public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) { }
+
+ public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) { }
+
+ /**
+ * @deprecated Use {@link #getPrimaryClip()} instead. This retrieves
+ * the primary clip and tries to coerce it to a string.
+ */
+ @Deprecated
+ public CharSequence getText() {
+ return null;
+ }
+
+ /**
+ * @deprecated Use {@link #setPrimaryClip(ClipData)} instead. This
+ * creates a ClippedItem holding the given text and sets it as the
+ * primary clip. It has no label or icon.
+ */
+ @Deprecated
+ public void setText(CharSequence text) { }
+
+ /**
+ * @deprecated Use {@link #hasPrimaryClip()} instead.
+ */
+ @Deprecated
+ public boolean hasText() {
+ return false;
+ }
+
+ void reportPrimaryClipChanged() { }
+}
diff --git a/android/content/ComponentCallbacks.java b/android/content/ComponentCallbacks.java
new file mode 100644
index 0000000..428545d
--- /dev/null
+++ b/android/content/ComponentCallbacks.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2006 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.NonNull;
+import android.content.res.Configuration;
+
+/**
+ * The set of callback APIs that are common to all application components
+ * ({@link android.app.Activity}, {@link android.app.Service},
+ * {@link ContentProvider}, and {@link android.app.Application}).
+ *
+ * <p class="note"><strong>Note:</strong> You should also implement the {@link
+ * ComponentCallbacks2} interface, which provides the {@link
+ * ComponentCallbacks2#onTrimMemory} callback to help your app manage its memory usage more
+ * effectively.</p>
+ */
+public interface ComponentCallbacks {
+ /**
+ * Called by the system when the device configuration changes while your
+ * component is running. Note that, unlike activities, other components
+ * are never restarted when a configuration changes: they must always deal
+ * with the results of the change, such as by re-retrieving resources.
+ *
+ * <p>At the time that this function has been called, your Resources
+ * object will have been updated to return resource values matching the
+ * new configuration.
+ *
+ * <p>For more information, read <a href="{@docRoot}guide/topics/resources/runtime-changes.html"
+ * >Handling Runtime Changes</a>.
+ *
+ * @param newConfig The new device configuration.
+ */
+ void onConfigurationChanged(@NonNull Configuration newConfig);
+
+ /**
+ * This is called when the overall system is running low on memory, and
+ * actively running processes should trim their memory usage. While
+ * the exact point at which this will be called is not defined, generally
+ * it will happen when all background process have been killed.
+ * That is, before reaching the point of killing processes hosting
+ * service and foreground UI that we would like to avoid killing.
+ *
+ * <p>You should implement this method to release
+ * any caches or other unnecessary resources you may be holding on to.
+ * The system will perform a garbage collection for you after returning from this method.
+ * <p>Preferably, you should implement {@link ComponentCallbacks2#onTrimMemory} from
+ * {@link ComponentCallbacks2} to incrementally unload your resources based on various
+ * levels of memory demands. That API is available for API level 14 and higher, so you should
+ * only use this {@link #onLowMemory} method as a fallback for older versions, which can be
+ * treated the same as {@link ComponentCallbacks2#onTrimMemory} with the {@link
+ * ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level.</p>
+ */
+ void onLowMemory();
+}
diff --git a/android/content/ComponentCallbacks2.java b/android/content/ComponentCallbacks2.java
new file mode 100644
index 0000000..6576c0f
--- /dev/null
+++ b/android/content/ComponentCallbacks2.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2006 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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Extended {@link ComponentCallbacks} interface with a new callback for
+ * finer-grained memory management. This interface is available in all application components
+ * ({@link android.app.Activity}, {@link android.app.Service},
+ * {@link ContentProvider}, and {@link android.app.Application}).
+ *
+ * <p>You should implement {@link #onTrimMemory} to incrementally release memory based on current
+ * system constraints. Using this callback to release your resources helps provide a more
+ * responsive system overall, but also directly benefits the user experience for
+ * your app by allowing the system to keep your process alive longer. That is,
+ * if you <em>don't</em> trim your resources based on memory levels defined by this callback,
+ * the system is more likely to kill your process while it is cached in the least-recently used
+ * (LRU) list, thus requiring your app to restart and restore all state when the user returns to it.
+ *
+ * <p>The values provided by {@link #onTrimMemory} do not represent a single linear progression of
+ * memory limits, but provide you different types of clues about memory availability:</p>
+ * <ul>
+ * <li>When your app is running:
+ * <ol>
+ * <li>{@link #TRIM_MEMORY_RUNNING_MODERATE} <br>The device is beginning to run low on memory.
+ * Your app is running and not killable.
+ * <li>{@link #TRIM_MEMORY_RUNNING_LOW} <br>The device is running much lower on memory.
+ * Your app is running and not killable, but please release unused resources to improve system
+ * performance (which directly impacts your app's performance).
+ * <li>{@link #TRIM_MEMORY_RUNNING_CRITICAL} <br>The device is running extremely low on memory.
+ * Your app is not yet considered a killable process, but the system will begin killing
+ * background processes if apps do not release resources, so you should release non-critical
+ * resources now to prevent performance degradation.
+ * </ol>
+ * </li>
+ * <li>When your app's visibility changes:
+ * <ol>
+ * <li>{@link #TRIM_MEMORY_UI_HIDDEN} <br>Your app's UI is no longer visible, so this is a good
+ * time to release large resources that are used only by your UI.
+ * </ol>
+ * </li>
+ * <li>When your app's process resides in the background LRU list:
+ * <ol>
+ * <li>{@link #TRIM_MEMORY_BACKGROUND} <br>The system is running low on memory and your process is
+ * near the beginning of the LRU list. Although your app process is not at a high risk of being
+ * killed, the system may already be killing processes in the LRU list, so you should release
+ * resources that are easy to recover so your process will remain in the list and resume
+ * quickly when the user returns to your app.
+ * <li>{@link #TRIM_MEMORY_MODERATE} <br>The system is running low on memory and your process is
+ * near the middle of the LRU list. If the system becomes further constrained for memory, there's a
+ * chance your process will be killed.
+ * <li>{@link #TRIM_MEMORY_COMPLETE} <br>The system is running low on memory and your process is
+ * one of the first to be killed if the system does not recover memory now. You should release
+ * absolutely everything that's not critical to resuming your app state.
+ * <p>To support API levels lower than 14, you can use the {@link #onLowMemory} method as a
+ * fallback that's roughly equivalent to the {@link ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level.
+ * </li>
+ * </ol>
+ * <p class="note"><strong>Note:</strong> When the system begins
+ * killing processes in the LRU list, although it primarily works bottom-up, it does give some
+ * consideration to which processes are consuming more memory and will thus provide more gains in
+ * memory if killed. So the less memory you consume while in the LRU list overall, the better
+ * your chances are to remain in the list and be able to quickly resume.</p>
+ * </li>
+ * </ul>
+ * <p>More information about the different stages of a process lifecycle (such as what it means
+ * to be placed in the background LRU list) is provided in the <a
+ * href="{@docRoot}guide/components/processes-and-threads.html#Lifecycle">Processes and Threads</a>
+ * document.
+ */
+public interface ComponentCallbacks2 extends ComponentCallbacks {
+
+ /** @hide */
+ @IntDef(prefix = { "TRIM_MEMORY_" }, value = {
+ TRIM_MEMORY_COMPLETE,
+ TRIM_MEMORY_MODERATE,
+ TRIM_MEMORY_BACKGROUND,
+ TRIM_MEMORY_UI_HIDDEN,
+ TRIM_MEMORY_RUNNING_CRITICAL,
+ TRIM_MEMORY_RUNNING_LOW,
+ TRIM_MEMORY_RUNNING_MODERATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrimMemoryLevel {}
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process is nearing the end
+ * of the background LRU list, and if more memory isn't found soon it will
+ * be killed.
+ */
+ static final int TRIM_MEMORY_COMPLETE = 80;
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process is around the middle
+ * of the background LRU list; freeing memory can help the system keep
+ * other processes running later in the list for better overall performance.
+ */
+ static final int TRIM_MEMORY_MODERATE = 60;
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process has gone on to the
+ * LRU list. This is a good opportunity to clean up resources that can
+ * efficiently and quickly be re-built if the user returns to the app.
+ */
+ static final int TRIM_MEMORY_BACKGROUND = 40;
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process had been showing
+ * a user interface, and is no longer doing so. Large allocations with
+ * the UI should be released at this point to allow memory to be better
+ * managed.
+ */
+ static final int TRIM_MEMORY_UI_HIDDEN = 20;
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process is not an expendable
+ * background process, but the device is running extremely low on memory
+ * and is about to not be able to keep any background processes running.
+ * Your running process should free up as many non-critical resources as it
+ * can to allow that memory to be used elsewhere. The next thing that
+ * will happen after this is {@link #onLowMemory()} called to report that
+ * nothing at all can be kept in the background, a situation that can start
+ * to notably impact the user.
+ */
+ static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process is not an expendable
+ * background process, but the device is running low on memory.
+ * Your running process should free up unneeded resources to allow that
+ * memory to be used elsewhere.
+ */
+ static final int TRIM_MEMORY_RUNNING_LOW = 10;
+
+ /**
+ * Level for {@link #onTrimMemory(int)}: the process is not an expendable
+ * background process, but the device is running moderately low on memory.
+ * Your running process may want to release some unneeded resources for
+ * use elsewhere.
+ */
+ static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
+
+ /**
+ * Called when the operating system has determined that it is a good
+ * time for a process to trim unneeded memory from its process. This will
+ * happen for example when it goes in the background and there is not enough
+ * memory to keep as many background processes running as desired. You
+ * should never compare to exact values of the level, since new intermediate
+ * values may be added -- you will typically want to compare if the value
+ * is greater or equal to a level you are interested in.
+ *
+ * <p>To retrieve the processes current trim level at any point, you can
+ * use {@link android.app.ActivityManager#getMyMemoryState
+ * ActivityManager.getMyMemoryState(RunningAppProcessInfo)}.
+ *
+ * @param level The context of the trim, giving a hint of the amount of
+ * trimming the application may like to perform.
+ */
+ void onTrimMemory(@TrimMemoryLevel int level);
+}
diff --git a/android/content/ComponentCallbacksController.java b/android/content/ComponentCallbacksController.java
new file mode 100644
index 0000000..a81aaf7
--- /dev/null
+++ b/android/content/ComponentCallbacksController.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.NonNull;
+import android.content.res.Configuration;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A helper class to manage {@link ComponentCallbacks} and {@link ComponentCallbacks2}, such as
+ * registering ,unregistering {@link ComponentCallbacks} and sending callbacks to all registered
+ * {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ * @see ComponentCallbacks
+ * @see ComponentCallbacks2
+ *
+ * @hide
+ */
+public class ComponentCallbacksController {
+ @GuardedBy("mLock")
+ private List<ComponentCallbacks> mComponentCallbacks;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Register the {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ */
+ public void registerCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null) {
+ mComponentCallbacks = new ArrayList<>();
+ }
+ mComponentCallbacks.add(callbacks);
+ }
+ }
+
+ /**
+ * Unregister the {@link ComponentCallbacks}.
+ *
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ */
+ public void unregisterCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ mComponentCallbacks.remove(callbacks);
+ }
+ }
+
+ /**
+ * Clear all registered {@link ComponentCallbacks}.
+ * It is useful when the associated {@link Context} is going to be released.
+ */
+ public void clearCallbacks() {
+ synchronized (mLock) {
+ if (mComponentCallbacks != null) {
+ mComponentCallbacks.clear();
+ }
+ }
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onConfigurationChanged(Configuration)} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ forAllComponentCallbacks(callbacks -> callbacks.onConfigurationChanged(newConfig));
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onLowMemory()} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchLowMemory() {
+ forAllComponentCallbacks(ComponentCallbacks::onLowMemory);
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks2#onTrimMemory(int)} to all registered
+ * {@link ComponentCallbacks2}.
+ */
+ public void dispatchTrimMemory(int level) {
+ forAllComponentCallbacks(callbacks -> {
+ if (callbacks instanceof ComponentCallbacks2) {
+ ((ComponentCallbacks2) callbacks).onTrimMemory(level);
+ }
+ });
+ }
+
+ private void forAllComponentCallbacks(Consumer<ComponentCallbacks> callbacksConsumer) {
+ final ComponentCallbacks[] callbacksArray;
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ callbacksArray = new ComponentCallbacks[mComponentCallbacks.size()];
+ mComponentCallbacks.toArray(callbacksArray);
+ }
+ for (ComponentCallbacks callbacks : callbacksArray) {
+ callbacksConsumer.accept(callbacks);
+ }
+ }
+}
diff --git a/android/content/ComponentName.java b/android/content/ComponentName.java
new file mode 100644
index 0000000..5f85984
--- /dev/null
+++ b/android/content/ComponentName.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2006 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.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+
+/**
+ * Identifier for a specific application component
+ * ({@link android.app.Activity}, {@link android.app.Service},
+ * {@link android.content.BroadcastReceiver}, or
+ * {@link android.content.ContentProvider}) that is available. Two
+ * pieces of information, encapsulated here, are required to identify
+ * a component: the package (a String) it exists in, and the class (a String)
+ * name inside of that package.
+ *
+ */
+public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
+ private final String mPackage;
+ private final String mClass;
+
+ /**
+ * Create a new component identifier where the class name may be specified
+ * as either absolute or relative to the containing package.
+ *
+ * <p>Relative package names begin with a <code>'.'</code> character. For a package
+ * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
+ * will return a ComponentName with the package <code>"com.example"</code>and class name
+ * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
+ * permitted.</p>
+ *
+ * @param pkg the name of the package the component exists in
+ * @param cls the name of the class inside of <var>pkg</var> that implements
+ * the component
+ * @return the new ComponentName
+ */
+ public static @NonNull ComponentName createRelative(@NonNull String pkg, @NonNull String cls) {
+ if (TextUtils.isEmpty(cls)) {
+ throw new IllegalArgumentException("class name cannot be empty");
+ }
+
+ final String fullName;
+ if (cls.charAt(0) == '.') {
+ // Relative to the package. Prepend the package name.
+ fullName = pkg + cls;
+ } else {
+ // Fully qualified package name.
+ fullName = cls;
+ }
+ return new ComponentName(pkg, fullName);
+ }
+
+ /**
+ * Create a new component identifier where the class name may be specified
+ * as either absolute or relative to the containing package.
+ *
+ * <p>Relative package names begin with a <code>'.'</code> character. For a package
+ * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
+ * will return a ComponentName with the package <code>"com.example"</code>and class name
+ * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
+ * permitted.</p>
+ *
+ * @param pkg a Context for the package implementing the component
+ * @param cls the name of the class inside of <var>pkg</var> that implements
+ * the component
+ * @return the new ComponentName
+ */
+ public static @NonNull ComponentName createRelative(@NonNull Context pkg, @NonNull String cls) {
+ return createRelative(pkg.getPackageName(), cls);
+ }
+
+ /**
+ * Create a new component identifier.
+ *
+ * @param pkg The name of the package that the component exists in. Can
+ * not be null.
+ * @param cls The name of the class inside of <var>pkg</var> that
+ * implements the component. Can not be null.
+ */
+ public ComponentName(@NonNull String pkg, @NonNull String cls) {
+ if (pkg == null) throw new NullPointerException("package name is null");
+ if (cls == null) throw new NullPointerException("class name is null");
+ mPackage = pkg;
+ mClass = cls;
+ }
+
+ /**
+ * Create a new component identifier from a Context and class name.
+ *
+ * @param pkg A Context for the package implementing the component,
+ * from which the actual package name will be retrieved.
+ * @param cls The name of the class inside of <var>pkg</var> that
+ * implements the component.
+ */
+ public ComponentName(@NonNull Context pkg, @NonNull String cls) {
+ if (cls == null) throw new NullPointerException("class name is null");
+ mPackage = pkg.getPackageName();
+ mClass = cls;
+ }
+
+ /**
+ * Create a new component identifier from a Context and Class object.
+ *
+ * @param pkg A Context for the package implementing the component, from
+ * which the actual package name will be retrieved.
+ * @param cls The Class object of the desired component, from which the
+ * actual class name will be retrieved.
+ */
+ public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) {
+ mPackage = pkg.getPackageName();
+ mClass = cls.getName();
+ }
+
+ public ComponentName clone() {
+ return new ComponentName(mPackage, mClass);
+ }
+
+ /**
+ * Return the package name of this component.
+ */
+ public @NonNull String getPackageName() {
+ return mPackage;
+ }
+
+ /**
+ * Return the class name of this component.
+ */
+ public @NonNull String getClassName() {
+ return mClass;
+ }
+
+ /**
+ * Return the class name, either fully qualified or in a shortened form
+ * (with a leading '.') if it is a suffix of the package.
+ */
+ public String getShortClassName() {
+ if (mClass.startsWith(mPackage)) {
+ int PN = mPackage.length();
+ int CN = mClass.length();
+ if (CN > PN && mClass.charAt(PN) == '.') {
+ return mClass.substring(PN, CN);
+ }
+ }
+ return mClass;
+ }
+
+ private static void appendShortClassName(StringBuilder sb, String packageName,
+ String className) {
+ if (className.startsWith(packageName)) {
+ int PN = packageName.length();
+ int CN = className.length();
+ if (CN > PN && className.charAt(PN) == '.') {
+ sb.append(className, PN, CN);
+ return;
+ }
+ }
+ sb.append(className);
+ }
+
+ private static void printShortClassName(PrintWriter pw, String packageName,
+ String className) {
+ if (className.startsWith(packageName)) {
+ int PN = packageName.length();
+ int CN = className.length();
+ if (CN > PN && className.charAt(PN) == '.') {
+ pw.write(className, PN, CN-PN);
+ return;
+ }
+ }
+ pw.print(className);
+ }
+
+ /**
+ * Helper to get {@link #flattenToShortString()} in a {@link ComponentName} reference that can
+ * be {@code null}.
+ *
+ * @hide
+ */
+ @Nullable
+ public static String flattenToShortString(@Nullable ComponentName componentName) {
+ return componentName == null ? null : componentName.flattenToShortString();
+ }
+
+ /**
+ * Return a String that unambiguously describes both the package and
+ * class names contained in the ComponentName. You can later recover
+ * the ComponentName from this string through
+ * {@link #unflattenFromString(String)}.
+ *
+ * @return Returns a new String holding the package and class names. This
+ * is represented as the package name, concatenated with a '/' and then the
+ * class name.
+ *
+ * @see #unflattenFromString(String)
+ */
+ public @NonNull String flattenToString() {
+ return mPackage + "/" + mClass;
+ }
+
+ /**
+ * The same as {@link #flattenToString()}, but abbreviates the class
+ * name if it is a suffix of the package. The result can still be used
+ * with {@link #unflattenFromString(String)}.
+ *
+ * @return Returns a new String holding the package and class names. This
+ * is represented as the package name, concatenated with a '/' and then the
+ * class name.
+ *
+ * @see #unflattenFromString(String)
+ */
+ public @NonNull String flattenToShortString() {
+ StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length());
+ appendShortString(sb, mPackage, mClass);
+ return sb.toString();
+ }
+
+ /** @hide */
+ public void appendShortString(StringBuilder sb) {
+ appendShortString(sb, mPackage, mClass);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static void appendShortString(StringBuilder sb, String packageName, String className) {
+ sb.append(packageName).append('/');
+ appendShortClassName(sb, packageName, className);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static void printShortString(PrintWriter pw, String packageName, String className) {
+ pw.print(packageName);
+ pw.print('/');
+ printShortClassName(pw, packageName, className);
+ }
+
+ /**
+ * Recover a ComponentName from a String that was previously created with
+ * {@link #flattenToString()}. It splits the string at the first '/',
+ * taking the part before as the package name and the part after as the
+ * class name. As a special convenience (to use, for example, when
+ * parsing component names on the command line), if the '/' is immediately
+ * followed by a '.' then the final class name will be the concatenation
+ * of the package name with the string following the '/'. Thus
+ * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
+ *
+ * @param str The String that was returned by flattenToString().
+ * @return Returns a new ComponentName containing the package and class
+ * names that were encoded in <var>str</var>
+ *
+ * @see #flattenToString()
+ */
+ public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
+ int sep = str.indexOf('/');
+ if (sep < 0 || (sep+1) >= str.length()) {
+ return null;
+ }
+ String pkg = str.substring(0, sep);
+ String cls = str.substring(sep+1);
+ if (cls.length() > 0 && cls.charAt(0) == '.') {
+ cls = pkg + cls;
+ }
+ return new ComponentName(pkg, cls);
+ }
+
+ /**
+ * Return string representation of this class without the class's name
+ * as a prefix.
+ */
+ public String toShortString() {
+ return "{" + mPackage + "/" + mClass + "}";
+ }
+
+ @Override
+ public String toString() {
+ return "ComponentInfo{" + mPackage + "/" + mClass + "}";
+ }
+
+ /** Put this here so that individual services don't have to reimplement this. @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
+ proto.write(ComponentNameProto.CLASS_NAME, mClass);
+ proto.end(token);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Two components are considered to be equal if the packages in which they reside have the
+ * same name, and if the classes that implement each component also have the same name.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ try {
+ if (obj != null) {
+ ComponentName other = (ComponentName)obj;
+ // Note: no null checks, because mPackage and mClass can
+ // never be null.
+ return mPackage.equals(other.mPackage)
+ && mClass.equals(other.mClass);
+ }
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPackage.hashCode() + mClass.hashCode();
+ }
+
+ public int compareTo(ComponentName that) {
+ int v;
+ v = this.mPackage.compareTo(that.mPackage);
+ if (v != 0) {
+ return v;
+ }
+ return this.mClass.compareTo(that.mClass);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ // WARNING: If you modify this function, also update
+ // frameworks/base/libs/services/src/content/ComponentName.cpp.
+ out.writeString(mPackage);
+ out.writeString(mClass);
+ }
+
+ /**
+ * Write a ComponentName to a Parcel, handling null pointers. Must be
+ * read with {@link #readFromParcel(Parcel)}.
+ *
+ * @param c The ComponentName to be written.
+ * @param out The Parcel in which the ComponentName will be placed.
+ *
+ * @see #readFromParcel(Parcel)
+ */
+ public static void writeToParcel(ComponentName c, Parcel out) {
+ if (c != null) {
+ c.writeToParcel(out, 0);
+ } else {
+ out.writeString(null);
+ }
+ }
+
+ /**
+ * Read a ComponentName from a Parcel that was previously written
+ * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
+ * a null or new object as appropriate.
+ *
+ * @param in The Parcel from which to read the ComponentName
+ * @return Returns a new ComponentName matching the previously written
+ * object, or null if a null had been written.
+ *
+ * @see #writeToParcel(ComponentName, Parcel)
+ */
+ public static ComponentName readFromParcel(Parcel in) {
+ String pkg = in.readString();
+ return pkg != null ? new ComponentName(pkg, in) : null;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ComponentName> CREATOR
+ = new Parcelable.Creator<ComponentName>() {
+ public ComponentName createFromParcel(Parcel in) {
+ return new ComponentName(in);
+ }
+
+ public ComponentName[] newArray(int size) {
+ return new ComponentName[size];
+ }
+ };
+
+ /**
+ * Instantiate a new ComponentName from the data in a Parcel that was
+ * previously written with {@link #writeToParcel(Parcel, int)}. Note that you
+ * must not use this with data written by
+ * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
+ * to handle a null ComponentObject here.
+ *
+ * @param in The Parcel containing the previously written ComponentName,
+ * positioned at the location in the buffer where it was written.
+ */
+ public ComponentName(Parcel in) {
+ mPackage = in.readString();
+ if (mPackage == null) throw new NullPointerException(
+ "package name is null");
+ mClass = in.readString();
+ if (mClass == null) throw new NullPointerException(
+ "class name is null");
+ }
+
+ private ComponentName(String pkg, Parcel in) {
+ mPackage = pkg;
+ mClass = in.readString();
+ }
+
+ /**
+ * Interface for classes associated with a component name.
+ * @hide
+ */
+ @FunctionalInterface
+ public interface WithComponentName {
+ /** Return the associated component name. */
+ ComponentName getComponentName();
+ }
+}
diff --git a/android/content/ContentCaptureOptions.java b/android/content/ContentCaptureOptions.java
new file mode 100644
index 0000000..7707289
--- /dev/null
+++ b/android/content/ContentCaptureOptions.java
@@ -0,0 +1,242 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+/**
+ * Content capture options for a given package.
+ *
+ * <p>This object is created by the Content Capture System Service and passed back to the app when
+ * the application is created.
+ *
+ * @hide
+ */
+@TestApi
+public final class ContentCaptureOptions implements Parcelable {
+
+ private static final String TAG = ContentCaptureOptions.class.getSimpleName();
+
+ /**
+ * Logging level for {@code logcat} statements.
+ */
+ public final int loggingLevel;
+
+ /**
+ * Maximum number of events that are buffered before sent to the app.
+ */
+ public final int maxBufferSize;
+
+ /**
+ * Frequency the buffer is flushed if idle.
+ */
+ public final int idleFlushingFrequencyMs;
+
+ /**
+ * Frequency the buffer is flushed if last event is a text change.
+ */
+ public final int textChangeFlushingFrequencyMs;
+
+ /**
+ * Size of events that are logging on {@code dump}.
+ */
+ public final int logHistorySize;
+
+ /**
+ * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
+ * for all acitivites in the package).
+ */
+ @Nullable
+ @SuppressLint("NullableCollection")
+ public final ArraySet<ComponentName> whitelistedComponents;
+
+ /**
+ * Used to enable just a small set of APIs so it can used by activities belonging to the
+ * content capture service APK.
+ */
+ public final boolean lite;
+
+ /**
+ * Constructor for "lite" objects that are just used to enable a {@link ContentCaptureManager}
+ * for contexts belonging to the content capture service app.
+ */
+ public ContentCaptureOptions(int loggingLevel) {
+ this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
+ /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0, /* whitelistedComponents= */ null);
+ }
+
+ /**
+ * Default constructor.
+ */
+ public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
+ int textChangeFlushingFrequencyMs, int logHistorySize,
+ @SuppressLint("NullableCollection")
+ @Nullable ArraySet<ComponentName> whitelistedComponents) {
+ this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
+ textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
+ this(ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
+ ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
+ ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
+ ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
+ ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, whitelistedComponents);
+ }
+
+ private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
+ int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+ @Nullable ArraySet<ComponentName> whitelistedComponents) {
+ this.lite = lite;
+ this.loggingLevel = loggingLevel;
+ this.maxBufferSize = maxBufferSize;
+ this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
+ this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
+ this.logHistorySize = logHistorySize;
+ this.whitelistedComponents = whitelistedComponents;
+ }
+
+ public static ContentCaptureOptions forWhitelistingItself() {
+ final ActivityThread at = ActivityThread.currentActivityThread();
+ if (at == null) {
+ throw new IllegalStateException("No ActivityThread");
+ }
+
+ final String packageName = at.getApplication().getPackageName();
+
+ if (!"android.contentcaptureservice.cts".equals(packageName)
+ && !"android.translation.cts".equals(packageName)) {
+ Log.e(TAG, "forWhitelistingItself(): called by " + packageName);
+ throw new SecurityException("Thou shall not pass!");
+ }
+
+ final ContentCaptureOptions options =
+ new ContentCaptureOptions(/* whitelistedComponents= */ null);
+ // Always log, as it's used by test only
+ Log.i(TAG, "forWhitelistingItself(" + packageName + "): " + options);
+
+ return options;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public boolean isWhitelisted(@NonNull Context context) {
+ if (whitelistedComponents == null) return true; // whole package is allowlisted
+ final ContentCaptureClient client = context.getContentCaptureClient();
+ if (client == null) {
+ // Shouldn't happen, but it doesn't hurt to check...
+ Log.w(TAG, "isWhitelisted(): no ContentCaptureClient on " + context);
+ return false;
+ }
+ return whitelistedComponents.contains(client.contentCaptureClientGetComponentName());
+ }
+
+ @Override
+ public String toString() {
+ if (lite) {
+ return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
+ }
+ final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
+ string.append("loggingLevel=").append(loggingLevel)
+ .append(", maxBufferSize=").append(maxBufferSize)
+ .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
+ .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
+ .append(", logHistorySize=").append(logHistorySize);
+ if (whitelistedComponents != null) {
+ string.append(", whitelisted=").append(whitelistedComponents);
+ }
+ return string.append(']').toString();
+ }
+
+ /** @hide */
+ public void dumpShort(@NonNull PrintWriter pw) {
+ pw.print("logLvl="); pw.print(loggingLevel);
+ if (lite) {
+ pw.print(", lite");
+ return;
+ }
+ pw.print(", bufferSize="); pw.print(maxBufferSize);
+ pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
+ pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
+ pw.print(", logSize="); pw.print(logHistorySize);
+ if (whitelistedComponents != null) {
+ pw.print(", whitelisted="); pw.print(whitelistedComponents);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeBoolean(lite);
+ parcel.writeInt(loggingLevel);
+ if (lite) return;
+
+ parcel.writeInt(maxBufferSize);
+ parcel.writeInt(idleFlushingFrequencyMs);
+ parcel.writeInt(textChangeFlushingFrequencyMs);
+ parcel.writeInt(logHistorySize);
+ parcel.writeArraySet(whitelistedComponents);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureOptions> CREATOR =
+ new Parcelable.Creator<ContentCaptureOptions>() {
+
+ @Override
+ public ContentCaptureOptions createFromParcel(Parcel parcel) {
+ final boolean lite = parcel.readBoolean();
+ final int loggingLevel = parcel.readInt();
+ if (lite) {
+ return new ContentCaptureOptions(loggingLevel);
+ }
+ final int maxBufferSize = parcel.readInt();
+ final int idleFlushingFrequencyMs = parcel.readInt();
+ final int textChangeFlushingFrequencyMs = parcel.readInt();
+ final int logHistorySize = parcel.readInt();
+ @SuppressWarnings("unchecked")
+ final ArraySet<ComponentName> whitelistedComponents =
+ (ArraySet<ComponentName>) parcel.readArraySet(null);
+ return new ContentCaptureOptions(loggingLevel, maxBufferSize,
+ idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
+ whitelistedComponents);
+ }
+
+ @Override
+ public ContentCaptureOptions[] newArray(int size) {
+ return new ContentCaptureOptions[size];
+ }
+ };
+}
diff --git a/android/content/ContentInsertHandler.java b/android/content/ContentInsertHandler.java
new file mode 100644
index 0000000..fbf726e
--- /dev/null
+++ b/android/content/ContentInsertHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 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 org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface to insert data to ContentResolver
+ * @hide
+ */
+public interface ContentInsertHandler extends ContentHandler {
+ /**
+ * insert data from InputStream to ContentResolver
+ * @param contentResolver
+ * @param in InputStream
+ * @throws IOException
+ * @throws SAXException
+ */
+ public void insert(ContentResolver contentResolver, InputStream in)
+ throws IOException, SAXException;
+
+ /**
+ * insert data from String to ContentResolver
+ * @param contentResolver
+ * @param in input string
+ * @throws SAXException
+ */
+ public void insert(ContentResolver contentResolver, String in)
+ throws SAXException;
+
+}
diff --git a/android/content/ContentInterface.java b/android/content/ContentInterface.java
new file mode 100644
index 0000000..5988dd3
--- /dev/null
+++ b/android/content/ContentInterface.java
@@ -0,0 +1,89 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Interface representing calls that can be made to {@link ContentProvider}
+ * instances.
+ * <p>
+ * These methods have been extracted into a general interface so that APIs can
+ * be flexible in accepting either a {@link ContentProvider}, a
+ * {@link ContentResolver}, or a {@link ContentProviderClient}.
+ *
+ * @hide
+ */
+public interface ContentInterface {
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
+ throws RemoteException;
+
+ public @Nullable String getType(@NonNull Uri uri) throws RemoteException;
+
+ public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter)
+ throws RemoteException;
+
+ public @Nullable Uri canonicalize(@NonNull Uri uri) throws RemoteException;
+
+ public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException;
+
+ public boolean refresh(@NonNull Uri uri, @Nullable Bundle extras,
+ @Nullable CancellationSignal cancellationSignal) throws RemoteException;
+
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
+ throws RemoteException;
+
+ public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues,
+ @Nullable Bundle extras) throws RemoteException;
+
+ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] initialValues)
+ throws RemoteException;
+
+ public int delete(@NonNull Uri uri, @Nullable Bundle extras) throws RemoteException;
+
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable Bundle extras)
+ throws RemoteException;
+
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;
+
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;
+
+ public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+ @NonNull String mimeTypeFilter, @Nullable Bundle opts,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException;
+
+ public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException;
+
+ public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
+ @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
+}
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
new file mode 100644
index 0000000..c714f50
--- /dev/null
+++ b/android/content/ContentProvider.java
@@ -0,0 +1,2689 @@
+/*
+ * Copyright (C) 2006 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 static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Trace.TRACE_TAG_DATABASE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.permission.PermissionCheckerManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Content providers are one of the primary building blocks of Android applications, providing
+ * content to applications. They encapsulate data and provide it to applications through the single
+ * {@link ContentResolver} interface. A content provider is only required if you need to share
+ * data between multiple applications. For example, the contacts data is used by multiple
+ * applications and must be stored in a content provider. If you don't need to share data amongst
+ * multiple applications you can use a database directly via
+ * {@link android.database.sqlite.SQLiteDatabase}.
+ *
+ * <p>When a request is made via
+ * a {@link ContentResolver} the system inspects the authority of the given URI and passes the
+ * request to the content provider registered with the authority. The content provider can interpret
+ * the rest of the URI however it wants. The {@link UriMatcher} class is helpful for parsing
+ * URIs.</p>
+ *
+ * <p>The primary methods that need to be implemented are:
+ * <ul>
+ * <li>{@link #onCreate} which is called to initialize the provider</li>
+ * <li>{@link #query} which returns data to the caller</li>
+ * <li>{@link #insert} which inserts new data into the content provider</li>
+ * <li>{@link #update} which updates existing data in the content provider</li>
+ * <li>{@link #delete} which deletes data from the content provider</li>
+ * <li>{@link #getType} which returns the MIME type of data in the content provider</li>
+ * </ul></p>
+ *
+ * <p class="caution">Data access methods (such as {@link #insert} and
+ * {@link #update}) may be called from many threads at once, and must be thread-safe.
+ * Other methods (such as {@link #onCreate}) are only called from the application
+ * main thread, and must avoid performing lengthy operations. See the method
+ * descriptions for their expected thread behavior.</p>
+ *
+ * <p>Requests to {@link ContentResolver} are automatically forwarded to the appropriate
+ * ContentProvider instance, so subclasses don't have to worry about the details of
+ * cross-process calls.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using content providers, read the
+ * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>
+ * developer guide.</p>
+ * </div>
+ */
+public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {
+
+ private static final String TAG = "ContentProvider";
+
+ /*
+ * Note: if you add methods to ContentProvider, you must add similar methods to
+ * MockContentProvider.
+ */
+
+ @UnsupportedAppUsage
+ private Context mContext = null;
+ private int mMyUid;
+
+ // Since most Providers have only one authority, we keep both a String and a String[] to improve
+ // performance.
+ @UnsupportedAppUsage
+ private String mAuthority;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private String[] mAuthorities;
+ @UnsupportedAppUsage
+ private String mReadPermission;
+ @UnsupportedAppUsage
+ private String mWritePermission;
+ @UnsupportedAppUsage
+ private PathPermission[] mPathPermissions;
+ private boolean mExported;
+ private boolean mNoPerms;
+ private boolean mSingleUser;
+
+ private ThreadLocal<AttributionSource> mCallingAttributionSource;
+
+ private Transport mTransport = new Transport();
+
+ /**
+ * Construct a ContentProvider instance. Content providers must be
+ * <a href="{@docRoot}guide/topics/manifest/provider-element.html">declared
+ * in the manifest</a>, accessed with {@link ContentResolver}, and created
+ * automatically by the system, so applications usually do not create
+ * ContentProvider instances directly.
+ *
+ * <p>At construction time, the object is uninitialized, and most fields and
+ * methods are unavailable. Subclasses should initialize themselves in
+ * {@link #onCreate}, not the constructor.
+ *
+ * <p>Content providers are created on the application main thread at
+ * application launch time. The constructor must not perform lengthy
+ * operations, or application startup will be delayed.
+ */
+ public ContentProvider() {
+ }
+
+ /**
+ * Constructor just for mocking.
+ *
+ * @param context A Context object which should be some mock instance (like the
+ * instance of {@link android.test.mock.MockContext}).
+ * @param readPermission The read permision you want this instance should have in the
+ * test, which is available via {@link #getReadPermission()}.
+ * @param writePermission The write permission you want this instance should have
+ * in the test, which is available via {@link #getWritePermission()}.
+ * @param pathPermissions The PathPermissions you want this instance should have
+ * in the test, which is available via {@link #getPathPermissions()}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public ContentProvider(
+ Context context,
+ String readPermission,
+ String writePermission,
+ PathPermission[] pathPermissions) {
+ mContext = context;
+ mReadPermission = readPermission;
+ mWritePermission = writePermission;
+ mPathPermissions = pathPermissions;
+ }
+
+ /**
+ * Given an IContentProvider, try to coerce it back to the real
+ * ContentProvider object if it is running in the local process. This can
+ * be used if you know you are running in the same process as a provider,
+ * and want to get direct access to its implementation details. Most
+ * clients should not nor have a reason to use it.
+ *
+ * @param abstractInterface The ContentProvider interface that is to be
+ * coerced.
+ * @return If the IContentProvider is non-{@code null} and local, returns its actual
+ * ContentProvider instance. Otherwise returns {@code null}.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static ContentProvider coerceToLocalContentProvider(
+ IContentProvider abstractInterface) {
+ if (abstractInterface instanceof Transport) {
+ return ((Transport)abstractInterface).getContentProvider();
+ }
+ return null;
+ }
+
+ /**
+ * Binder object that deals with remoting.
+ *
+ * @hide
+ */
+ class Transport extends ContentProviderNative {
+ volatile AppOpsManager mAppOpsManager = null;
+ volatile int mReadOp = AppOpsManager.OP_NONE;
+ volatile int mWriteOp = AppOpsManager.OP_NONE;
+ volatile ContentInterface mInterface = ContentProvider.this;
+
+ ContentProvider getContentProvider() {
+ return ContentProvider.this;
+ }
+
+ @Override
+ public String getProviderName() {
+ return getContentProvider().getClass().getName();
+ }
+
+ @Override
+ public Cursor query(@NonNull AttributionSource attributionSource, Uri uri,
+ @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable ICancellationSignal cancellationSignal) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ // The caller has no access to the data, so return an empty cursor with
+ // the columns in the requested order. The caller may ask for an invalid
+ // column and we would not catch that but this is not a problem in practice.
+ // We do not call ContentProvider#query with a modified where clause since
+ // the implementation is not guaranteed to be backed by a SQL database, hence
+ // it may not handle properly the tautology where clause we would have created.
+ if (projection != null) {
+ return new MatrixCursor(projection, 0);
+ }
+
+ // Null projection means all columns but we have no idea which they are.
+ // However, the caller may be expecting to access them my index. Hence,
+ // we have to execute the query as if allowed to get a cursor with the
+ // columns. We then use the column names to return an empty cursor.
+ Cursor cursor;
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ cursor = mInterface.query(
+ uri, projection, queryArgs,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ }
+ if (cursor == null) {
+ return null;
+ }
+
+ // Return an empty cursor for all columns.
+ return new MatrixCursor(cursor.getColumnNames(), 0);
+ }
+ traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.query(
+ uri, projection, queryArgs,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // getCallingPackage() isn't available in getType(), as the javadoc states.
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
+ try {
+ return mInterface.getType(uri);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public void getTypeAsync(Uri uri, RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public Uri insert(@NonNull AttributionSource attributionSource, Uri uri,
+ ContentValues initialValues, Bundle extras) {
+ uri = validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return rejectInsert(uri, initialValues);
+ } finally {
+ setCallingAttributionSource(original);
+ }
+ }
+ traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int bulkInsert(@NonNull AttributionSource attributionSource, Uri uri,
+ ContentValues[] initialValues) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ return 0;
+ }
+ traceBegin(TRACE_TAG_DATABASE, "bulkInsert: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.bulkInsert(uri, initialValues);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
+ String authority, ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ validateIncomingAuthority(authority);
+ int numOperations = operations.size();
+ final int[] userIds = new int[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ ContentProviderOperation operation = operations.get(i);
+ Uri uri = operation.getUri();
+ userIds[i] = getUserIdFromUri(uri);
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ // Rebuild operation if we changed the Uri above
+ if (!Objects.equals(operation.getUri(), uri)) {
+ operation = new ContentProviderOperation(operation, uri);
+ operations.set(i, operation);
+ }
+ final AttributionSource accessAttributionSource =
+ attributionSource;
+ if (operation.isReadOperation()) {
+ if (enforceReadPermission(accessAttributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
+ }
+ if (operation.isWriteOperation()) {
+ if (enforceWritePermission(accessAttributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
+ }
+ }
+ traceBegin(TRACE_TAG_DATABASE, "applyBatch: ", authority);
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ ContentProviderResult[] results = mInterface.applyBatch(authority,
+ operations);
+ if (results != null) {
+ for (int i = 0; i < results.length ; i++) {
+ if (userIds[i] != UserHandle.USER_CURRENT) {
+ // Adding the userId to the uri.
+ results[i] = new ContentProviderResult(results[i], userIds[i]);
+ }
+ }
+ }
+ return results;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int delete(@NonNull AttributionSource attributionSource, Uri uri,
+ Bundle extras) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ return 0;
+ }
+ traceBegin(TRACE_TAG_DATABASE, "delete: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.delete(uri, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int update(@NonNull AttributionSource attributionSource, Uri uri,
+ ContentValues values, Bundle extras) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ return 0;
+ }
+ traceBegin(TRACE_TAG_DATABASE, "update: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.update(uri, values, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
+ Uri uri, String mode, ICancellationSignal cancellationSignal)
+ throws FileNotFoundException {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(attributionSource, uri, mode);
+ traceBegin(TRACE_TAG_DATABASE, "openFile: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.openFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
+ Uri uri, String mode, ICancellationSignal cancellationSignal)
+ throws FileNotFoundException {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(attributionSource, uri, mode);
+ traceBegin(TRACE_TAG_DATABASE, "openAssetFile: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.openAssetFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public Bundle call(@NonNull AttributionSource attributionSource, String authority,
+ String method, @Nullable String arg, @Nullable Bundle extras) {
+ validateIncomingAuthority(authority);
+ Bundle.setDefusable(extras, true);
+ traceBegin(TRACE_TAG_DATABASE, "call: ", authority);
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.call(authority, method, arg, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ // getCallingPackage() isn't available in getType(), as the javadoc states.
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ traceBegin(TRACE_TAG_DATABASE, "getStreamTypes: ", uri.getAuthority());
+ try {
+ return mInterface.getStreamTypes(uri, mimeTypeFilter);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(
+ @NonNull AttributionSource attributionSource, Uri uri, String mimeType,
+ Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
+ Bundle.setDefusable(opts, true);
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(attributionSource, uri, "r");
+ traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.openTypedAssetFile(
+ uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public ICancellationSignal createCancellationSignal() {
+ return CancellationSignal.createTransport();
+ }
+
+ @Override
+ public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri) {
+ uri = validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ return null;
+ }
+ traceBegin(TRACE_TAG_DATABASE, "canonicalize: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return maybeAddUserId(mInterface.canonicalize(uri), userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
+ RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
+ canonicalize(attributionSource, uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri) {
+ uri = validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ return null;
+ }
+ traceBegin(TRACE_TAG_DATABASE, "uncanonicalize: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return maybeAddUserId(mInterface.uncanonicalize(uri), userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
+ RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
+ uncanonicalize(attributionSource, uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri uri,
+ Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException {
+ uri = validateIncomingUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ return false;
+ }
+ traceBegin(TRACE_TAG_DATABASE, "refresh: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.refresh(uri, extras,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
+ int uid, int modeFlags) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ traceBegin(TRACE_TAG_DATABASE, "checkUriPermission: ", uri.getAuthority());
+ final AttributionSource original = setCallingAttributionSource(
+ attributionSource);
+ try {
+ return mInterface.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingAttributionSource(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ private void enforceFilePermission(@NonNull AttributionSource attributionSource,
+ Uri uri, String mode)
+ throws FileNotFoundException, SecurityException {
+ if (mode != null && mode.indexOf('w') != -1) {
+ if (enforceWritePermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ } else {
+ if (enforceReadPermission(attributionSource, uri)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ }
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri)
+ throws SecurityException {
+ final int result = enforceReadPermissionInner(uri, attributionSource);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ return result;
+ }
+ // Only check the read op if it differs from the one for the permission
+ // we already checked above to avoid double attribution for every access.
+ if (mTransport.mReadOp != AppOpsManager.OP_NONE
+ && mTransport.mReadOp != AppOpsManager.permissionToOpCode(mReadPermission)) {
+ return PermissionChecker.checkOpForDataDelivery(getContext(),
+ AppOpsManager.opToPublicName(mTransport.mReadOp),
+ attributionSource, /*message*/ null);
+ }
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri)
+ throws SecurityException {
+ final int result = enforceWritePermissionInner(uri, attributionSource);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ return result;
+ }
+ // Only check the write op if it differs from the one for the permission
+ // we already checked above to avoid double attribution for every access.
+ if (mTransport.mWriteOp != AppOpsManager.OP_NONE
+ && mTransport.mWriteOp != AppOpsManager.permissionToOpCode(mWritePermission)) {
+ return PermissionChecker.checkOpForDataDelivery(getContext(),
+ AppOpsManager.opToPublicName(mTransport.mWriteOp),
+ attributionSource, /*message*/ null);
+ }
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+ }
+
+ boolean checkUser(int pid, int uid, Context context) {
+ if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) {
+ return true;
+ }
+ return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED
+ || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Verify that calling app holds both the given permission and any app-op
+ * associated with that permission.
+ */
+ @PermissionCheckerManager.PermissionResult
+ private int checkPermission(String permission,
+ @NonNull AttributionSource attributionSource) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+ return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(getContext(),
+ permission, -1, new AttributionSource(getContext().getAttributionSource(),
+ attributionSource), /*message*/ null);
+ }
+
+ /** {@hide} */
+ @PermissionCheckerManager.PermissionResult
+ protected int enforceReadPermissionInner(Uri uri,
+ @NonNull AttributionSource attributionSource) throws SecurityException {
+ final Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ String missingPerm = null;
+ int strongestResult = PermissionChecker.PERMISSION_GRANTED;
+
+ if (UserHandle.isSameApp(uid, mMyUid)) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ if (mExported && checkUser(pid, uid, context)) {
+ final String componentPerm = getReadPermission();
+ if (componentPerm != null) {
+ final int result = checkPermission(componentPerm, attributionSource);
+ if (result == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ } else {
+ missingPerm = componentPerm;
+ strongestResult = Math.max(strongestResult, result);
+ }
+ }
+
+ // track if unprotected read is allowed; any denied
+ // <path-permission> below removes this ability
+ boolean allowDefaultRead = (componentPerm == null);
+
+ final PathPermission[] pps = getPathPermissions();
+ if (pps != null) {
+ final String path = uri.getPath();
+ for (PathPermission pp : pps) {
+ final String pathPerm = pp.getReadPermission();
+ if (pathPerm != null && pp.match(path)) {
+ final int result = checkPermission(pathPerm, attributionSource);
+ if (result == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ } else {
+ // any denied <path-permission> means we lose
+ // default <provider> access.
+ allowDefaultRead = false;
+ missingPerm = pathPerm;
+ strongestResult = Math.max(strongestResult, result);
+ }
+ }
+ }
+ }
+
+ // if we passed <path-permission> checks above, and no default
+ // <provider> permission, then allow access.
+ if (allowDefaultRead) return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ // last chance, check against any uri grants
+ final int callingUserId = UserHandle.getUserId(uid);
+ final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
+ ? maybeAddUserId(uri, callingUserId) : uri;
+ if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ // If the worst denial we found above was ignored, then pass that
+ // ignored through; otherwise we assume it should be a real error below.
+ if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) {
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
+ }
+
+ final String suffix;
+ if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(mReadPermission)) {
+ suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
+ } else if (mExported) {
+ suffix = " requires " + missingPerm + ", or grantUriPermission()";
+ } else {
+ suffix = " requires the provider be exported, or grantUriPermission()";
+ }
+ throw new SecurityException("Permission Denial: reading "
+ + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid + suffix);
+ }
+
+ /** {@hide} */
+ @PermissionCheckerManager.PermissionResult
+ protected int enforceWritePermissionInner(Uri uri,
+ @NonNull AttributionSource attributionSource) throws SecurityException {
+ final Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ String missingPerm = null;
+ int strongestResult = PermissionChecker.PERMISSION_GRANTED;
+
+ if (UserHandle.isSameApp(uid, mMyUid)) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ if (mExported && checkUser(pid, uid, context)) {
+ final String componentPerm = getWritePermission();
+ if (componentPerm != null) {
+ final int mode = checkPermission(componentPerm, attributionSource);
+ if (mode == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ } else {
+ missingPerm = componentPerm;
+ strongestResult = Math.max(strongestResult, mode);
+ }
+ }
+
+ // track if unprotected write is allowed; any denied
+ // <path-permission> below removes this ability
+ boolean allowDefaultWrite = (componentPerm == null);
+
+ final PathPermission[] pps = getPathPermissions();
+ if (pps != null) {
+ final String path = uri.getPath();
+ for (PathPermission pp : pps) {
+ final String pathPerm = pp.getWritePermission();
+ if (pathPerm != null && pp.match(path)) {
+ final int mode = checkPermission(pathPerm, attributionSource);
+ if (mode == PermissionChecker.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ } else {
+ // any denied <path-permission> means we lose
+ // default <provider> access.
+ allowDefaultWrite = false;
+ missingPerm = pathPerm;
+ strongestResult = Math.max(strongestResult, mode);
+ }
+ }
+ }
+ }
+
+ // if we passed <path-permission> checks above, and no default
+ // <provider> permission, then allow access.
+ if (allowDefaultWrite) return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ // If the worst denial we found above was ignored, then pass that
+ // ignored through; otherwise we assume it should be a real error below.
+ if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) {
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
+ }
+
+ final String failReason = mExported
+ ? " requires " + missingPerm + ", or grantUriPermission()"
+ : " requires the provider be exported, or grantUriPermission()";
+ throw new SecurityException("Permission Denial: writing "
+ + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid + failReason);
+ }
+
+ /**
+ * Retrieves the Context this provider is running in. Only available once
+ * {@link #onCreate} has been called -- this will return {@code null} in the
+ * constructor.
+ */
+ public final @Nullable Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Retrieves a Non-Nullable Context this provider is running in, this is intended to be called
+ * after {@link #onCreate}. When called before context was created, an IllegalStateException
+ * will be thrown.
+ * <p>
+ * Note A provider must be declared in the manifest and created automatically by the system,
+ * and context is only available after {@link #onCreate} is called.
+ */
+ @NonNull
+ public final Context requireContext() {
+ final Context ctx = getContext();
+ if (ctx == null) {
+ throw new IllegalStateException("Cannot find context from the provider.");
+ }
+ return ctx;
+ }
+
+ /**
+ * Set the calling package/feature, returning the current value (or {@code null})
+ * which can be used later to restore the previous state.
+ */
+ private @Nullable AttributionSource setCallingAttributionSource(
+ @Nullable AttributionSource attributionSource) {
+ final AttributionSource original = mCallingAttributionSource.get();
+ mCallingAttributionSource.set(attributionSource);
+ onCallingPackageChanged();
+ return original;
+ }
+
+ /**
+ * Return the package name of the caller that initiated the request being
+ * processed on the current thread. The returned package will have been
+ * verified to belong to the calling UID. Returns {@code null} if not
+ * currently processing a request.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see Binder#getCallingUid()
+ * @see Context#grantUriPermission(String, Uri, int)
+ * @throws SecurityException if the calling package doesn't belong to the
+ * calling UID.
+ */
+ public final @Nullable String getCallingPackage() {
+ final AttributionSource callingAttributionSource = getCallingAttributionSource();
+ return (callingAttributionSource != null)
+ ? callingAttributionSource.getPackageName() : null;
+ }
+
+ /**
+ * Gets the attribution source of the calling app. If you want to attribute
+ * the data access to the calling app you can create an attribution context
+ * via {@link android.content.Context#createContext(ContextParams)} and passing
+ * this identity to {@link ContextParams.Builder#setNextAttributionSource(
+ * AttributionSource)}.
+ *
+ * @return The identity of the caller for permission purposes.
+ *
+ * @see ContextParams.Builder#setNextAttributionSource(AttributionSource)
+ * @see AttributionSource
+ */
+ public final @Nullable AttributionSource getCallingAttributionSource() {
+ final AttributionSource attributionSource = mCallingAttributionSource.get();
+ if (attributionSource != null) {
+ mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(),
+ attributionSource.getPackageName());
+ }
+ return attributionSource;
+ }
+
+ /**
+ * Return the attribution tag of the caller that initiated the request being
+ * processed on the current thread. Returns {@code null} if not currently processing
+ * a request of the request is for the default attribution.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see #getCallingPackage
+ */
+ public final @Nullable String getCallingAttributionTag() {
+ final AttributionSource attributionSource = mCallingAttributionSource.get();
+ if (attributionSource != null) {
+ return attributionSource.getAttributionTag();
+ }
+ return null;
+ }
+
+ /**
+ * @removed
+ */
+ @Deprecated
+ public final @Nullable String getCallingFeatureId() {
+ return getCallingAttributionTag();
+ }
+
+ /**
+ * Return the package name of the caller that initiated the request being
+ * processed on the current thread. The returned package will have
+ * <em>not</em> been verified to belong to the calling UID. Returns
+ * {@code null} if not currently processing a request.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see Binder#getCallingUid()
+ * @see Context#grantUriPermission(String, Uri, int)
+ */
+ public final @Nullable String getCallingPackageUnchecked() {
+ final AttributionSource attributionSource = mCallingAttributionSource.get();
+ if (attributionSource != null) {
+ return attributionSource.getPackageName();
+ }
+ return null;
+ }
+
+ /**
+ * Called whenever the value of {@link #getCallingPackage()} changes, giving
+ * the provider an opportunity to invalidate any security related caching it
+ * may be performing.
+ * <p>
+ * This typically happens when a {@link ContentProvider} makes a nested call
+ * back into itself when already processing a call from a remote process.
+ */
+ public void onCallingPackageChanged() {
+ }
+
+ /**
+ * Opaque token representing the identity of an incoming IPC.
+ */
+ public final class CallingIdentity {
+ /** {@hide} */
+ public final long binderToken;
+ /** {@hide} */
+ public final @Nullable AttributionSource callingAttributionSource;
+
+ /** {@hide} */
+ public CallingIdentity(long binderToken, @Nullable AttributionSource attributionSource) {
+ this.binderToken = binderToken;
+ this.callingAttributionSource = attributionSource;
+ }
+ }
+
+ /**
+ * Reset the identity of the incoming IPC on the current thread.
+ * <p>
+ * Internally this calls {@link Binder#clearCallingIdentity()} and also
+ * clears any value stored in {@link #getCallingPackage()}.
+ *
+ * @return Returns an opaque token that can be used to restore the original
+ * calling identity by passing it to
+ * {@link #restoreCallingIdentity}.
+ */
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
+ public final @NonNull CallingIdentity clearCallingIdentity() {
+ return new CallingIdentity(Binder.clearCallingIdentity(),
+ setCallingAttributionSource(null));
+ }
+
+ /**
+ * Restore the identity of the incoming IPC on the current thread back to a
+ * previously identity that was returned by {@link #clearCallingIdentity}.
+ * <p>
+ * Internally this calls {@link Binder#restoreCallingIdentity(long)} and
+ * also restores any value stored in {@link #getCallingPackage()}.
+ */
+ public final void restoreCallingIdentity(@NonNull CallingIdentity identity) {
+ Binder.restoreCallingIdentity(identity.binderToken);
+ mCallingAttributionSource.set(identity.callingAttributionSource);
+ }
+
+ /**
+ * Change the authorities of the ContentProvider.
+ * This is normally set for you from its manifest information when the provider is first
+ * created.
+ * @hide
+ * @param authorities the semi-colon separated authorities of the ContentProvider.
+ */
+ protected final void setAuthorities(String authorities) {
+ if (authorities != null) {
+ if (authorities.indexOf(';') == -1) {
+ mAuthority = authorities;
+ mAuthorities = null;
+ } else {
+ mAuthority = null;
+ mAuthorities = authorities.split(";");
+ }
+ }
+ }
+
+ /** @hide */
+ protected final boolean matchesOurAuthorities(String authority) {
+ if (mAuthority != null) {
+ return mAuthority.equals(authority);
+ }
+ if (mAuthorities != null) {
+ int length = mAuthorities.length;
+ for (int i = 0; i < length; i++) {
+ if (mAuthorities[i].equals(authority)) return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Change the permission required to read data from the content
+ * provider. This is normally set for you from its manifest information
+ * when the provider is first created.
+ *
+ * @param permission Name of the permission required for read-only access.
+ */
+ protected final void setReadPermission(@Nullable String permission) {
+ mReadPermission = permission;
+ }
+
+ /**
+ * Return the name of the permission required for read-only access to
+ * this content provider. This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ */
+ public final @Nullable String getReadPermission() {
+ return mReadPermission;
+ }
+
+ /**
+ * Change the permission required to read and write data in the content
+ * provider. This is normally set for you from its manifest information
+ * when the provider is first created.
+ *
+ * @param permission Name of the permission required for read/write access.
+ */
+ protected final void setWritePermission(@Nullable String permission) {
+ mWritePermission = permission;
+ }
+
+ /**
+ * Return the name of the permission required for read/write access to
+ * this content provider. This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ */
+ public final @Nullable String getWritePermission() {
+ return mWritePermission;
+ }
+
+ /**
+ * Change the path-based permission required to read and/or write data in
+ * the content provider. This is normally set for you from its manifest
+ * information when the provider is first created.
+ *
+ * @param permissions Array of path permission descriptions.
+ */
+ protected final void setPathPermissions(@Nullable PathPermission[] permissions) {
+ mPathPermissions = permissions;
+ }
+
+ /**
+ * Return the path-based permissions required for read and/or write access to
+ * this content provider. This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ */
+ public final @Nullable PathPermission[] getPathPermissions() {
+ return mPathPermissions;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public final void setAppOps(int readOp, int writeOp) {
+ if (!mNoPerms) {
+ mTransport.mReadOp = readOp;
+ mTransport.mWriteOp = writeOp;
+ }
+ }
+
+ /** @hide */
+ public AppOpsManager getAppOpsManager() {
+ return mTransport.mAppOpsManager;
+ }
+
+ /** @hide */
+ public final void setTransportLoggingEnabled(boolean enabled) {
+ if (mTransport == null) {
+ return;
+ }
+ if (enabled) {
+ mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this);
+ } else {
+ mTransport.mInterface = this;
+ }
+ }
+
+ /**
+ * Implement this to initialize your content provider on startup.
+ * This method is called for all registered content providers on the
+ * application main thread at application launch time. It must not perform
+ * lengthy operations, or application startup will be delayed.
+ *
+ * <p>You should defer nontrivial initialization (such as opening,
+ * upgrading, and scanning databases) until the content provider is used
+ * (via {@link #query}, {@link #insert}, etc). Deferred initialization
+ * keeps application startup fast, avoids unnecessary work if the provider
+ * turns out not to be needed, and stops database errors (such as a full
+ * disk) from halting application launch.
+ *
+ * <p>If you use SQLite, {@link android.database.sqlite.SQLiteOpenHelper}
+ * is a helpful utility class that makes it easy to manage databases,
+ * and will automatically defer opening until first use. If you do use
+ * SQLiteOpenHelper, make sure to avoid calling
+ * {@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} or
+ * {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase}
+ * from this method. (Instead, override
+ * {@link android.database.sqlite.SQLiteOpenHelper#onOpen} to initialize the
+ * database when it is first opened.)
+ *
+ * @return true if the provider was successfully loaded, false otherwise
+ */
+ public abstract boolean onCreate();
+
+ /**
+ * {@inheritDoc}
+ * This method is always called on the application main thread, and must
+ * not perform lengthy operations.
+ *
+ * <p>The default content provider implementation does nothing.
+ * Override this method to take appropriate action.
+ * (Content providers do not usually care about things like screen
+ * orientation, but may want to know about locale changes.)
+ */
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ /**
+ * {@inheritDoc}
+ * This method is always called on the application main thread, and must
+ * not perform lengthy operations.
+ *
+ * <p>The default content provider implementation does nothing.
+ * Subclasses may override this method to take appropriate action.
+ */
+ @Override
+ public void onLowMemory() {
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ }
+
+ /**
+ * Implement this to handle query requests from clients.
+ *
+ * <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher should override
+ * {@link #query(Uri, String[], Bundle, CancellationSignal)} and provide a stub
+ * implementation of this method.
+ *
+ * <p>This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ * <p>
+ * Example client call:<p>
+ * <pre>// Request a specific record.
+ * Cursor managedCursor = managedQuery(
+ ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
+ projection, // Which columns to return.
+ null, // WHERE clause.
+ null, // WHERE clause value substitution
+ People.NAME + " ASC"); // Sort order.</pre>
+ * Example implementation:<p>
+ * <pre>// SQLiteQueryBuilder is a helper class that creates the
+ // proper SQL syntax for us.
+ SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
+
+ // Set the table we're querying.
+ qBuilder.setTables(DATABASE_TABLE_NAME);
+
+ // If the query ends in a specific record number, we're
+ // being asked for a specific record, so set the
+ // WHERE clause in our query.
+ if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
+ qBuilder.appendWhere("_id=" + uri.getPathLeafId());
+ }
+
+ // Make the query.
+ Cursor c = qBuilder.query(mDb,
+ projection,
+ selection,
+ selectionArgs,
+ groupBy,
+ having,
+ sortOrder);
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ return c;</pre>
+ *
+ * @param uri The URI to query. This will be the full URI sent by the client;
+ * if the client is requesting a specific record, the URI will end in a record number
+ * that the implementation should parse and add to a WHERE or HAVING clause, specifying
+ * that _id value.
+ * @param projection The list of columns to put into the cursor. If
+ * {@code null} all columns are included.
+ * @param selection A selection criteria to apply when filtering rows.
+ * If {@code null} then all rows are included.
+ * @param selectionArgs You may include ?s in selection, which will be replaced by
+ * the values from selectionArgs, in order that they appear in the selection.
+ * The values will be bound as Strings.
+ * @param sortOrder How the rows in the cursor should be sorted.
+ * If {@code null} then the provider is free to define the sort order.
+ * @return a Cursor or {@code null}.
+ */
+ public abstract @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder);
+
+ /**
+ * Implement this to handle query requests from clients with support for cancellation.
+ *
+ * <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher should override
+ * {@link #query(Uri, String[], Bundle, CancellationSignal)} instead of this method.
+ *
+ * <p>This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ * <p>
+ * Example client call:<p>
+ * <pre>// Request a specific record.
+ * Cursor managedCursor = managedQuery(
+ ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
+ projection, // Which columns to return.
+ null, // WHERE clause.
+ null, // WHERE clause value substitution
+ People.NAME + " ASC"); // Sort order.</pre>
+ * Example implementation:<p>
+ * <pre>// SQLiteQueryBuilder is a helper class that creates the
+ // proper SQL syntax for us.
+ SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
+
+ // Set the table we're querying.
+ qBuilder.setTables(DATABASE_TABLE_NAME);
+
+ // If the query ends in a specific record number, we're
+ // being asked for a specific record, so set the
+ // WHERE clause in our query.
+ if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
+ qBuilder.appendWhere("_id=" + uri.getPathLeafId());
+ }
+
+ // Make the query.
+ Cursor c = qBuilder.query(mDb,
+ projection,
+ selection,
+ selectionArgs,
+ groupBy,
+ having,
+ sortOrder);
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ return c;</pre>
+ * <p>
+ * If you implement this method then you must also implement the version of
+ * {@link #query(Uri, String[], String, String[], String)} that does not take a cancellation
+ * signal to ensure correct operation on older versions of the Android Framework in
+ * which the cancellation signal overload was not available.
+ *
+ * @param uri The URI to query. This will be the full URI sent by the client;
+ * if the client is requesting a specific record, the URI will end in a record number
+ * that the implementation should parse and add to a WHERE or HAVING clause, specifying
+ * that _id value.
+ * @param projection The list of columns to put into the cursor. If
+ * {@code null} all columns are included.
+ * @param selection A selection criteria to apply when filtering rows.
+ * If {@code null} then all rows are included.
+ * @param selectionArgs You may include ?s in selection, which will be replaced by
+ * the values from selectionArgs, in order that they appear in the selection.
+ * The values will be bound as Strings.
+ * @param sortOrder How the rows in the cursor should be sorted.
+ * If {@code null} then the provider is free to define the sort order.
+ * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if none.
+ * If the operation is canceled, then {@link android.os.OperationCanceledException} will be thrown
+ * when the query is executed.
+ * @return a Cursor or {@code null}.
+ */
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
+ return query(uri, projection, selection, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Implement this to handle query requests where the arguments are packed into a {@link Bundle}.
+ * Arguments may include traditional SQL style query arguments. When present these
+ * should be handled according to the contract established in
+ * {@link #query(Uri, String[], String, String[], String, CancellationSignal)}.
+ *
+ * <p>Traditional SQL arguments can be found in the bundle using the following keys:
+ * <li>{@link android.content.ContentResolver#QUERY_ARG_SQL_SELECTION}
+ * <li>{@link android.content.ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}
+ * <li>{@link android.content.ContentResolver#QUERY_ARG_SQL_SORT_ORDER}
+ *
+ * <p>This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * <p>
+ * Example client call:<p>
+ * <pre>// Request 20 records starting at row index 30.
+ Bundle queryArgs = new Bundle();
+ queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 30);
+ queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 20);
+
+ Cursor cursor = getContentResolver().query(
+ contentUri, // Content Uri is specific to individual content providers.
+ projection, // String[] describing which columns to return.
+ queryArgs, // Query arguments.
+ null); // Cancellation signal.</pre>
+ *
+ * Example implementation:<p>
+ * <pre>
+
+ int recordsetSize = 0x1000; // Actual value is implementation specific.
+ queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY; // ensure queryArgs is non-null
+
+ int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
+ int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MIN_VALUE);
+
+ MatrixCursor c = new MatrixCursor(PROJECTION, limit);
+
+ // Calculate the number of items to include in the cursor.
+ int numItems = MathUtils.constrain(recordsetSize - offset, 0, limit);
+
+ // Build the paged result set....
+ for (int i = offset; i < offset + numItems; i++) {
+ // populate row from your data.
+ }
+
+ Bundle extras = new Bundle();
+ c.setExtras(extras);
+
+ // Any QUERY_ARG_* key may be included if honored.
+ // In an actual implementation, include only keys that are both present in queryArgs
+ // and reflected in the Cursor output. For example, if QUERY_ARG_OFFSET were included
+ // in queryArgs, but was ignored because it contained an invalid value (like –273),
+ // then QUERY_ARG_OFFSET should be omitted.
+ extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[] {
+ ContentResolver.QUERY_ARG_OFFSET,
+ ContentResolver.QUERY_ARG_LIMIT
+ });
+
+ extras.putInt(ContentResolver.EXTRA_TOTAL_COUNT, recordsetSize);
+
+ cursor.setNotificationUri(getContext().getContentResolver(), uri);
+
+ return cursor;</pre>
+ * <p>
+ * See {@link #query(Uri, String[], String, String[], String, CancellationSignal)}
+ * for implementation details.
+ *
+ * @param uri The URI to query. This will be the full URI sent by the client.
+ * @param projection The list of columns to put into the cursor.
+ * If {@code null} provide a default set of columns.
+ * @param queryArgs A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or {@code null}.
+ * @return a Cursor or {@code null}.
+ */
+ @Override
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+ queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
+
+ // if client doesn't supply an SQL sort order argument, attempt to build one from
+ // QUERY_ARG_SORT* arguments.
+ String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
+ if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
+ sortClause = ContentResolver.createSqlSortClause(queryArgs);
+ }
+
+ return query(
+ uri,
+ projection,
+ queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
+ queryArgs.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS),
+ sortClause,
+ cancellationSignal);
+ }
+
+ /**
+ * Implement this to handle requests for the MIME type of the data at the
+ * given URI. The returned MIME type should start with
+ * <code>vnd.android.cursor.item</code> for a single record,
+ * or <code>vnd.android.cursor.dir/</code> for multiple items.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * <p>Note that there are no permissions needed for an application to
+ * access this information; if your content provider requires read and/or
+ * write permissions, or is not exported, all applications can still call
+ * this method regardless of their access permissions. This allows them
+ * to retrieve the MIME type for a URI when dispatching intents.
+ *
+ * @param uri the URI to query.
+ * @return a MIME type string, or {@code null} if there is no type.
+ */
+ @Override
+ public abstract @Nullable String getType(@NonNull Uri uri);
+
+ /**
+ * Implement this to support canonicalization of URIs that refer to your
+ * content provider. A canonical URI is one that can be transported across
+ * devices, backup/restore, and other contexts, and still be able to refer
+ * to the same data item. Typically this is implemented by adding query
+ * params to the URI allowing the content provider to verify that an incoming
+ * canonical URI references the same data as it was originally intended for and,
+ * if it doesn't, to find that data (if it exists) in the current environment.
+ *
+ * <p>For example, if the content provider holds people and a normal URI in it
+ * is created with a row index into that people database, the cananical representation
+ * may have an additional query param at the end which specifies the name of the
+ * person it is intended for. Later calls into the provider with that URI will look
+ * up the row of that URI's base index and, if it doesn't match or its entry's
+ * name doesn't match the name in the query param, perform a query on its database
+ * to find the correct row to operate on.</p>
+ *
+ * <p>If you implement support for canonical URIs, <b>all</b> incoming calls with
+ * URIs (including this one) must perform this verification and recovery of any
+ * canonical URIs they receive. In addition, you must also implement
+ * {@link #uncanonicalize} to strip the canonicalization of any of these URIs.</p>
+ *
+ * <p>The default implementation of this method returns null, indicating that
+ * canonical URIs are not supported.</p>
+ *
+ * @param url The Uri to canonicalize.
+ *
+ * @return Return the canonical representation of <var>url</var>, or null if
+ * canonicalization of that Uri is not supported.
+ */
+ @Override
+ public @Nullable Uri canonicalize(@NonNull Uri url) {
+ return null;
+ }
+
+ /**
+ * Remove canonicalization from canonical URIs previously returned by
+ * {@link #canonicalize}. For example, if your implementation is to add
+ * a query param to canonicalize a URI, this method can simply trip any
+ * query params on the URI. The default implementation always returns the
+ * same <var>url</var> that was passed in.
+ *
+ * @param url The Uri to remove any canonicalization from.
+ *
+ * @return Return the non-canonical representation of <var>url</var>, return
+ * the <var>url</var> as-is if there is nothing to do, or return null if
+ * the data identified by the canonical representation can not be found in
+ * the current environment.
+ */
+ @Override
+ public @Nullable Uri uncanonicalize(@NonNull Uri url) {
+ return url;
+ }
+
+ /**
+ * Implement this to support refresh of content identified by {@code uri}.
+ * By default, this method returns false; providers who wish to implement
+ * this should return true to signal the client that the provider has tried
+ * refreshing with its own implementation.
+ * <p>
+ * This allows clients to request an explicit refresh of content identified
+ * by {@code uri}.
+ * <p>
+ * Client code should only invoke this method when there is a strong
+ * indication (such as a user initiated pull to refresh gesture) that the
+ * content is stale.
+ * <p>
+ * Remember to send
+ * {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)}
+ * notifications when content changes.
+ *
+ * @param uri The Uri identifying the data to refresh.
+ * @param extras Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or {@code null} if none. For example, if you called refresh on
+ * a particular uri, you should call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the refresh request.
+ * @return true if the provider actually tried refreshing.
+ */
+ @Override
+ public boolean refresh(Uri uri, @Nullable Bundle extras,
+ @Nullable CancellationSignal cancellationSignal) {
+ return false;
+ }
+
+ /**
+ * Perform a detailed internal check on a {@link Uri} to determine if a UID
+ * is able to access it with specific mode flags.
+ * <p>
+ * This method is typically used when the provider implements more dynamic
+ * access controls that cannot be expressed with {@code <path-permission>}
+ * style static rules.
+ * <p>
+ * Because validation of these dynamic access controls has significant
+ * system health impact, this feature is only available to providers that
+ * are built into the system.
+ *
+ * @param uri the {@link Uri} to perform an access check on.
+ * @param uid the UID to check the permission for.
+ * @param modeFlags the access flags to use for the access check, such as
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+ * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
+ * otherwise {@link PackageManager#PERMISSION_DENIED}.
+ * @hide
+ */
+ @Override
+ @SystemApi
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ /**
+ * @hide
+ * Implementation when a caller has performed an insert on the content
+ * provider, but that call has been rejected for the operation given
+ * to {@link #setAppOps(int, int)}. The default implementation simply
+ * returns a URI that is the base URI with a 0 path element appended.
+ */
+ public Uri rejectInsert(Uri uri, ContentValues values) {
+ // If not allowed, we need to return some reasonable URI. Maybe the
+ // content provider should be responsible for this, but for now we
+ // will just return the base URI with a '0' tagged on to it.
+ // You shouldn't be able to read if you can't write, anyway, so it
+ // shouldn't matter much what is returned.
+ return uri.buildUpon().appendPath("0").build();
+ }
+
+ /**
+ * Implement this to handle requests to insert a new row. As a courtesy,
+ * call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after inserting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The content:// URI of the insertion request.
+ * @param values A set of column_name/value pairs to add to the database.
+ * @return The URI for the newly inserted item.
+ */
+ public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values);
+
+ /**
+ * Implement this to handle requests to insert a new row. As a courtesy,
+ * call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after inserting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The content:// URI of the insertion request.
+ * @param values A set of column_name/value pairs to add to the database.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return The URI for the newly inserted item.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable Bundle extras) {
+ return insert(uri, values);
+ }
+
+ /**
+ * Override this to handle requests to insert a set of new rows, or the
+ * default implementation will iterate over the values and call
+ * {@link #insert} on each of them.
+ * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
+ * after inserting.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The content:// URI of the insertion request.
+ * @param values An array of sets of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
+ * @return The number of values that were inserted.
+ */
+ @Override
+ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
+ int numValues = values.length;
+ for (int i = 0; i < numValues; i++) {
+ insert(uri, values[i]);
+ }
+ return numValues;
+ }
+
+ /**
+ * Implement this to handle requests to delete one or more rows. The
+ * implementation should apply the selection clause when performing
+ * deletion, allowing the operation to affect multiple rows in a directory.
+ * As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after deleting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ * <p>
+ * The implementation is responsible for parsing out a row ID at the end of
+ * the URI, if a specific row is being deleted. That is, the client would
+ * pass in <code>content://contacts/people/22</code> and the implementation
+ * is responsible for parsing the record number (22) when creating a SQL
+ * statement.
+ *
+ * @param uri The full URI to query, including a row ID (if a specific
+ * record is requested).
+ * @param selection An optional restriction to apply to rows when deleting.
+ * @return The number of rows affected.
+ * @throws SQLException
+ */
+ public abstract int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs);
+
+ /**
+ * Implement this to handle requests to delete one or more rows. The
+ * implementation should apply the selection clause when performing
+ * deletion, allowing the operation to affect multiple rows in a directory.
+ * As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after deleting. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ * <p>
+ * The implementation is responsible for parsing out a row ID at the end of
+ * the URI, if a specific row is being deleted. That is, the client would
+ * pass in <code>content://contacts/people/22</code> and the implementation
+ * is responsible for parsing the record number (22) when creating a SQL
+ * statement.
+ *
+ * @param uri The full URI to query, including a row ID (if a specific
+ * record is requested).
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ * @throws SQLException
+ */
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable Bundle extras) {
+ extras = (extras != null) ? extras : Bundle.EMPTY;
+ return delete(uri,
+ extras.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
+ extras.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS));
+ }
+
+ /**
+ * Implement this to handle requests to update one or more rows. The
+ * implementation should update all rows matching the selection to set the
+ * columns according to the provided values map. As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after updating. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The URI to query. This can potentially have a record ID if
+ * this is an update request for a specific record.
+ * @param values A set of column_name/value pairs to update in the database.
+ * @param selection An optional filter to match rows to update.
+ * @return the number of rows affected.
+ */
+ public abstract int update(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable String selection, @Nullable String[] selectionArgs);
+
+ /**
+ * Implement this to handle requests to update one or more rows. The
+ * implementation should update all rows matching the selection to set the
+ * columns according to the provided values map. As a courtesy, call
+ * {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver)
+ * notifyChange()} after updating. This method can be called from multiple
+ * threads, as described in <a href="
+ * {@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param uri The URI to query. This can potentially have a record ID if
+ * this is an update request for a specific record.
+ * @param values A set of column_name/value pairs to update in the database.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return the number of rows affected.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable Bundle extras) {
+ extras = (extras != null) ? extras : Bundle.EMPTY;
+ return update(uri, values,
+ extras.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
+ extras.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS));
+ }
+
+ /**
+ * Override this to handle requests to open a file blob.
+ * The default implementation always throws {@link FileNotFoundException}.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * <p>This method returns a ParcelFileDescriptor, which is returned directly
+ * to the caller. This way large data (such as images and documents) can be
+ * returned without copying the content.
+ *
+ * <p>The returned ParcelFileDescriptor is owned by the caller, so it is
+ * their responsibility to close it when done. That is, the implementation
+ * of this method should create a new ParcelFileDescriptor for each call.
+ * <p>
+ * If opened with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor can be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" or "rwt" modes implies a file on disk that
+ * supports seeking.
+ * <p>
+ * If you need to detect when the returned ParcelFileDescriptor has been
+ * closed, or if the remote process has crashed or encountered some other
+ * error, you can use {@link ParcelFileDescriptor#open(File, int,
+ * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)},
+ * {@link ParcelFileDescriptor#createReliablePipe()}, or
+ * {@link ParcelFileDescriptor#createReliableSocketPair()}.
+ * <p>
+ * If you need to return a large file that isn't backed by a real file on
+ * disk, such as a file on a network share or cloud storage service,
+ * consider using
+ * {@link StorageManager#openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler)}
+ * which will let you to stream the content on-demand.
+ *
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ *
+ * @return Returns a new ParcelFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openAssetFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
+ * @see ParcelFileDescriptor#parseMode(String)
+ */
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+ throws FileNotFoundException {
+ throw new FileNotFoundException("No files supported by provider at "
+ + uri);
+ }
+
+ /**
+ * Override this to handle requests to open a file blob.
+ * The default implementation always throws {@link FileNotFoundException}.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * <p>This method returns a ParcelFileDescriptor, which is returned directly
+ * to the caller. This way large data (such as images and documents) can be
+ * returned without copying the content.
+ *
+ * <p>The returned ParcelFileDescriptor is owned by the caller, so it is
+ * their responsibility to close it when done. That is, the implementation
+ * of this method should create a new ParcelFileDescriptor for each call.
+ * <p>
+ * If opened with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor can be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" or "rwt" modes implies a file on disk that
+ * supports seeking.
+ * <p>
+ * If you need to detect when the returned ParcelFileDescriptor has been
+ * closed, or if the remote process has crashed or encountered some other
+ * error, you can use {@link ParcelFileDescriptor#open(File, int,
+ * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)},
+ * {@link ParcelFileDescriptor#createReliablePipe()}, or
+ * {@link ParcelFileDescriptor#createReliableSocketPair()}.
+ *
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @param signal A signal to cancel the operation in progress, or
+ * {@code null} if none. For example, if you are downloading a
+ * file from the network to service a "rw" mode request, you
+ * should periodically call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the request and abort the download.
+ *
+ * @return Returns a new ParcelFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openAssetFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
+ * @see ParcelFileDescriptor#parseMode(String)
+ */
+ @Override
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws FileNotFoundException {
+ return openFile(uri, mode);
+ }
+
+ /**
+ * This is like {@link #openFile}, but can be implemented by providers
+ * that need to be able to return sub-sections of files, often assets
+ * inside of their .apk.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * <p>If you implement this, your clients must be able to deal with such
+ * file slices, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
+ *
+ * <p class="note">If you are implementing this to return a full file, you
+ * should create the AssetFileDescriptor with
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+ * applications that cannot handle sub-sections of files.</p>
+ *
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.</p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ *
+ * @return Returns a new AssetFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
+ */
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
+ throws FileNotFoundException {
+ ParcelFileDescriptor fd = openFile(uri, mode);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
+
+ /**
+ * This is like {@link #openFile}, but can be implemented by providers
+ * that need to be able to return sub-sections of files, often assets
+ * inside of their .apk.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * <p>If you implement this, your clients must be able to deal with such
+ * file slices, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
+ *
+ * <p class="note">If you are implementing this to return a full file, you
+ * should create the AssetFileDescriptor with
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+ * applications that cannot handle sub-sections of files.</p>
+ *
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.</p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @param signal A signal to cancel the operation in progress, or
+ * {@code null} if none. For example, if you are downloading a
+ * file from the network to service a "rw" mode request, you
+ * should periodically call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the request and abort the download.
+ *
+ * @return Returns a new AssetFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
+ */
+ @Override
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws FileNotFoundException {
+ return openAssetFile(uri, mode);
+ }
+
+ /**
+ * Convenience for subclasses that wish to implement {@link #openFile}
+ * by looking up a column named "_data" at the given URI.
+ *
+ * @param uri The URI to be opened.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ *
+ * @return Returns a new ParcelFileDescriptor that can be used by the
+ * client to access the file.
+ */
+ protected final @NonNull ParcelFileDescriptor openFileHelper(@NonNull Uri uri,
+ @NonNull String mode) throws FileNotFoundException {
+ Cursor c = query(uri, new String[]{"_data"}, null, null, null);
+ int count = (c != null) ? c.getCount() : 0;
+ if (count != 1) {
+ // If there is not exactly one result, throw an appropriate
+ // exception.
+ if (c != null) {
+ c.close();
+ }
+ if (count == 0) {
+ throw new FileNotFoundException("No entry for " + uri);
+ }
+ throw new FileNotFoundException("Multiple items at " + uri);
+ }
+
+ c.moveToFirst();
+ int i = c.getColumnIndex("_data");
+ String path = (i >= 0 ? c.getString(i) : null);
+ c.close();
+ if (path == null) {
+ throw new FileNotFoundException("Column _data not found.");
+ }
+
+ int modeBits = ParcelFileDescriptor.parseMode(mode);
+ return ParcelFileDescriptor.open(new File(path), modeBits);
+ }
+
+ /**
+ * Called by a client to determine the types of data streams that this
+ * content provider supports for the given URI. The default implementation
+ * returns {@code null}, meaning no types. If your content provider stores data
+ * of a particular type, return that MIME type if it matches the given
+ * mimeTypeFilter. If it can perform type conversions, return an array
+ * of all supported MIME types that match mimeTypeFilter.
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as */* to retrieve all possible data types.
+ * @return Returns {@code null} if there are no possible data streams for the
+ * given mimeTypeFilter. Otherwise returns an array of all available
+ * concrete MIME types.
+ *
+ * @see #getType(Uri)
+ * @see #openTypedAssetFile(Uri, String, Bundle)
+ * @see ClipDescription#compareMimeTypes(String, String)
+ */
+ @Override
+ public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) {
+ return null;
+ }
+
+ /**
+ * Called by a client to open a read-only stream containing data of a
+ * particular MIME type. This is like {@link #openAssetFile(Uri, String)},
+ * except the file can only be read-only and the content provider may
+ * perform data conversions to generate data of the desired type.
+ *
+ * <p>The default implementation compares the given mimeType against the
+ * result of {@link #getType(Uri)} and, if they match, simply calls
+ * {@link #openAssetFile(Uri, String)}.
+ *
+ * <p>See {@link ClipData} for examples of the use and implementation
+ * of this method.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as */*, if the caller does not have specific type
+ * requirements; in this case the content provider will pick its best
+ * type matching the pattern.
+ * @param opts Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ *
+ * @return Returns a new AssetFileDescriptor from which the client can
+ * read data of the desired type.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the data.
+ * @throws IllegalArgumentException Throws IllegalArgumentException if the
+ * content provider does not support the requested MIME type.
+ *
+ * @see #getStreamTypes(Uri, String)
+ * @see #openAssetFile(Uri, String)
+ * @see ClipDescription#compareMimeTypes(String, String)
+ */
+ public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+ @NonNull String mimeTypeFilter, @Nullable Bundle opts) throws FileNotFoundException {
+ if ("*/*".equals(mimeTypeFilter)) {
+ // If they can take anything, the untyped open call is good enough.
+ return openAssetFile(uri, "r");
+ }
+ String baseType = getType(uri);
+ if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
+ // Use old untyped open call if this provider has a type for this
+ // URI and it matches the request.
+ return openAssetFile(uri, "r");
+ }
+ throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter);
+ }
+
+
+ /**
+ * Called by a client to open a read-only stream containing data of a
+ * particular MIME type. This is like {@link #openAssetFile(Uri, String)},
+ * except the file can only be read-only and the content provider may
+ * perform data conversions to generate data of the desired type.
+ *
+ * <p>The default implementation compares the given mimeType against the
+ * result of {@link #getType(Uri)} and, if they match, simply calls
+ * {@link #openAssetFile(Uri, String)}.
+ *
+ * <p>See {@link ClipData} for examples of the use and implementation
+ * of this method.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as */*, if the caller does not have specific type
+ * requirements; in this case the content provider will pick its best
+ * type matching the pattern.
+ * @param opts Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ * @param signal A signal to cancel the operation in progress, or
+ * {@code null} if none. For example, if you are downloading a
+ * file from the network to service a "rw" mode request, you
+ * should periodically call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the request and abort the download.
+ *
+ * @return Returns a new AssetFileDescriptor from which the client can
+ * read data of the desired type.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the data.
+ * @throws IllegalArgumentException Throws IllegalArgumentException if the
+ * content provider does not support the requested MIME type.
+ *
+ * @see #getStreamTypes(Uri, String)
+ * @see #openAssetFile(Uri, String)
+ * @see ClipDescription#compareMimeTypes(String, String)
+ */
+ @Override
+ public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+ @NonNull String mimeTypeFilter, @Nullable Bundle opts,
+ @Nullable CancellationSignal signal) throws FileNotFoundException {
+ return openTypedAssetFile(uri, mimeTypeFilter, opts);
+ }
+
+ /**
+ * Interface to write a stream of data to a pipe. Use with
+ * {@link ContentProvider#openPipeHelper}.
+ */
+ public interface PipeDataWriter<T> {
+ /**
+ * Called from a background thread to stream data out to a pipe.
+ * Note that the pipe is blocking, so this thread can block on
+ * writes for an arbitrary amount of time if the client is slow
+ * at reading.
+ *
+ * @param output The pipe where data should be written. This will be
+ * closed for you upon returning from this function.
+ * @param uri The URI whose data is to be written.
+ * @param mimeType The desired type of data to be written.
+ * @param opts Options supplied by caller.
+ * @param args Your own custom arguments.
+ */
+ public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri,
+ @NonNull String mimeType, @Nullable Bundle opts, @Nullable T args);
+ }
+
+ /**
+ * A helper function for implementing {@link #openTypedAssetFile}, for
+ * creating a data pipe and background thread allowing you to stream
+ * generated data back to the client. This function returns a new
+ * ParcelFileDescriptor that should be returned to the caller (the caller
+ * is responsible for closing it).
+ *
+ * @param uri The URI whose data is to be written.
+ * @param mimeType The desired type of data to be written.
+ * @param opts Options supplied by caller.
+ * @param args Your own custom arguments.
+ * @param func Interface implementing the function that will actually
+ * stream the data.
+ * @return Returns a new ParcelFileDescriptor holding the read side of
+ * the pipe. This should be returned to the caller for reading; the caller
+ * is responsible for closing it when done.
+ */
+ public @NonNull <T> ParcelFileDescriptor openPipeHelper(final @NonNull Uri uri,
+ final @NonNull String mimeType, final @Nullable Bundle opts, final @Nullable T args,
+ final @NonNull PipeDataWriter<T> func) throws FileNotFoundException {
+ try {
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+
+ AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
+ @Override
+ protected Object doInBackground(Object... params) {
+ func.writeDataToPipe(fds[1], uri, mimeType, opts, args);
+ try {
+ fds[1].close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failure closing pipe", e);
+ }
+ return null;
+ }
+ };
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[])null);
+
+ return fds[0];
+ } catch (IOException e) {
+ throw new FileNotFoundException("failure making pipe");
+ }
+ }
+
+ /**
+ * Returns true if this instance is a temporary content provider.
+ * @return true if this instance is a temporary content provider
+ */
+ protected boolean isTemporary() {
+ return false;
+ }
+
+ /**
+ * Returns the Binder object for this provider.
+ *
+ * @return the Binder object for this provider
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public IContentProvider getIContentProvider() {
+ return mTransport;
+ }
+
+ /**
+ * Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
+ * when directly instantiating the provider for testing.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void attachInfoForTesting(Context context, ProviderInfo info) {
+ attachInfo(context, info, true);
+ }
+
+ /**
+ * After being instantiated, this is called to tell the content provider
+ * about itself.
+ *
+ * @param context The context this provider is running in
+ * @param info Registered information about this content provider
+ */
+ public void attachInfo(Context context, ProviderInfo info) {
+ attachInfo(context, info, false);
+ }
+
+ private void attachInfo(Context context, ProviderInfo info, boolean testing) {
+ mNoPerms = testing;
+ mCallingAttributionSource = new ThreadLocal<>();
+
+ /*
+ * Only allow it to be set once, so after the content service gives
+ * this to us clients can't change it.
+ */
+ if (mContext == null) {
+ mContext = context;
+ if (context != null && mTransport != null) {
+ mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
+ Context.APP_OPS_SERVICE);
+ }
+ mMyUid = Process.myUid();
+ if (info != null) {
+ setReadPermission(info.readPermission);
+ setWritePermission(info.writePermission);
+ setPathPermissions(info.pathPermissions);
+ mExported = info.exported;
+ mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
+ setAuthorities(info.authority);
+ }
+ if (Build.IS_DEBUGGABLE) {
+ setTransportLoggingEnabled(Log.isLoggable(getClass().getSimpleName(),
+ Log.VERBOSE));
+ }
+ ContentProvider.this.onCreate();
+ }
+ }
+
+ /**
+ * Override this to handle requests to perform a batch of operations, or the
+ * default implementation will iterate over the operations and call
+ * {@link ContentProviderOperation#apply} on each of them.
+ * If all calls to {@link ContentProviderOperation#apply} succeed
+ * then a {@link ContentProviderResult} array with as many
+ * elements as there were operations will be returned. If any of the calls
+ * fail, it is up to the implementation how many of the others take effect.
+ * This method can be called from multiple threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+ * and Threads</a>.
+ *
+ * @param operations the operations to apply
+ * @return the results of the applications
+ * @throws OperationApplicationException thrown if any operation fails.
+ * @see ContentProviderOperation#apply
+ */
+ @Override
+ public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ return applyBatch(operations);
+ }
+
+ public @NonNull ContentProviderResult[] applyBatch(
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ results[i] = operations.get(i).apply(this, results, i);
+ }
+ return results;
+ }
+
+ /**
+ * Call a provider-defined method. This can be used to implement
+ * interfaces that are cheaper and/or unnatural for a table-like
+ * model.
+ *
+ * <p class="note"><strong>WARNING:</strong> The framework does no permission checking
+ * on this entry into the content provider besides the basic ability for the application
+ * to get access to the provider at all. For example, it has no idea whether the call
+ * being executed may read or write data in the provider, so can't enforce those
+ * individual permissions. Any implementation of this method <strong>must</strong>
+ * do its own permission checks on incoming calls to make sure they are allowed.</p>
+ *
+ * @param method method name to call. Opaque to framework, but should not be {@code null}.
+ * @param arg provider-defined String argument. May be {@code null}.
+ * @param extras provider-defined Bundle argument. May be {@code null}.
+ * @return provider-defined return value. May be {@code null}, which is also
+ * the default for providers which don't implement any call methods.
+ */
+ @Override
+ public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
+ @Nullable String arg, @Nullable Bundle extras) {
+ return call(method, arg, extras);
+ }
+
+ public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
+ @Nullable Bundle extras) {
+ return null;
+ }
+
+ /**
+ * Implement this to shut down the ContentProvider instance. You can then
+ * invoke this method in unit tests.
+ *
+ * <p>
+ * Android normally handles ContentProvider startup and shutdown
+ * automatically. You do not need to start up or shut down a
+ * ContentProvider. When you invoke a test method on a ContentProvider,
+ * however, a ContentProvider instance is started and keeps running after
+ * the test finishes, even if a succeeding test instantiates another
+ * ContentProvider. A conflict develops because the two instances are
+ * usually running against the same underlying data source (for example, an
+ * sqlite database).
+ * </p>
+ * <p>
+ * Implementing shutDown() avoids this conflict by providing a way to
+ * terminate the ContentProvider. This method can also prevent memory leaks
+ * from multiple instantiations of the ContentProvider, and it can ensure
+ * unit test isolation by allowing you to completely clean up the test
+ * fixture before moving on to the next test.
+ * </p>
+ */
+ public void shutdown() {
+ Log.w(TAG, "implement ContentProvider shutdown() to make sure all database " +
+ "connections are gracefully shutdown");
+ }
+
+ /**
+ * Print the Provider's state into the given stream. This gets invoked if
+ * you run "adb shell dumpsys activity provider <provider_component_name>".
+ *
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("nothing to dump");
+ }
+
+ private void validateIncomingAuthority(String authority) throws SecurityException {
+ if (!matchesOurAuthorities(getAuthorityWithoutUserId(authority))) {
+ String message = "The authority " + authority + " does not match the one of the "
+ + "contentProvider: ";
+ if (mAuthority != null) {
+ message += mAuthority;
+ } else {
+ message += Arrays.toString(mAuthorities);
+ }
+ throw new SecurityException(message);
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public Uri validateIncomingUri(Uri uri) throws SecurityException {
+ String auth = uri.getAuthority();
+ if (!mSingleUser) {
+ int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+ if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId()) {
+ throw new SecurityException("trying to query a ContentProvider in user "
+ + mContext.getUserId() + " with a uri belonging to user " + userId);
+ }
+ }
+ validateIncomingAuthority(auth);
+
+ // Normalize the path by removing any empty path segments, which can be
+ // a source of security issues.
+ final String encodedPath = uri.getEncodedPath();
+ if (encodedPath != null && encodedPath.indexOf("//") != -1) {
+ final Uri normalized = uri.buildUpon()
+ .encodedPath(encodedPath.replaceAll("//+", "/")).build();
+ Log.w(TAG, "Normalized " + uri + " to " + normalized
+ + " to avoid possible security issues");
+ return normalized;
+ } else {
+ return uri;
+ }
+ }
+
+ /** @hide */
+ private Uri maybeGetUriWithoutUserId(Uri uri) {
+ if (mSingleUser) {
+ return uri;
+ }
+ return getUriWithoutUserId(uri);
+ }
+
+ /** @hide */
+ public static int getUserIdFromAuthority(String auth, int defaultUserId) {
+ if (auth == null) return defaultUserId;
+ int end = auth.lastIndexOf('@');
+ if (end == -1) return defaultUserId;
+ String userIdString = auth.substring(0, end);
+ try {
+ return Integer.parseInt(userIdString);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Error parsing userId.", e);
+ return UserHandle.USER_NULL;
+ }
+ }
+
+ /** @hide */
+ public static int getUserIdFromAuthority(String auth) {
+ return getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+ }
+
+ /** @hide */
+ public static int getUserIdFromUri(Uri uri, int defaultUserId) {
+ if (uri == null) return defaultUserId;
+ return getUserIdFromAuthority(uri.getAuthority(), defaultUserId);
+ }
+
+ /** @hide */
+ public static int getUserIdFromUri(Uri uri) {
+ return getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Returns the user associated with the given URI.
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull static UserHandle getUserHandleFromUri(@NonNull Uri uri) {
+ return UserHandle.of(getUserIdFromUri(uri, Process.myUserHandle().getIdentifier()));
+ }
+
+ /**
+ * Removes userId part from authority string. Expects format:
+ * [email protected]
+ * If there is no userId in the authority, it symply returns the argument
+ * @hide
+ */
+ public static String getAuthorityWithoutUserId(String auth) {
+ if (auth == null) return null;
+ int end = auth.lastIndexOf('@');
+ return auth.substring(end+1);
+ }
+
+ /** @hide */
+ public static Uri getUriWithoutUserId(Uri uri) {
+ if (uri == null) return null;
+ Uri.Builder builder = uri.buildUpon();
+ builder.authority(getAuthorityWithoutUserId(uri.getAuthority()));
+ return builder.build();
+ }
+
+ /** @hide */
+ public static boolean uriHasUserId(Uri uri) {
+ if (uri == null) return false;
+ return !TextUtils.isEmpty(uri.getUserInfo());
+ }
+
+ /**
+ * Returns the given content URI explicitly associated with the given {@link UserHandle}.
+ *
+ * @param contentUri The content URI to be associated with a user handle.
+ * @param userHandle The user handle with which to associate the URI.
+ *
+ * @throws IllegalArgumentException if
+ * <ul>
+ * <li>the given URI is not content URI (a content URI has {@link Uri#getScheme} equal to
+ * {@link ContentResolver.SCHEME_CONTENT}) or</li>
+ * <li>the given URI is already explicitly associated with a {@link UserHandle}, which is
+ * different than the given one.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static Uri createContentUriForUser(
+ @NonNull Uri contentUri, @NonNull UserHandle userHandle) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+ throw new IllegalArgumentException(String.format(
+ "Given URI [%s] is not a content URI: ", contentUri));
+ }
+
+ int userId = userHandle.getIdentifier();
+ if (uriHasUserId(contentUri)) {
+ if (String.valueOf(userId).equals(contentUri.getUserInfo())) {
+ return contentUri;
+ }
+ throw new IllegalArgumentException(String.format(
+ "Given URI [%s] already has a user ID, different from given user handle [%s]",
+ contentUri,
+ userId));
+ }
+
+ Uri.Builder builder = contentUri.buildUpon();
+ builder.encodedAuthority(
+ "" + userHandle.getIdentifier() + "@" + contentUri.getEncodedAuthority());
+ return builder.build();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static Uri maybeAddUserId(Uri uri, int userId) {
+ if (uri == null) return null;
+ if (userId != UserHandle.USER_CURRENT
+ && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (!uriHasUserId(uri)) {
+ //We don't add the user Id if there's already one
+ Uri.Builder builder = uri.buildUpon();
+ builder.encodedAuthority("" + userId + "@" + uri.getEncodedAuthority());
+ return builder.build();
+ }
+ }
+ return uri;
+ }
+
+ private static void traceBegin(long traceTag, String methodName, String subInfo) {
+ if (Trace.isTagEnabled(traceTag)) {
+ Trace.traceBegin(traceTag, methodName + subInfo);
+ }
+ }
+}
diff --git a/android/content/ContentProviderClient.java b/android/content/ContentProviderClient.java
new file mode 100644
index 0000000..518e753
--- /dev/null
+++ b/android/content/ContentProviderClient.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2009 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.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetFileDescriptor;
+import android.database.CrossProcessCursorWrapper;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import libcore.io.IoUtils;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * The public interface object used to interact with a specific
+ * {@link ContentProvider}.
+ * <p>
+ * Instances can be obtained by calling
+ * {@link ContentResolver#acquireContentProviderClient} or
+ * {@link ContentResolver#acquireUnstableContentProviderClient}. Instances must
+ * be released using {@link #close()} in order to indicate to the system that
+ * the underlying {@link ContentProvider} is no longer needed and can be killed
+ * to free up resources.
+ * <p>
+ * Note that you should generally create a new ContentProviderClient instance
+ * for each thread that will be performing operations. Unlike
+ * {@link ContentResolver}, the methods here such as {@link #query} and
+ * {@link #openFile} are not thread safe -- you must not call {@link #close()}
+ * on the ContentProviderClient those calls are made from until you are finished
+ * with the data they have returned.
+ */
+public class ContentProviderClient implements ContentInterface, AutoCloseable {
+ private static final String TAG = "ContentProviderClient";
+
+ @GuardedBy("ContentProviderClient.class")
+ private static Handler sAnrHandler;
+
+ private final ContentResolver mContentResolver;
+ @UnsupportedAppUsage
+ private final IContentProvider mContentProvider;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final String mPackageName;
+ private final @NonNull AttributionSource mAttributionSource;
+
+ private final String mAuthority;
+ private final boolean mStable;
+
+ private final AtomicBoolean mClosed = new AtomicBoolean();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private long mAnrTimeout;
+ private NotRespondingRunnable mAnrRunnable;
+
+ /** {@hide} */
+ @VisibleForTesting
+ public ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider,
+ boolean stable) {
+ // Only used for testing, so use a fake authority
+ this(contentResolver, contentProvider, "unknown", stable);
+ }
+
+ /** {@hide} */
+ public ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider,
+ String authority, boolean stable) {
+ mContentResolver = contentResolver;
+ mContentProvider = contentProvider;
+ mPackageName = contentResolver.mPackageName;
+ mAttributionSource = contentResolver.getAttributionSource();
+
+ mAuthority = authority;
+ mStable = stable;
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Configure this client to automatically detect and kill the remote
+ * provider when an "application not responding" event is detected.
+ *
+ * @param timeoutMillis the duration for which a pending call is allowed
+ * block before the remote provider is considered to be
+ * unresponsive. Set to {@code 0} to allow pending calls to block
+ * indefinitely with no action taken.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
+ public void setDetectNotResponding(@DurationMillisLong long timeoutMillis) {
+ synchronized (ContentProviderClient.class) {
+ mAnrTimeout = timeoutMillis;
+
+ if (timeoutMillis > 0) {
+ if (mAnrRunnable == null) {
+ mAnrRunnable = new NotRespondingRunnable();
+ }
+ if (sAnrHandler == null) {
+ sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
+ }
+
+ // If the remote process hangs, we're going to kill it, so we're
+ // technically okay doing blocking calls.
+ Binder.allowBlocking(mContentProvider.asBinder());
+ } else {
+ mAnrRunnable = null;
+
+ // If we're no longer watching for hangs, revert back to default
+ // blocking behavior.
+ Binder.defaultBlocking(mContentProvider.asBinder());
+ }
+ }
+ }
+
+ private void beforeRemote() {
+ if (mAnrRunnable != null) {
+ sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
+ }
+ }
+
+ private void afterRemote() {
+ if (mAnrRunnable != null) {
+ sAnrHandler.removeCallbacks(mAnrRunnable);
+ }
+ }
+
+ /** See {@link ContentProvider#query ContentProvider.query} */
+ public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) throws RemoteException {
+ return query(url, projection, selection, selectionArgs, sortOrder, null);
+ }
+
+ /** See {@link ContentProvider#query ContentProvider.query} */
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)
+ throws RemoteException {
+ Bundle queryArgs =
+ ContentResolver.createSqlQueryBundle(selection, selectionArgs, sortOrder);
+ return query(uri, projection, queryArgs, cancellationSignal);
+ }
+
+ /** See {@link ContentProvider#query ContentProvider.query} */
+ @Override
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
+ throws RemoteException {
+ Objects.requireNonNull(uri, "url");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = mContentProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ final Cursor cursor = mContentProvider.query(
+ mAttributionSource, uri, projection, queryArgs,
+ remoteCancellationSignal);
+ if (cursor == null) {
+ return null;
+ }
+ return new CursorWrapperInner(cursor);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#getType ContentProvider.getType} */
+ @Override
+ public @Nullable String getType(@NonNull Uri url) throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.getType(url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
+ @Override
+ public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
+ throws RemoteException {
+ Objects.requireNonNull(url, "url");
+ Objects.requireNonNull(mimeTypeFilter, "mimeTypeFilter");
+
+ beforeRemote();
+ try {
+ return mContentProvider.getStreamTypes(url, mimeTypeFilter);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#canonicalize} */
+ @Override
+ public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.canonicalize(mAttributionSource, url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#uncanonicalize} */
+ @Override
+ public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.uncanonicalize(mAttributionSource, url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#refresh} */
+ @Override
+ public boolean refresh(Uri url, @Nullable Bundle extras,
+ @Nullable CancellationSignal cancellationSignal) throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = mContentProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ return mContentProvider.refresh(mAttributionSource, url, extras,
+ remoteCancellationSignal);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** {@hide} */
+ @Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
+ throws RemoteException {
+ Objects.requireNonNull(uri, "uri");
+
+ beforeRemote();
+ try {
+ return mContentProvider.checkUriPermission(mAttributionSource, uri, uid,
+ modeFlags);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#insert ContentProvider.insert} */
+ public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
+ throws RemoteException {
+ return insert(url, initialValues, null);
+ }
+
+ /** See {@link ContentProvider#insert ContentProvider.insert} */
+ @Override
+ public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues,
+ @Nullable Bundle extras) throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.insert(mAttributionSource, url, initialValues,
+ extras);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
+ @Override
+ public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
+ throws RemoteException {
+ Objects.requireNonNull(url, "url");
+ Objects.requireNonNull(initialValues, "initialValues");
+
+ beforeRemote();
+ try {
+ return mContentProvider.bulkInsert(mAttributionSource, url, initialValues);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#delete ContentProvider.delete} */
+ public int delete(@NonNull Uri url, @Nullable String selection,
+ @Nullable String[] selectionArgs) throws RemoteException {
+ return delete(url, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ }
+
+ /** See {@link ContentProvider#delete ContentProvider.delete} */
+ @Override
+ public int delete(@NonNull Uri url, @Nullable Bundle extras) throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.delete(mAttributionSource, url, extras);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#update ContentProvider.update} */
+ public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
+ @Nullable String[] selectionArgs) throws RemoteException {
+ return update(url, values, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ }
+
+ /** See {@link ContentProvider#update ContentProvider.update} */
+ @Override
+ public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable Bundle extras)
+ throws RemoteException {
+ Objects.requireNonNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.update(mAttributionSource, url, values, extras);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /**
+ * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that
+ * this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openFileDescriptor
+ * ContentResolver.openFileDescriptor} API instead.
+ */
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
+ throws RemoteException, FileNotFoundException {
+ return openFile(url, mode, null);
+ }
+
+ /**
+ * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that
+ * this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openFileDescriptor
+ * ContentResolver.openFileDescriptor} API instead.
+ */
+ @Override
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ Objects.requireNonNull(url, "url");
+ Objects.requireNonNull(mode, "mode");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ signal.throwIfCanceled();
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
+ return mContentProvider.openFile(mAttributionSource, url, mode, remoteSignal);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /**
+ * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
+ * Note that this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor} API instead.
+ */
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
+ throws RemoteException, FileNotFoundException {
+ return openAssetFile(url, mode, null);
+ }
+
+ /**
+ * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
+ * Note that this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor} API instead.
+ */
+ @Override
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ Objects.requireNonNull(url, "url");
+ Objects.requireNonNull(mode, "mode");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ signal.throwIfCanceled();
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
+ return mContentProvider.openAssetFile(mAttributionSource, url, mode,
+ remoteSignal);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
+ public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
+ @NonNull String mimeType, @Nullable Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
+ }
+
+ /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
+ public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
+ @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ return openTypedAssetFile(uri, mimeType, opts, signal);
+ }
+
+ @Override
+ public final @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+ @NonNull String mimeTypeFilter, @Nullable Bundle opts,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ Objects.requireNonNull(uri, "uri");
+ Objects.requireNonNull(mimeTypeFilter, "mimeTypeFilter");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ signal.throwIfCanceled();
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
+ return mContentProvider.openTypedAssetFile(
+ mAttributionSource, uri, mimeTypeFilter, opts, remoteSignal);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
+ public @NonNull ContentProviderResult[] applyBatch(
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ return applyBatch(mAuthority, operations);
+ }
+
+ /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
+ @Override
+ public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ Objects.requireNonNull(operations, "operations");
+
+ beforeRemote();
+ try {
+ return mContentProvider.applyBatch(mAttributionSource, authority,
+ operations);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#call(String, String, Bundle)} */
+ public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
+ @Nullable Bundle extras) throws RemoteException {
+ return call(mAuthority, method, arg, extras);
+ }
+
+ /** See {@link ContentProvider#call(String, String, Bundle)} */
+ @Override
+ public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
+ @Nullable String arg, @Nullable Bundle extras) throws RemoteException {
+ Objects.requireNonNull(authority, "authority");
+ Objects.requireNonNull(method, "method");
+
+ beforeRemote();
+ try {
+ return mContentProvider.call(mAttributionSource, authority, method, arg,
+ extras);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /**
+ * Closes this client connection, indicating to the system that the
+ * underlying {@link ContentProvider} is no longer needed.
+ */
+ @Override
+ public void close() {
+ closeInternal();
+ }
+
+ /**
+ * @deprecated replaced by {@link #close()}.
+ */
+ @Deprecated
+ public boolean release() {
+ return closeInternal();
+ }
+
+ private boolean closeInternal() {
+ mCloseGuard.close();
+ if (mClosed.compareAndSet(false, true)) {
+ // We can't do ANR checks after we cease to exist! Reset any
+ // blocking behavior changes we might have made.
+ setDetectNotResponding(0);
+
+ if (mStable) {
+ return mContentResolver.releaseProvider(mContentProvider);
+ } else {
+ return mContentResolver.releaseUnstableProvider(mContentProvider);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Get a reference to the {@link ContentProvider} that is associated with this
+ * client. If the {@link ContentProvider} is running in a different process then
+ * null will be returned. This can be used if you know you are running in the same
+ * process as a provider, and want to get direct access to its implementation details.
+ *
+ * @return If the associated {@link ContentProvider} is local, returns it.
+ * Otherwise returns null.
+ */
+ public @Nullable ContentProvider getLocalContentProvider() {
+ return ContentProvider.coerceToLocalContentProvider(mContentProvider);
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public static void closeQuietly(ContentProviderClient client) {
+ IoUtils.closeQuietly(client);
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public static void releaseQuietly(ContentProviderClient client) {
+ IoUtils.closeQuietly(client);
+ }
+
+ private class NotRespondingRunnable implements Runnable {
+ @Override
+ public void run() {
+ Log.w(TAG, "Detected provider not responding: " + mContentProvider);
+ mContentResolver.appNotRespondingViaProvider(mContentProvider);
+ }
+ }
+
+ private final class CursorWrapperInner extends CrossProcessCursorWrapper {
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ CursorWrapperInner(Cursor cursor) {
+ super(cursor);
+ mCloseGuard.open("close");
+ }
+
+ @Override
+ public void close() {
+ mCloseGuard.close();
+ super.close();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+}
diff --git a/android/content/ContentProviderNative.java b/android/content/ContentProviderNative.java
new file mode 100644
index 0000000..47c9669
--- /dev/null
+++ b/android/content/ContentProviderNative.java
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2006 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.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetFileDescriptor;
+import android.database.BulkCursorDescriptor;
+import android.database.BulkCursorToCursorAdaptor;
+import android.database.Cursor;
+import android.database.CursorToBulkCursorAdaptor;
+import android.database.DatabaseUtils;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+abstract public class ContentProviderNative extends Binder implements IContentProvider {
+ public ContentProviderNative()
+ {
+ attachInterface(this, descriptor);
+ }
+
+ /**
+ * Cast a Binder object into a content resolver interface, generating
+ * a proxy if needed.
+ */
+ @UnsupportedAppUsage
+ static public IContentProvider asInterface(IBinder obj)
+ {
+ if (obj == null) {
+ return null;
+ }
+ IContentProvider in =
+ (IContentProvider)obj.queryLocalInterface(descriptor);
+ if (in != null) {
+ return in;
+ }
+
+ return new ContentProviderProxy(obj);
+ }
+
+ /**
+ * Gets the name of the content provider.
+ * Should probably be part of the {@link IContentProvider} interface.
+ * @return The content provider name.
+ */
+ public abstract String getProviderName();
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ switch (code) {
+ case QUERY_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ // String[] projection
+ int num = data.readInt();
+ String[] projection = null;
+ if (num > 0) {
+ projection = new String[num];
+ for (int i = 0; i < num; i++) {
+ projection[i] = data.readString();
+ }
+ }
+
+ Bundle queryArgs = data.readBundle();
+ IContentObserver observer = IContentObserver.Stub.asInterface(
+ data.readStrongBinder());
+ ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ Cursor cursor = query(attributionSource, url, projection, queryArgs,
+ cancellationSignal);
+ if (cursor != null) {
+ CursorToBulkCursorAdaptor adaptor = null;
+
+ try {
+ adaptor = new CursorToBulkCursorAdaptor(cursor, observer,
+ getProviderName());
+ cursor = null;
+
+ BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
+ adaptor = null;
+
+ reply.writeNoException();
+ reply.writeInt(1);
+ d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } finally {
+ // Close cursor if an exception was thrown while constructing the adaptor.
+ if (adaptor != null) {
+ adaptor.close();
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ } else {
+ reply.writeNoException();
+ reply.writeInt(0);
+ }
+
+ return true;
+ }
+
+ case GET_TYPE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String type = getType(url);
+ reply.writeNoException();
+ reply.writeString(type);
+
+ return true;
+ }
+
+ case GET_TYPE_ASYNC_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
+ getTypeAsync(url, callback);
+ return true;
+ }
+
+ case INSERT_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+
+ Uri out = insert(attributionSource, url, values, extras);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case BULK_INSERT_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
+
+ int count = bulkInsert(attributionSource, url, values);
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case APPLY_BATCH_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ String authority = data.readString();
+ final int numOperations = data.readInt();
+ final ArrayList<ContentProviderOperation> operations =
+ new ArrayList<>(numOperations);
+ for (int i = 0; i < numOperations; i++) {
+ operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
+ }
+ final ContentProviderResult[] results = applyBatch(attributionSource,
+ authority, operations);
+ reply.writeNoException();
+ reply.writeTypedArray(results, 0);
+ return true;
+ }
+
+ case DELETE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+
+ int count = delete(attributionSource, url, extras);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case UPDATE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+
+ int count = update(attributionSource, url, values, extras);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case OPEN_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ ParcelFileDescriptor fd;
+ fd = openFile(attributionSource, url, mode, signal);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
+ case OPEN_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(attributionSource, url, mode, signal);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
+ case CALL_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ String authority = data.readString();
+ String method = data.readString();
+ String stringArg = data.readString();
+ Bundle extras = data.readBundle();
+
+ Bundle responseBundle = call(attributionSource, authority, method,
+ stringArg, extras);
+
+ reply.writeNoException();
+ reply.writeBundle(responseBundle);
+ return true;
+ }
+
+ case GET_STREAM_TYPES_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mimeTypeFilter = data.readString();
+ String[] types = getStreamTypes(url, mimeTypeFilter);
+ reply.writeNoException();
+ reply.writeStringArray(types);
+
+ return true;
+ }
+
+ case OPEN_TYPED_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mimeType = data.readString();
+ Bundle opts = data.readBundle();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ AssetFileDescriptor fd;
+ fd = openTypedAssetFile(attributionSource, url, mimeType, opts, signal);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
+ case CREATE_CANCELATION_SIGNAL_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+
+ ICancellationSignal cancellationSignal = createCancellationSignal();
+ reply.writeNoException();
+ reply.writeStrongBinder(cancellationSignal.asBinder());
+ return true;
+ }
+
+ case CANONICALIZE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = canonicalize(attributionSource, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case CANONICALIZE_ASYNC_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
+ canonicalizeAsync(attributionSource, uri, callback);
+ return true;
+ }
+
+ case UNCANONICALIZE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = uncanonicalize(attributionSource, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case UNCANONICALIZE_ASYNC_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
+ uncanonicalizeAsync(attributionSource, uri, callback);
+ return true;
+ }
+
+ case REFRESH_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ boolean out = refresh(attributionSource, url, extras, signal);
+ reply.writeNoException();
+ reply.writeInt(out ? 0 : -1);
+ return true;
+ }
+
+ case CHECK_URI_PERMISSION_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ int uid = data.readInt();
+ int modeFlags = data.readInt();
+
+ int out = checkUriPermission(attributionSource, uri, uid, modeFlags);
+ reply.writeNoException();
+ reply.writeInt(out);
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ DatabaseUtils.writeExceptionToParcel(reply, e);
+ return true;
+ }
+
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ @Override
+ public IBinder asBinder()
+ {
+ return this;
+ }
+}
+
+
+final class ContentProviderProxy implements IContentProvider
+{
+ public ContentProviderProxy(IBinder remote)
+ {
+ mRemote = remote;
+ }
+
+ @Override
+ public IBinder asBinder()
+ {
+ return mRemote;
+ }
+
+ @Override
+ public Cursor query(@NonNull AttributionSource attributionSource, Uri url,
+ @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable ICancellationSignal cancellationSignal)
+ throws RemoteException {
+ BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ int length = 0;
+ if (projection != null) {
+ length = projection.length;
+ }
+ data.writeInt(length);
+ for (int i = 0; i < length; i++) {
+ data.writeString(projection[i]);
+ }
+ data.writeBundle(queryArgs);
+ data.writeStrongBinder(adaptor.getObserver().asBinder());
+ data.writeStrongBinder(
+ cancellationSignal != null ? cancellationSignal.asBinder() : null);
+
+ mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+
+ if (reply.readInt() != 0) {
+ BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply);
+ Binder.copyAllowBlocking(mRemote, (d.cursor != null) ? d.cursor.asBinder() : null);
+ adaptor.initialize(d);
+ } else {
+ adaptor.close();
+ adaptor = null;
+ }
+ return adaptor;
+ } catch (RemoteException ex) {
+ adaptor.close();
+ throw ex;
+ } catch (RuntimeException ex) {
+ adaptor.close();
+ throw ex;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public String getType(Uri url) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String out = reply.readString();
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ /* oneway */ public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ uri.writeToParcel(data, 0);
+ callback.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.GET_TYPE_ASYNC_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ public Uri insert(@NonNull AttributionSource attributionSource, Uri url,
+ ContentValues values, Bundle extras) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ values.writeToParcel(data, 0);
+ data.writeBundle(extras);
+
+ mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Uri out = Uri.CREATOR.createFromParcel(reply);
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
+ ContentValues[] values) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeTypedArray(values, 0);
+
+ mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
+ String authority, ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+ attributionSource.writeToParcel(data, 0);
+ data.writeString(authority);
+ data.writeInt(operations.size());
+ for (ContentProviderOperation operation : operations) {
+ operation.writeToParcel(data, 0);
+ }
+ mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply);
+ final ContentProviderResult[] results =
+ reply.createTypedArray(ContentProviderResult.CREATOR);
+ return results;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeBundle(extras);
+
+ mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public int update(@NonNull AttributionSource attributionSource, Uri url,
+ ContentValues values, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ values.writeToParcel(data, 0);
+ data.writeBundle(extras);
+
+ mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource, Uri url,
+ String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+
+ mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ ParcelFileDescriptor fd = has != 0 ? ParcelFileDescriptor.CREATOR
+ .createFromParcel(reply) : null;
+ return fd;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+
+ mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+ return fd;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public Bundle call(@NonNull AttributionSource attributionSource, String authority,
+ String method, String request, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ data.writeString(authority);
+ data.writeString(method);
+ data.writeString(request);
+ data.writeBundle(extras);
+
+ mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Bundle bundle = reply.readBundle();
+ return bundle;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mimeTypeFilter);
+
+ mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String[] out = reply.createStringArray();
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeString(mimeType);
+ data.writeBundle(opts);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+
+ mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+ return fd;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public ICancellationSignal createCancellationSignal() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ mRemote.transact(IContentProvider.CREATE_CANCELATION_SIGNAL_TRANSACTION,
+ data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
+ reply.readStrongBinder());
+ return cancellationSignal;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri url)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Uri out = Uri.CREATOR.createFromParcel(reply);
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ /* oneway */ public void canonicalizeAsync(@NonNull AttributionSource attributionSource,
+ Uri uri, RemoteCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ uri.writeToParcel(data, 0);
+ callback.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.CANONICALIZE_ASYNC_TRANSACTION, data, null,
+ Binder.FLAG_ONEWAY);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri url)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Uri out = Uri.CREATOR.createFromParcel(reply);
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ /* oneway */ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource,
+ Uri uri, RemoteCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ uri.writeToParcel(data, 0);
+ callback.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.UNCANONICALIZE_ASYNC_TRANSACTION, data, null,
+ Binder.FLAG_ONEWAY);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri url, Bundle extras,
+ ICancellationSignal signal) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeBundle(extras);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+
+ mRemote.transact(IContentProvider.REFRESH_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int success = reply.readInt();
+ return (success == 0);
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @Override
+ public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri url, int uid,
+ int modeFlags) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ attributionSource.writeToParcel(data, 0);
+ url.writeToParcel(data, 0);
+ data.writeInt(uid);
+ data.writeInt(modeFlags);
+
+ mRemote.transact(IContentProvider.CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ return reply.readInt();
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ @UnsupportedAppUsage
+ private IBinder mRemote;
+}
diff --git a/android/content/ContentProviderOperation.java b/android/content/ContentProviderOperation.java
new file mode 100644
index 0000000..30775b1
--- /dev/null
+++ b/android/content/ContentProviderOperation.java
@@ -0,0 +1,1039 @@
+/*
+ * Copyright (C) 2009 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.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Represents a single operation to be performed as part of a batch of operations.
+ *
+ * @see ContentProvider#applyBatch(ArrayList)
+ */
+public class ContentProviderOperation implements Parcelable {
+ /** @hide exposed for unit tests */
+ @UnsupportedAppUsage
+ public final static int TYPE_INSERT = 1;
+ /** @hide exposed for unit tests */
+ @UnsupportedAppUsage
+ public final static int TYPE_UPDATE = 2;
+ /** @hide exposed for unit tests */
+ @UnsupportedAppUsage
+ public final static int TYPE_DELETE = 3;
+ /** @hide exposed for unit tests */
+ public final static int TYPE_ASSERT = 4;
+ /** @hide exposed for unit tests */
+ public final static int TYPE_CALL = 5;
+
+ @UnsupportedAppUsage
+ private final int mType;
+ @UnsupportedAppUsage
+ private final Uri mUri;
+ private final String mMethod;
+ private final String mArg;
+ private final ArrayMap<String, Object> mValues;
+ private final ArrayMap<String, Object> mExtras;
+ @UnsupportedAppUsage
+ private final String mSelection;
+ private final SparseArray<Object> mSelectionArgs;
+ private final Integer mExpectedCount;
+ private final boolean mYieldAllowed;
+ private final boolean mExceptionAllowed;
+
+ private final static String TAG = "ContentProviderOperation";
+
+ /**
+ * Creates a {@link ContentProviderOperation} by copying the contents of a
+ * {@link Builder}.
+ */
+ private ContentProviderOperation(Builder builder) {
+ mType = builder.mType;
+ mUri = builder.mUri;
+ mMethod = builder.mMethod;
+ mArg = builder.mArg;
+ mValues = builder.mValues;
+ mExtras = builder.mExtras;
+ mSelection = builder.mSelection;
+ mSelectionArgs = builder.mSelectionArgs;
+ mExpectedCount = builder.mExpectedCount;
+ mYieldAllowed = builder.mYieldAllowed;
+ mExceptionAllowed = builder.mExceptionAllowed;
+ }
+
+ private ContentProviderOperation(Parcel source) {
+ mType = source.readInt();
+ mUri = Uri.CREATOR.createFromParcel(source);
+ mMethod = source.readInt() != 0 ? source.readString8() : null;
+ mArg = source.readInt() != 0 ? source.readString8() : null;
+ final int valuesSize = source.readInt();
+ if (valuesSize != -1) {
+ mValues = new ArrayMap<>(valuesSize);
+ source.readArrayMap(mValues, null);
+ } else {
+ mValues = null;
+ }
+ final int extrasSize = source.readInt();
+ if (extrasSize != -1) {
+ mExtras = new ArrayMap<>(extrasSize);
+ source.readArrayMap(mExtras, null);
+ } else {
+ mExtras = null;
+ }
+ mSelection = source.readInt() != 0 ? source.readString8() : null;
+ mSelectionArgs = source.readSparseArray(null);
+ mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
+ mYieldAllowed = source.readInt() != 0;
+ mExceptionAllowed = source.readInt() != 0;
+ }
+
+ /** @hide */
+ public ContentProviderOperation(ContentProviderOperation cpo, Uri withUri) {
+ mType = cpo.mType;
+ mUri = withUri;
+ mMethod = cpo.mMethod;
+ mArg = cpo.mArg;
+ mValues = cpo.mValues;
+ mExtras = cpo.mExtras;
+ mSelection = cpo.mSelection;
+ mSelectionArgs = cpo.mSelectionArgs;
+ mExpectedCount = cpo.mExpectedCount;
+ mYieldAllowed = cpo.mYieldAllowed;
+ mExceptionAllowed = cpo.mExceptionAllowed;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ Uri.writeToParcel(dest, mUri);
+ if (mMethod != null) {
+ dest.writeInt(1);
+ dest.writeString8(mMethod);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mArg != null) {
+ dest.writeInt(1);
+ dest.writeString8(mArg);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mValues != null) {
+ dest.writeInt(mValues.size());
+ dest.writeArrayMap(mValues);
+ } else {
+ dest.writeInt(-1);
+ }
+ if (mExtras != null) {
+ dest.writeInt(mExtras.size());
+ dest.writeArrayMap(mExtras);
+ } else {
+ dest.writeInt(-1);
+ }
+ if (mSelection != null) {
+ dest.writeInt(1);
+ dest.writeString8(mSelection);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeSparseArray(mSelectionArgs);
+ if (mExpectedCount != null) {
+ dest.writeInt(1);
+ dest.writeInt(mExpectedCount);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mYieldAllowed ? 1 : 0);
+ dest.writeInt(mExceptionAllowed ? 1 : 0);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building an operation that will
+ * invoke {@link ContentProvider#insert}.
+ *
+ * @param uri The {@link Uri} that is the target of the operation.
+ */
+ public static @NonNull Builder newInsert(@NonNull Uri uri) {
+ return new Builder(TYPE_INSERT, uri);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building an operation that will
+ * invoke {@link ContentProvider#update}.
+ *
+ * @param uri The {@link Uri} that is the target of the operation.
+ */
+ public static @NonNull Builder newUpdate(@NonNull Uri uri) {
+ return new Builder(TYPE_UPDATE, uri);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building an operation that will
+ * invoke {@link ContentProvider#delete}.
+ *
+ * @param uri The {@link Uri} that is the target of the operation.
+ */
+ public static @NonNull Builder newDelete(@NonNull Uri uri) {
+ return new Builder(TYPE_DELETE, uri);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building a
+ * {@link ContentProviderOperation} to assert a set of values as provided
+ * through {@link Builder#withValues(ContentValues)}.
+ */
+ public static @NonNull Builder newAssertQuery(@NonNull Uri uri) {
+ return new Builder(TYPE_ASSERT, uri);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building an operation that will
+ * invoke {@link ContentProvider#call}.
+ *
+ * @param uri The {@link Uri} that is the target of the operation.
+ */
+ public static @NonNull Builder newCall(@NonNull Uri uri, @Nullable String method,
+ @Nullable String arg) {
+ return new Builder(TYPE_CALL, uri, method, arg);
+ }
+
+ /**
+ * Gets the Uri for the target of the operation.
+ */
+ public @NonNull Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Returns true if the operation allows yielding the database to other transactions
+ * if the database is contended.
+ *
+ * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely()
+ */
+ public boolean isYieldAllowed() {
+ return mYieldAllowed;
+ }
+
+ /**
+ * Returns true if this operation allows subsequent operations to continue
+ * even if this operation throws an exception. When true, any encountered
+ * exception is returned via {@link ContentProviderResult#exception}.
+ */
+ public boolean isExceptionAllowed() {
+ return mExceptionAllowed;
+ }
+
+ /** @hide exposed for unit tests */
+ @UnsupportedAppUsage
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns true if the operation represents a {@link ContentProvider#insert}
+ * operation.
+ *
+ * @see #newInsert
+ */
+ public boolean isInsert() {
+ return mType == TYPE_INSERT;
+ }
+
+ /**
+ * Returns true if the operation represents a {@link ContentProvider#delete}
+ * operation.
+ *
+ * @see #newDelete
+ */
+ public boolean isDelete() {
+ return mType == TYPE_DELETE;
+ }
+
+ /**
+ * Returns true if the operation represents a {@link ContentProvider#update}
+ * operation.
+ *
+ * @see #newUpdate
+ */
+ public boolean isUpdate() {
+ return mType == TYPE_UPDATE;
+ }
+
+ /**
+ * Returns true if the operation represents an assert query.
+ *
+ * @see #newAssertQuery
+ */
+ public boolean isAssertQuery() {
+ return mType == TYPE_ASSERT;
+ }
+
+ /**
+ * Returns true if the operation represents a {@link ContentProvider#call}
+ * operation.
+ *
+ * @see #newCall
+ */
+ public boolean isCall() {
+ return mType == TYPE_CALL;
+ }
+
+ /**
+ * Returns true if the operation represents an insertion, deletion, or update.
+ *
+ * @see #isInsert
+ * @see #isDelete
+ * @see #isUpdate
+ */
+ public boolean isWriteOperation() {
+ return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE;
+ }
+
+ /**
+ * Returns true if the operation represents an assert query.
+ *
+ * @see #isAssertQuery
+ */
+ public boolean isReadOperation() {
+ return mType == TYPE_ASSERT;
+ }
+
+ /**
+ * Applies this operation using the given provider. The backRefs array is used to resolve any
+ * back references that were requested using
+ * {@link Builder#withValueBackReferences(ContentValues)} and
+ * {@link Builder#withSelectionBackReference}.
+ * @param provider the {@link ContentProvider} on which this batch is applied
+ * @param backRefs a {@link ContentProviderResult} array that will be consulted
+ * to resolve any requested back references.
+ * @param numBackRefs the number of valid results on the backRefs array.
+ * @return a {@link ContentProviderResult} that contains either the {@link Uri} of the inserted
+ * row if this was an insert otherwise the number of rows affected.
+ * @throws OperationApplicationException thrown if either the insert fails or
+ * if the number of rows affected didn't match the expected count
+ */
+ public @NonNull ContentProviderResult apply(@NonNull ContentProvider provider,
+ @NonNull ContentProviderResult[] backRefs, int numBackRefs)
+ throws OperationApplicationException {
+ if (mExceptionAllowed) {
+ try {
+ return applyInternal(provider, backRefs, numBackRefs);
+ } catch (Exception e) {
+ return new ContentProviderResult(e);
+ }
+ } else {
+ return applyInternal(provider, backRefs, numBackRefs);
+ }
+ }
+
+ private ContentProviderResult applyInternal(ContentProvider provider,
+ ContentProviderResult[] backRefs, int numBackRefs)
+ throws OperationApplicationException {
+ final ContentValues values = resolveValueBackReferences(backRefs, numBackRefs);
+
+ // If the creator requested explicit selection or selectionArgs, it
+ // should take precedence over similar values they defined in extras
+ Bundle extras = resolveExtrasBackReferences(backRefs, numBackRefs);
+ if (mSelection != null) {
+ extras = (extras != null) ? extras : new Bundle();
+ extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, mSelection);
+ }
+ if (mSelectionArgs != null) {
+ extras = (extras != null) ? extras : new Bundle();
+ extras.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
+ resolveSelectionArgsBackReferences(backRefs, numBackRefs));
+ }
+
+ if (mType == TYPE_INSERT) {
+ final Uri newUri = provider.insert(mUri, values, extras);
+ if (newUri != null) {
+ return new ContentProviderResult(newUri);
+ } else {
+ throw new OperationApplicationException(
+ "Insert into " + mUri + " returned no result");
+ }
+ } else if (mType == TYPE_CALL) {
+ final Bundle res = provider.call(mUri.getAuthority(), mMethod, mArg, extras);
+ return new ContentProviderResult(res);
+ }
+
+ final int numRows;
+ if (mType == TYPE_DELETE) {
+ numRows = provider.delete(mUri, extras);
+ } else if (mType == TYPE_UPDATE) {
+ numRows = provider.update(mUri, values, extras);
+ } else if (mType == TYPE_ASSERT) {
+ // Assert that all rows match expected values
+ String[] projection = null;
+ if (values != null) {
+ // Build projection map from expected values
+ final ArrayList<String> projectionList = new ArrayList<String>();
+ for (Map.Entry<String, Object> entry : values.valueSet()) {
+ projectionList.add(entry.getKey());
+ }
+ projection = projectionList.toArray(new String[projectionList.size()]);
+ }
+ final Cursor cursor = provider.query(mUri, projection, extras, null);
+ try {
+ numRows = cursor.getCount();
+ if (projection != null) {
+ while (cursor.moveToNext()) {
+ for (int i = 0; i < projection.length; i++) {
+ final String cursorValue = cursor.getString(i);
+ final String expectedValue = values.getAsString(projection[i]);
+ if (!TextUtils.equals(cursorValue, expectedValue)) {
+ // Throw exception when expected values don't match
+ throw new OperationApplicationException("Found value " + cursorValue
+ + " when expected " + expectedValue + " for column "
+ + projection[i]);
+ }
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ } else {
+ throw new IllegalStateException("bad type, " + mType);
+ }
+
+ if (mExpectedCount != null && mExpectedCount != numRows) {
+ throw new OperationApplicationException(
+ "Expected " + mExpectedCount + " rows but actual " + numRows);
+ }
+
+ return new ContentProviderResult(numRows);
+ }
+
+ /**
+ * Return the values for this operation after resolving any requested
+ * back-references using the given results.
+ *
+ * @param backRefs the results to use when resolving any back-references
+ * @param numBackRefs the number of results which are valid
+ */
+ public @Nullable ContentValues resolveValueBackReferences(
+ @NonNull ContentProviderResult[] backRefs, int numBackRefs) {
+ if (mValues != null) {
+ final ContentValues values = new ContentValues();
+ for (int i = 0; i < mValues.size(); i++) {
+ final Object value = mValues.valueAt(i);
+ final Object resolved;
+ if (value instanceof BackReference) {
+ resolved = ((BackReference) value).resolve(backRefs, numBackRefs);
+ } else {
+ resolved = value;
+ }
+ values.putObject(mValues.keyAt(i), resolved);
+ }
+ return values;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the extras for this operation after resolving any requested
+ * back-references using the given results.
+ *
+ * @param backRefs the results to use when resolving any back-references
+ * @param numBackRefs the number of results which are valid
+ */
+ public @Nullable Bundle resolveExtrasBackReferences(
+ @NonNull ContentProviderResult[] backRefs, int numBackRefs) {
+ if (mExtras != null) {
+ final Bundle extras = new Bundle();
+ for (int i = 0; i < mExtras.size(); i++) {
+ final Object value = mExtras.valueAt(i);
+ final Object resolved;
+ if (value instanceof BackReference) {
+ resolved = ((BackReference) value).resolve(backRefs, numBackRefs);
+ } else {
+ resolved = value;
+ }
+ extras.putObject(mExtras.keyAt(i), resolved);
+ }
+ return extras;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the selection arguments for this operation after resolving any
+ * requested back-references using the given results.
+ *
+ * @param backRefs the results to use when resolving any back-references
+ * @param numBackRefs the number of results which are valid
+ */
+ public @Nullable String[] resolveSelectionArgsBackReferences(
+ @NonNull ContentProviderResult[] backRefs, int numBackRefs) {
+ if (mSelectionArgs != null) {
+ int max = -1;
+ for (int i = 0; i < mSelectionArgs.size(); i++) {
+ max = Math.max(max, mSelectionArgs.keyAt(i));
+ }
+
+ final String[] selectionArgs = new String[max + 1];
+ for (int i = 0; i < mSelectionArgs.size(); i++) {
+ final Object value = mSelectionArgs.valueAt(i);
+ final Object resolved;
+ if (value instanceof BackReference) {
+ resolved = ((BackReference) value).resolve(backRefs, numBackRefs);
+ } else {
+ resolved = value;
+ }
+ selectionArgs[mSelectionArgs.keyAt(i)] = String.valueOf(resolved);
+ }
+ return selectionArgs;
+ } else {
+ return null;
+ }
+ }
+
+ /** {@hide} */
+ public static String typeToString(int type) {
+ switch (type) {
+ case TYPE_INSERT: return "insert";
+ case TYPE_UPDATE: return "update";
+ case TYPE_DELETE: return "delete";
+ case TYPE_ASSERT: return "assert";
+ case TYPE_CALL: return "call";
+ default: return Integer.toString(type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ContentProviderOperation(");
+ sb.append("type=").append(typeToString(mType)).append(' ');
+ if (mUri != null) {
+ sb.append("uri=").append(mUri).append(' ');
+ }
+ if (mValues != null) {
+ sb.append("values=").append(mValues).append(' ');
+ }
+ if (mSelection != null) {
+ sb.append("selection=").append(mSelection).append(' ');
+ }
+ if (mSelectionArgs != null) {
+ sb.append("selectionArgs=").append(mSelectionArgs).append(' ');
+ }
+ if (mExpectedCount != null) {
+ sb.append("expectedCount=").append(mExpectedCount).append(' ');
+ }
+ if (mYieldAllowed) {
+ sb.append("yieldAllowed ");
+ }
+ if (mExceptionAllowed) {
+ sb.append("exceptionAllowed ");
+ }
+ sb.deleteCharAt(sb.length() - 1);
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Creator<ContentProviderOperation> CREATOR =
+ new Creator<ContentProviderOperation>() {
+ @Override
+ public ContentProviderOperation createFromParcel(Parcel source) {
+ return new ContentProviderOperation(source);
+ }
+
+ @Override
+ public ContentProviderOperation[] newArray(int size) {
+ return new ContentProviderOperation[size];
+ }
+ };
+
+ /** {@hide} */
+ public static class BackReference implements Parcelable {
+ private final int fromIndex;
+ private final String fromKey;
+
+ private BackReference(int fromIndex, String fromKey) {
+ this.fromIndex = fromIndex;
+ this.fromKey = fromKey;
+ }
+
+ public BackReference(Parcel src) {
+ this.fromIndex = src.readInt();
+ if (src.readInt() != 0) {
+ this.fromKey = src.readString8();
+ } else {
+ this.fromKey = null;
+ }
+ }
+
+ public Object resolve(ContentProviderResult[] backRefs, int numBackRefs) {
+ if (fromIndex >= numBackRefs) {
+ Log.e(TAG, this.toString());
+ throw new ArrayIndexOutOfBoundsException("asked for back ref " + fromIndex
+ + " but there are only " + numBackRefs + " back refs");
+ }
+ ContentProviderResult backRef = backRefs[fromIndex];
+ Object backRefValue;
+ if (backRef.extras != null) {
+ backRefValue = backRef.extras.get(fromKey);
+ } else if (backRef.uri != null) {
+ backRefValue = ContentUris.parseId(backRef.uri);
+ } else {
+ backRefValue = (long) backRef.count;
+ }
+ return backRefValue;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(fromIndex);
+ if (fromKey != null) {
+ dest.writeInt(1);
+ dest.writeString8(fromKey);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Creator<BackReference> CREATOR =
+ new Creator<BackReference>() {
+ @Override
+ public BackReference createFromParcel(Parcel source) {
+ return new BackReference(source);
+ }
+
+ @Override
+ public BackReference[] newArray(int size) {
+ return new BackReference[size];
+ }
+ };
+ }
+
+ /**
+ * Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is
+ * first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)},
+ * {@link ContentProviderOperation#newUpdate(android.net.Uri)},
+ * {@link ContentProviderOperation#newDelete(android.net.Uri)} or
+ * {@link ContentProviderOperation#newAssertQuery(Uri)}. The withXXX methods
+ * can then be used to add parameters to the builder. See the specific methods to find for
+ * which {@link Builder} type each is allowed. Call {@link #build} to create the
+ * {@link ContentProviderOperation} once all the parameters have been supplied.
+ */
+ public static class Builder {
+ private final int mType;
+ private final Uri mUri;
+ private final String mMethod;
+ private final String mArg;
+ private ArrayMap<String, Object> mValues;
+ private ArrayMap<String, Object> mExtras;
+ private String mSelection;
+ private SparseArray<Object> mSelectionArgs;
+ private Integer mExpectedCount;
+ private boolean mYieldAllowed;
+ private boolean mExceptionAllowed;
+
+ private Builder(int type, Uri uri) {
+ this(type, uri, null, null);
+ }
+
+ private Builder(int type, Uri uri, String method, String arg) {
+ mType = type;
+ mUri = Objects.requireNonNull(uri);
+ mMethod = method;
+ mArg = arg;
+ }
+
+ /** Create a ContentProviderOperation from this {@link Builder}. */
+ public @NonNull ContentProviderOperation build() {
+ if (mType == TYPE_UPDATE) {
+ if ((mValues == null || mValues.isEmpty())) {
+ throw new IllegalArgumentException("Empty values");
+ }
+ }
+ if (mType == TYPE_ASSERT) {
+ if ((mValues == null || mValues.isEmpty())
+ && (mExpectedCount == null)) {
+ throw new IllegalArgumentException("Empty values");
+ }
+ }
+ return new ContentProviderOperation(this);
+ }
+
+ private void ensureValues() {
+ if (mValues == null) {
+ mValues = new ArrayMap<>();
+ }
+ }
+
+ private void ensureExtras() {
+ if (mExtras == null) {
+ mExtras = new ArrayMap<>();
+ }
+ }
+
+ private void ensureSelectionArgs() {
+ if (mSelectionArgs == null) {
+ mSelectionArgs = new SparseArray<>();
+ }
+ }
+
+ private void setValue(@NonNull String key, @NonNull Object value) {
+ ensureValues();
+ final boolean oldReference = mValues.get(key) instanceof BackReference;
+ final boolean newReference = value instanceof BackReference;
+ if (!oldReference || newReference) {
+ mValues.put(key, value);
+ }
+ }
+
+ private void setExtra(@NonNull String key, @NonNull Object value) {
+ ensureExtras();
+ final boolean oldReference = mExtras.get(key) instanceof BackReference;
+ final boolean newReference = value instanceof BackReference;
+ if (!oldReference || newReference) {
+ mExtras.put(key, value);
+ }
+ }
+
+ private void setSelectionArg(int index, @NonNull Object value) {
+ ensureSelectionArgs();
+ final boolean oldReference = mSelectionArgs.get(index) instanceof BackReference;
+ final boolean newReference = value instanceof BackReference;
+ if (!oldReference || newReference) {
+ mSelectionArgs.put(index, value);
+ }
+ }
+
+ /**
+ * Configure the values to use for this operation. This method will
+ * replace any previously defined values for the contained keys, but it
+ * will not replace any back-reference requests.
+ * <p>
+ * Any value may be dynamically overwritten using the result of a
+ * previous operation by using methods such as
+ * {@link #withValueBackReference(String, int)}.
+ */
+ public @NonNull Builder withValues(@NonNull ContentValues values) {
+ assertValuesAllowed();
+ ensureValues();
+ final ArrayMap<String, Object> rawValues = values.getValues();
+ for (int i = 0; i < rawValues.size(); i++) {
+ setValue(rawValues.keyAt(i), rawValues.valueAt(i));
+ }
+ return this;
+ }
+
+ /**
+ * Configure the given value to use for this operation. This method will
+ * replace any previously defined value for this key.
+ *
+ * @param key the key indicating which value to configure
+ */
+ public @NonNull Builder withValue(@NonNull String key, @Nullable Object value) {
+ assertValuesAllowed();
+ if (!ContentValues.isSupportedValue(value)) {
+ throw new IllegalArgumentException("bad value type: " + value.getClass().getName());
+ }
+ setValue(key, value);
+ return this;
+ }
+
+ /**
+ * Configure the given values to be dynamically overwritten using the
+ * result of a previous operation. This method will replace any
+ * previously defined values for these keys.
+ *
+ * @param backReferences set of values where the key indicates which
+ * value to configure and the value the index indicating
+ * which historical {@link ContentProviderResult} should
+ * overwrite the value
+ */
+ public @NonNull Builder withValueBackReferences(@NonNull ContentValues backReferences) {
+ assertValuesAllowed();
+ final ArrayMap<String, Object> rawValues = backReferences.getValues();
+ for (int i = 0; i < rawValues.size(); i++) {
+ setValue(rawValues.keyAt(i),
+ new BackReference((int) rawValues.valueAt(i), null));
+ }
+ return this;
+ }
+
+ /**
+ * Configure the given value to be dynamically overwritten using the
+ * result of a previous operation. This method will replace any
+ * previously defined value for this key.
+ *
+ * @param key the key indicating which value to configure
+ * @param fromIndex the index indicating which historical
+ * {@link ContentProviderResult} should overwrite the value
+ */
+ public @NonNull Builder withValueBackReference(@NonNull String key, int fromIndex) {
+ assertValuesAllowed();
+ setValue(key, new BackReference(fromIndex, null));
+ return this;
+ }
+
+ /**
+ * Configure the given value to be dynamically overwritten using the
+ * result of a previous operation. This method will replace any
+ * previously defined value for this key.
+ *
+ * @param key the key indicating which value to configure
+ * @param fromIndex the index indicating which historical
+ * {@link ContentProviderResult} should overwrite the value
+ * @param fromKey the key of indicating which
+ * {@link ContentProviderResult#extras} value should
+ * overwrite the value
+ */
+ public @NonNull Builder withValueBackReference(@NonNull String key, int fromIndex,
+ @NonNull String fromKey) {
+ assertValuesAllowed();
+ setValue(key, new BackReference(fromIndex, fromKey));
+ return this;
+ }
+
+ /**
+ * Configure the extras to use for this operation. This method will
+ * replace any previously defined values for the contained keys, but it
+ * will not replace any back-reference requests.
+ * <p>
+ * Any value may be dynamically overwritten using the result of a
+ * previous operation by using methods such as
+ * {@link #withExtraBackReference(String, int)}.
+ */
+ public @NonNull Builder withExtras(@NonNull Bundle extras) {
+ assertExtrasAllowed();
+ ensureExtras();
+ for (String key : extras.keySet()) {
+ setExtra(key, extras.get(key));
+ }
+ return this;
+ }
+
+ /**
+ * Configure the given extra to use for this operation. This method will
+ * replace any previously defined extras for this key.
+ *
+ * @param key the key indicating which extra to configure
+ */
+ public @NonNull Builder withExtra(@NonNull String key, @Nullable Object value) {
+ assertExtrasAllowed();
+ setExtra(key, value);
+ return this;
+ }
+
+ /**
+ * Configure the given extra to be dynamically overwritten using the
+ * result of a previous operation. This method will replace any
+ * previously defined extras for this key.
+ *
+ * @param key the key indicating which extra to configure
+ * @param fromIndex the index indicating which historical
+ * {@link ContentProviderResult} should overwrite the extra
+ */
+ public @NonNull Builder withExtraBackReference(@NonNull String key, int fromIndex) {
+ assertExtrasAllowed();
+ setExtra(key, new BackReference(fromIndex, null));
+ return this;
+ }
+
+ /**
+ * Configure the given extra to be dynamically overwritten using the
+ * result of a previous operation. This method will replace any
+ * previously defined extras for this key.
+ *
+ * @param key the key indicating which extra to configure
+ * @param fromIndex the index indicating which historical
+ * {@link ContentProviderResult} should overwrite the extra
+ * @param fromKey the key of indicating which
+ * {@link ContentProviderResult#extras} value should
+ * overwrite the extra
+ */
+ public @NonNull Builder withExtraBackReference(@NonNull String key, int fromIndex,
+ @NonNull String fromKey) {
+ assertExtrasAllowed();
+ setExtra(key, new BackReference(fromIndex, fromKey));
+ return this;
+ }
+
+ /**
+ * Configure the selection and selection arguments to use for this
+ * operation. This method will replace any previously defined selection
+ * and selection arguments, but it will not replace any back-reference
+ * requests.
+ * <p>
+ * An occurrence of {@code ?} in the selection will be replaced with the
+ * corresponding selection argument when the operation is executed.
+ * <p>
+ * Any selection argument may be dynamically overwritten using the
+ * result of a previous operation by using methods such as
+ * {@link #withSelectionBackReference(int, int)}.
+ */
+ public @NonNull Builder withSelection(@Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ assertSelectionAllowed();
+ mSelection = selection;
+ if (selectionArgs != null) {
+ ensureSelectionArgs();
+ for (int i = 0; i < selectionArgs.length; i++) {
+ setSelectionArg(i, selectionArgs[i]);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Configure the given selection argument to be dynamically overwritten
+ * using the result of a previous operation. This method will replace
+ * any previously defined selection argument at this index.
+ *
+ * @param index the index indicating which selection argument to
+ * configure
+ * @param fromIndex the index indicating which historical
+ * {@link ContentProviderResult} should overwrite the
+ * selection argument
+ */
+ public @NonNull Builder withSelectionBackReference(int index, int fromIndex) {
+ assertSelectionAllowed();
+ setSelectionArg(index, new BackReference(fromIndex, null));
+ return this;
+ }
+
+ /**
+ * Configure the given selection argument to be dynamically overwritten
+ * using the result of a previous operation. This method will replace
+ * any previously defined selection argument at this index.
+ *
+ * @param index the index indicating which selection argument to
+ * configure
+ * @param fromIndex the index indicating which historical
+ * {@link ContentProviderResult} should overwrite the
+ * selection argument
+ * @param fromKey the key of indicating which
+ * {@link ContentProviderResult#extras} value should
+ * overwrite the selection argument
+ */
+ public @NonNull Builder withSelectionBackReference(int index, int fromIndex,
+ @NonNull String fromKey) {
+ assertSelectionAllowed();
+ setSelectionArg(index, new BackReference(fromIndex, fromKey));
+ return this;
+ }
+
+ /**
+ * If set then if the number of rows affected by this operation does not match
+ * this count {@link OperationApplicationException} will be throw.
+ * This can only be used with builders of type update, delete, or assert.
+ * @return this builder, to allow for chaining.
+ */
+ public @NonNull Builder withExpectedCount(int count) {
+ if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException(
+ "only updates, deletes, and asserts can have expected counts");
+ }
+ mExpectedCount = count;
+ return this;
+ }
+
+ /**
+ * If set to true then the operation allows yielding the database to other transactions
+ * if the database is contended.
+ * @return this builder, to allow for chaining.
+ * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely()
+ */
+ public @NonNull Builder withYieldAllowed(boolean yieldAllowed) {
+ mYieldAllowed = yieldAllowed;
+ return this;
+ }
+
+ /**
+ * If set to true, this operation allows subsequent operations to
+ * continue even if this operation throws an exception. When true, any
+ * encountered exception is returned via
+ * {@link ContentProviderResult#exception}.
+ */
+ public @NonNull Builder withExceptionAllowed(boolean exceptionAllowed) {
+ mExceptionAllowed = exceptionAllowed;
+ return this;
+ }
+
+ /** {@hide} */
+ public @NonNull Builder withFailureAllowed(boolean failureAllowed) {
+ return withExceptionAllowed(failureAllowed);
+ }
+
+ private void assertValuesAllowed() {
+ switch (mType) {
+ case TYPE_INSERT:
+ case TYPE_UPDATE:
+ case TYPE_ASSERT:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Values not supported for " + typeToString(mType));
+ }
+ }
+
+ private void assertSelectionAllowed() {
+ switch (mType) {
+ case TYPE_UPDATE:
+ case TYPE_DELETE:
+ case TYPE_ASSERT:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Selection not supported for " + typeToString(mType));
+ }
+ }
+
+ private void assertExtrasAllowed() {
+ switch (mType) {
+ case TYPE_INSERT:
+ case TYPE_UPDATE:
+ case TYPE_DELETE:
+ case TYPE_ASSERT:
+ case TYPE_CALL:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Extras not supported for " + typeToString(mType));
+ }
+ }
+ }
+}
diff --git a/android/content/ContentProviderResult.java b/android/content/ContentProviderResult.java
new file mode 100644
index 0000000..fdfe7eb
--- /dev/null
+++ b/android/content/ContentProviderResult.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2009 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.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableException;
+
+import java.util.Objects;
+
+/**
+ * Contains the result of the application of a {@link ContentProviderOperation}.
+ * <p>
+ * It is guaranteed to have exactly one of {@link #uri}, {@link #count},
+ * {@link #extras}, or {@link #exception} set.
+ */
+public class ContentProviderResult implements Parcelable {
+ public final @Nullable Uri uri;
+ public final @Nullable Integer count;
+ public final @Nullable Bundle extras;
+ public final @Nullable Throwable exception;
+
+ public ContentProviderResult(@NonNull Uri uri) {
+ this(Objects.requireNonNull(uri), null, null, null);
+ }
+
+ public ContentProviderResult(int count) {
+ this(null, count, null, null);
+ }
+
+ public ContentProviderResult(@NonNull Bundle extras) {
+ this(null, null, Objects.requireNonNull(extras), null);
+ }
+
+ public ContentProviderResult(@NonNull Throwable exception) {
+ this(null, null, null, exception);
+ }
+
+ /** {@hide} */
+ public ContentProviderResult(Uri uri, Integer count, Bundle extras, Throwable exception) {
+ this.uri = uri;
+ this.count = count;
+ this.extras = extras;
+ this.exception = exception;
+ }
+
+ public ContentProviderResult(Parcel source) {
+ if (source.readInt() != 0) {
+ uri = Uri.CREATOR.createFromParcel(source);
+ } else {
+ uri = null;
+ }
+ if (source.readInt() != 0) {
+ count = source.readInt();
+ } else {
+ count = null;
+ }
+ if (source.readInt() != 0) {
+ extras = source.readBundle();
+ } else {
+ extras = null;
+ }
+ if (source.readInt() != 0) {
+ exception = ParcelableException.readFromParcel(source);
+ } else {
+ exception = null;
+ }
+ }
+
+ /** @hide */
+ public ContentProviderResult(ContentProviderResult cpr, int userId) {
+ uri = ContentProvider.maybeAddUserId(cpr.uri, userId);
+ count = cpr.count;
+ extras = cpr.extras;
+ exception = cpr.exception;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (uri != null) {
+ dest.writeInt(1);
+ uri.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (count != null) {
+ dest.writeInt(1);
+ dest.writeInt(count);
+ } else {
+ dest.writeInt(0);
+ }
+ if (extras != null) {
+ dest.writeInt(1);
+ dest.writeBundle(extras);
+ } else {
+ dest.writeInt(0);
+ }
+ if (exception != null) {
+ dest.writeInt(1);
+ ParcelableException.writeToParcel(dest, exception);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Creator<ContentProviderResult> CREATOR =
+ new Creator<ContentProviderResult>() {
+ @Override
+ public ContentProviderResult createFromParcel(Parcel source) {
+ return new ContentProviderResult(source);
+ }
+
+ @Override
+ public ContentProviderResult[] newArray(int size) {
+ return new ContentProviderResult[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ContentProviderResult(");
+ if (uri != null) {
+ sb.append("uri=").append(uri).append(' ');
+ }
+ if (count != null) {
+ sb.append("count=").append(count).append(' ');
+ }
+ if (extras != null) {
+ sb.append("extras=").append(extras).append(' ');
+ }
+ if (exception != null) {
+ sb.append("exception=").append(exception).append(' ');
+ }
+ sb.deleteCharAt(sb.length() - 1);
+ sb.append(")");
+ return sb.toString();
+ }
+}
diff --git a/android/content/ContentQueryMap.java b/android/content/ContentQueryMap.java
new file mode 100644
index 0000000..8aeaa8f
--- /dev/null
+++ b/android/content/ContentQueryMap.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2007 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.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Handler;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Observable;
+
+/**
+ * Caches the contents of a cursor into a Map of String->ContentValues and optionally
+ * keeps the cache fresh by registering for updates on the content backing the cursor. The column of
+ * the database that is to be used as the key of the map is user-configurable, and the
+ * ContentValues contains all columns other than the one that is designated the key.
+ * <p>
+ * The cursor data is accessed by row key and column name via getValue().
+ */
+public class ContentQueryMap extends Observable {
+ private volatile Cursor mCursor;
+ private String[] mColumnNames;
+ private int mKeyColumn;
+
+ private Handler mHandlerForUpdateNotifications = null;
+ private boolean mKeepUpdated = false;
+
+ private Map<String, ContentValues> mValues = null;
+
+ private ContentObserver mContentObserver;
+
+ /** Set when a cursor change notification is received and is cleared on a call to requery(). */
+ private boolean mDirty = false;
+
+ /**
+ * Creates a ContentQueryMap that caches the content backing the cursor
+ *
+ * @param cursor the cursor whose contents should be cached
+ * @param columnNameOfKey the column that is to be used as the key of the values map
+ * @param keepUpdated true if the cursor's ContentProvider should be monitored for changes and
+ * the map updated when changes do occur
+ * @param handlerForUpdateNotifications the Handler that should be used to receive
+ * notifications of changes (if requested). Normally you pass null here, but if
+ * you know that the thread that is creating this isn't a thread that can receive
+ * messages then you can create your own handler and use that here.
+ */
+ public ContentQueryMap(Cursor cursor, String columnNameOfKey, boolean keepUpdated,
+ Handler handlerForUpdateNotifications) {
+ mCursor = cursor;
+ mColumnNames = mCursor.getColumnNames();
+ mKeyColumn = mCursor.getColumnIndexOrThrow(columnNameOfKey);
+ mHandlerForUpdateNotifications = handlerForUpdateNotifications;
+ setKeepUpdated(keepUpdated);
+
+ // If we aren't keeping the cache updated with the current state of the cursor's
+ // ContentProvider then read it once into the cache. Otherwise the cache will be filled
+ // automatically.
+ if (!keepUpdated) {
+ readCursorIntoCache(cursor);
+ }
+ }
+
+ /**
+ * Change whether or not the ContentQueryMap will register with the cursor's ContentProvider
+ * for change notifications. If you use a ContentQueryMap in an activity you should call this
+ * with false in onPause(), which means you need to call it with true in onResume()
+ * if want it to be kept updated.
+ * @param keepUpdated if true the ContentQueryMap should be registered with the cursor's
+ * ContentProvider, false otherwise
+ */
+ public void setKeepUpdated(boolean keepUpdated) {
+ if (keepUpdated == mKeepUpdated) return;
+ mKeepUpdated = keepUpdated;
+
+ if (!mKeepUpdated) {
+ mCursor.unregisterContentObserver(mContentObserver);
+ mContentObserver = null;
+ } else {
+ if (mHandlerForUpdateNotifications == null) {
+ mHandlerForUpdateNotifications = new Handler();
+ }
+ if (mContentObserver == null) {
+ mContentObserver = new ContentObserver(mHandlerForUpdateNotifications) {
+ @Override
+ public void onChange(boolean selfChange) {
+ // If anyone is listening, we need to do this now to broadcast
+ // to the observers. Otherwise, we'll just set mDirty and
+ // let it query lazily when they ask for the values.
+ if (countObservers() != 0) {
+ requery();
+ } else {
+ mDirty = true;
+ }
+ }
+ };
+ }
+ mCursor.registerContentObserver(mContentObserver);
+ // mark dirty, since it is possible the cursor's backing data had changed before we
+ // registered for changes
+ mDirty = true;
+ }
+ }
+
+ /**
+ * Access the ContentValues for the row specified by rowName
+ * @param rowName which row to read
+ * @return the ContentValues for the row, or null if the row wasn't present in the cursor
+ */
+ public synchronized ContentValues getValues(String rowName) {
+ if (mDirty) requery();
+ return mValues.get(rowName);
+ }
+
+ /** Requeries the cursor and reads the contents into the cache */
+ public void requery() {
+ final Cursor cursor = mCursor;
+ if (cursor == null) {
+ // If mCursor is null then it means there was a requery() in flight
+ // while another thread called close(), which nulls out mCursor.
+ // If this happens ignore the requery() since we are closed anyways.
+ return;
+ }
+ mDirty = false;
+ if (!cursor.requery()) {
+ // again, don't do anything if the cursor is already closed
+ return;
+ }
+ readCursorIntoCache(cursor);
+ setChanged();
+ notifyObservers();
+ }
+
+ private synchronized void readCursorIntoCache(Cursor cursor) {
+ // Make a new map so old values returned by getRows() are undisturbed.
+ int capacity = mValues != null ? mValues.size() : 0;
+ mValues = new HashMap<String, ContentValues>(capacity);
+ while (cursor.moveToNext()) {
+ ContentValues values = new ContentValues();
+ for (int i = 0; i < mColumnNames.length; i++) {
+ if (i != mKeyColumn) {
+ values.put(mColumnNames[i], cursor.getString(i));
+ }
+ }
+ mValues.put(cursor.getString(mKeyColumn), values);
+ }
+ }
+
+ public synchronized Map<String, ContentValues> getRows() {
+ if (mDirty) requery();
+ return mValues;
+ }
+
+ public synchronized void close() {
+ if (mContentObserver != null) {
+ mCursor.unregisterContentObserver(mContentObserver);
+ mContentObserver = null;
+ }
+ mCursor.close();
+ mCursor = null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCursor != null) close();
+ super.finalize();
+ }
+}
diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java
new file mode 100644
index 0000000..265ff33
--- /dev/null
+++ b/android/content/ContentResolver.java
@@ -0,0 +1,4195 @@
+/*
+ * Copyright (C) 2006 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 static android.provider.DocumentsContract.EXTRA_ORIENTATION;
+
+import android.accounts.Account;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.AppGlobals;
+import android.app.UriGrantsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.database.CrossProcessCursorWrapper;
+import android.database.Cursor;
+import android.database.IContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
+import android.graphics.ImageDecoder.ImageInfo;
+import android.graphics.ImageDecoder.Source;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.system.Int64Ref;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Size;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.MimeIconUtils;
+
+import dalvik.system.CloseGuard;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class provides applications access to the content model.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using a ContentResolver with content providers, read the
+ * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>
+ * developer guide.</p>
+ * </div>
+ */
+public abstract class ContentResolver implements ContentInterface {
+ /**
+ * Enables logic that supports deprecation of {@code _data} columns,
+ * typically by replacing values with fake paths that the OS then offers to
+ * redirect to {@link #openFileDescriptor(Uri, String)}, which developers
+ * should be using directly.
+ *
+ * @hide
+ */
+ public static final boolean DEPRECATE_DATA_COLUMNS = true;
+
+ /**
+ * Special filesystem path prefix which indicates that a path should be
+ * treated as a {@code content://} {@link Uri} when
+ * {@link #DEPRECATE_DATA_COLUMNS} is enabled.
+ * <p>
+ * The remainder of the path after this prefix is a
+ * {@link Uri#getSchemeSpecificPart()} value, which includes authority, path
+ * segments, and query parameters.
+ *
+ * @hide
+ */
+ public static final String DEPRECATE_DATA_PREFIX = "/mnt/content/";
+
+ /**
+ * @deprecated instead use
+ * {@link #requestSync(android.accounts.Account, String, android.os.Bundle)}
+ */
+ @Deprecated
+ public static final String SYNC_EXTRAS_ACCOUNT = "account";
+
+ /**
+ * If this extra is set to true, the sync request will be scheduled at the front of the
+ * sync request queue, but it is still subject to JobScheduler quota and throttling due to
+ * App Standby buckets.
+ *
+ * <p>This is different from {@link #SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB}.
+ */
+ public static final String SYNC_EXTRAS_EXPEDITED = "expedited";
+
+ /**
+ * If this extra is set to true, the sync request will be scheduled
+ * only when the device is plugged in. This is equivalent to calling
+ * setRequiresCharging(true) on {@link SyncRequest}.
+ */
+ public static final String SYNC_EXTRAS_REQUIRE_CHARGING = "require_charging";
+
+ /**
+ * Run this sync operation as an "expedited job"
+ * (see {@link android.app.job.JobInfo.Builder#setExpedited(boolean)}).
+ * Normally (if this flag isn't specified), sync operations are executed as regular
+ * {@link android.app.job.JobService} jobs.
+ *
+ * <p> Because Expedited Jobs have various restrictions compared to regular jobs, this flag
+ * cannot be combined with certain other flags, otherwise an
+ * <code>IllegalArgumentException</code> will be thrown. Notably, because Expedited Jobs do not
+ * support various constraints, the following restriction apply:
+ * <ul>
+ * <li>Can't be used with {@link #SYNC_EXTRAS_REQUIRE_CHARGING}
+ * <li>Can't be used with {@link #SYNC_EXTRAS_EXPEDITED}
+ * <li>Can't be used on periodic syncs.
+ * <li>When an expedited-job-sync fails and a retry is scheduled, the retried sync will be
+ * scheduled as a regular job unless {@link #SYNC_EXTRAS_IGNORE_BACKOFF} is set.
+ * </ul>
+ *
+ * <p>This is different from {@link #SYNC_EXTRAS_EXPEDITED}.
+ */
+ @SuppressLint("IntentName")
+ public static final String SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB = "schedule_as_expedited_job";
+
+ /**
+ * @deprecated instead use
+ * {@link #SYNC_EXTRAS_MANUAL}
+ */
+ @Deprecated
+ public static final String SYNC_EXTRAS_FORCE = "force";
+
+ /**
+ * If this extra is set to true then the sync settings (like getSyncAutomatically())
+ * are ignored by the sync scheduler.
+ */
+ public static final String SYNC_EXTRAS_IGNORE_SETTINGS = "ignore_settings";
+
+ /**
+ * If this extra is set to true then any backoffs for the initial attempt (e.g. due to retries)
+ * are ignored by the sync scheduler. If this request fails and gets rescheduled then the
+ * retries will still honor the backoff.
+ */
+ public static final String SYNC_EXTRAS_IGNORE_BACKOFF = "ignore_backoff";
+
+ /**
+ * If this extra is set to true then the request will not be retried if it fails.
+ */
+ public static final String SYNC_EXTRAS_DO_NOT_RETRY = "do_not_retry";
+
+ /**
+ * Setting this extra is the equivalent of setting both {@link #SYNC_EXTRAS_IGNORE_SETTINGS}
+ * and {@link #SYNC_EXTRAS_IGNORE_BACKOFF}
+ */
+ public static final String SYNC_EXTRAS_MANUAL = "force";
+
+ /**
+ * Indicates that this sync is intended to only upload local changes to the server.
+ * For example, this will be set to true if the sync is initiated by a call to
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
+ */
+ public static final String SYNC_EXTRAS_UPLOAD = "upload";
+
+ /**
+ * Indicates that the sync adapter should proceed with the delete operations,
+ * even if it determines that there are too many.
+ * See {@link SyncResult#tooManyDeletions}
+ */
+ public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
+
+ /**
+ * Indicates that the sync adapter should not proceed with the delete operations,
+ * if it determines that there are too many.
+ * See {@link SyncResult#tooManyDeletions}
+ */
+ public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
+
+ /* Extensions to API. TODO: Not clear if we will keep these as public flags. */
+ /** {@hide} User-specified flag for expected upload size. */
+ public static final String SYNC_EXTRAS_EXPECTED_UPLOAD = "expected_upload";
+
+ /** {@hide} User-specified flag for expected download size. */
+ public static final String SYNC_EXTRAS_EXPECTED_DOWNLOAD = "expected_download";
+
+ /** {@hide} Priority of this sync with respect to other syncs scheduled for this application. */
+ public static final String SYNC_EXTRAS_PRIORITY = "sync_priority";
+
+ /** {@hide} Flag to allow sync to occur on metered network. */
+ 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
+ * called with a >= 0 value. When this flag is set the SyncAdapter does not need to
+ * do a full sync, though it is allowed to do so.
+ */
+ public static final String SYNC_EXTRAS_INITIALIZE = "initialize";
+
+ /** @hide */
+ public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
+ new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
+
+ public static final String SCHEME_CONTENT = "content";
+ public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
+ public static final String SCHEME_FILE = "file";
+
+ /**
+ * An extra {@link Point} describing the optimal size for a requested image
+ * resource, in pixels. If a provider has multiple sizes of the image, it
+ * should return the image closest to this size.
+ *
+ * @see #openTypedAssetFileDescriptor(Uri, String, Bundle)
+ * @see #openTypedAssetFileDescriptor(Uri, String, Bundle,
+ * CancellationSignal)
+ */
+ public static final String EXTRA_SIZE = "android.content.extra.SIZE";
+
+ /**
+ * An extra boolean describing whether a particular provider supports refresh
+ * or not. If a provider supports refresh, it should include this key in its
+ * returned Cursor as part of its query call.
+ *
+ */
+ public static final String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
+
+ /**
+ * Key for an SQL style selection string that may be present in the query Bundle argument
+ * passed to {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}
+ * when called by a legacy client.
+ *
+ * <p>Clients should never include user supplied values directly in the selection string,
+ * as this presents an avenue for SQL injection attacks. In lieu of this, a client
+ * should use standard placeholder notation to represent values in a selection string,
+ * then supply a corresponding value in {@value #QUERY_ARG_SQL_SELECTION_ARGS}.
+ *
+ * <p><b>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+ * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS
+ * @see #QUERY_ARG_SORT_DIRECTION
+ * @see #QUERY_ARG_SORT_COLLATION
+ * @see #QUERY_ARG_SORT_LOCALE
+ */
+ public static final String QUERY_ARG_SQL_SELECTION = "android:query-arg-sql-selection";
+
+ /**
+ * Key for SQL selection string arguments list.
+ *
+ * <p>Clients should never include user supplied values directly in the selection string,
+ * as this presents an avenue for SQL injection attacks. In lieu of this, a client
+ * should use standard placeholder notation to represent values in a selection string,
+ * then supply a corresponding value in {@value #QUERY_ARG_SQL_SELECTION_ARGS}.
+ *
+ * <p><b>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+ * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS
+ * @see #QUERY_ARG_SORT_DIRECTION
+ * @see #QUERY_ARG_SORT_COLLATION
+ * @see #QUERY_ARG_SORT_LOCALE
+ */
+ public static final String QUERY_ARG_SQL_SELECTION_ARGS =
+ "android:query-arg-sql-selection-args";
+
+ /**
+ * Key for an SQL style sort string that may be present in the query Bundle argument
+ * passed to {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}
+ * when called by a legacy client.
+ *
+ * <p><b>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+ * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS
+ * @see #QUERY_ARG_SORT_DIRECTION
+ * @see #QUERY_ARG_SORT_COLLATION
+ * @see #QUERY_ARG_SORT_LOCALE
+ */
+ public static final String QUERY_ARG_SQL_SORT_ORDER = "android:query-arg-sql-sort-order";
+
+ /**
+ * Key for an SQL style {@code GROUP BY} string that may be present in the
+ * query Bundle argument passed to
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}.
+ *
+ * <p><b>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+ * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+ *
+ * @see #QUERY_ARG_GROUP_COLUMNS
+ */
+ public static final String QUERY_ARG_SQL_GROUP_BY = "android:query-arg-sql-group-by";
+
+ /**
+ * Key for an SQL style {@code HAVING} string that may be present in the
+ * query Bundle argument passed to
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}.
+ *
+ * <p><b>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+ * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+ */
+ public static final String QUERY_ARG_SQL_HAVING = "android:query-arg-sql-having";
+
+ /**
+ * Key for an SQL style {@code LIMIT} string that may be present in the
+ * query Bundle argument passed to
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}.
+ *
+ * <p><b>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+ * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+ *
+ * @see #QUERY_ARG_LIMIT
+ * @see #QUERY_ARG_OFFSET
+ */
+ public static final String QUERY_ARG_SQL_LIMIT = "android:query-arg-sql-limit";
+
+ /**
+ * Specifies the list of columns (stored as a {@code String[]}) against
+ * which to sort results. When first column values are identical, records
+ * are then sorted based on second column values, and so on.
+ * <p>
+ * Columns present in this list must also be included in the projection
+ * supplied to
+ * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
+ * <p>
+ * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher:
+ * <li>{@link ContentProvider} implementations: When preparing data in
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)},
+ * if sort columns is reflected in the returned Cursor, it is strongly
+ * recommended that {@link #QUERY_ARG_SORT_COLUMNS} then be included in the
+ * array of honored arguments reflected in {@link Cursor} extras
+ * {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+ * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in
+ * the arguments {@link Bundle}, the Content framework will attempt to
+ * synthesize an QUERY_ARG_SQL* argument using the corresponding
+ * QUERY_ARG_SORT* values.
+ */
+ public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-sort-columns";
+
+ /**
+ * Specifies desired sort order. When unspecified a provider may provide a default
+ * sort direction, or choose to return unsorted results.
+ *
+ * <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher:
+ *
+ * <li>{@link ContentProvider} implementations: When preparing data in
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}, if sort direction
+ * is reflected in the returned Cursor, it is strongly recommended that
+ * {@link #QUERY_ARG_SORT_DIRECTION} then be included in the array of honored arguments
+ * reflected in {@link Cursor} extras {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+ *
+ * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in the
+ * arguments {@link Bundle}, the Content framework will attempt to synthesize
+ * a QUERY_ARG_SQL* argument using the corresponding QUERY_ARG_SORT* values.
+ *
+ * @see #QUERY_SORT_DIRECTION_ASCENDING
+ * @see #QUERY_SORT_DIRECTION_DESCENDING
+ */
+ public static final String QUERY_ARG_SORT_DIRECTION = "android:query-arg-sort-direction";
+
+ /**
+ * Allows client to specify a hint to the provider declaring which collation
+ * to use when sorting values.
+ * <p>
+ * Providers may support custom collators. When specifying a custom collator
+ * the value is determined by the Provider.
+ * <p>
+ * {@link ContentProvider} implementations: When preparing data in
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)},
+ * if sort collation is reflected in the returned Cursor, it is strongly
+ * recommended that {@link #QUERY_ARG_SORT_COLLATION} then be included in
+ * the array of honored arguments reflected in {@link Cursor} extras
+ * {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+ * <p>
+ * When querying a provider, where no QUERY_ARG_SQL* otherwise exists in the
+ * arguments {@link Bundle}, the Content framework will attempt to
+ * synthesize a QUERY_ARG_SQL* argument using the corresponding
+ * QUERY_ARG_SORT* values.
+ *
+ * @see java.text.Collator#PRIMARY
+ * @see java.text.Collator#SECONDARY
+ * @see java.text.Collator#TERTIARY
+ * @see java.text.Collator#IDENTICAL
+ */
+ public static final String QUERY_ARG_SORT_COLLATION = "android:query-arg-sort-collation";
+
+ /**
+ * Allows client to specify a hint to the provider declaring which locale to
+ * use when sorting values.
+ * <p>
+ * The value is defined as a RFC 3066 locale ID followed by an optional
+ * keyword list, which is the locale format used to configure ICU through
+ * classes like {@link android.icu.util.ULocale}. This supports requesting
+ * advanced sorting options, such as {@code de@collation=phonebook},
+ * {@code zh@collation=pinyin}, etc.
+ * <p>
+ * {@link ContentProvider} implementations: When preparing data in
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)},
+ * if sort locale is reflected in the returned Cursor, it is strongly
+ * recommended that {@link #QUERY_ARG_SORT_LOCALE} then be included in the
+ * array of honored arguments reflected in {@link Cursor} extras
+ * {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+ *
+ * @see java.util.Locale#Locale(String)
+ * @see android.icu.util.ULocale#ULocale(String)
+ */
+ public static final String QUERY_ARG_SORT_LOCALE = "android:query-arg-sort-locale";
+
+ /**
+ * Specifies the list of columns (stored as a {@code String[]}) against
+ * which to group results. When column values are identical, multiple
+ * records are collapsed together into a single record.
+ * <p>
+ * Columns present in this list must also be included in the projection
+ * supplied to
+ * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
+ * <p>
+ * Apps targeting {@link android.os.Build.VERSION_CODES#R} or higher:
+ * <li>{@link ContentProvider} implementations: When preparing data in
+ * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)},
+ * if group columns is reflected in the returned Cursor, it is strongly
+ * recommended that {@link #QUERY_ARG_SORT_COLUMNS} then be included in the
+ * array of honored arguments reflected in {@link Cursor} extras
+ * {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+ * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in
+ * the arguments {@link Bundle}, the Content framework will attempt to
+ * synthesize an QUERY_ARG_SQL* argument using the corresponding
+ * QUERY_ARG_SORT* values.
+ */
+ public static final String QUERY_ARG_GROUP_COLUMNS = "android:query-arg-group-columns";
+
+ /**
+ * Allows provider to report back to client which query keys are honored in a Cursor.
+ *
+ * <p>Key identifying a {@code String[]} containing all QUERY_ARG_SORT* arguments
+ * honored by the provider. Include this in {@link Cursor} extras {@link Bundle}
+ * when any QUERY_ARG_SORT* value was honored during the preparation of the
+ * results {@link Cursor}.
+ *
+ * <p>If present, ALL honored arguments are enumerated in this extra’s payload.
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS
+ * @see #QUERY_ARG_SORT_DIRECTION
+ * @see #QUERY_ARG_SORT_COLLATION
+ * @see #QUERY_ARG_SORT_LOCALE
+ * @see #QUERY_ARG_GROUP_COLUMNS
+ */
+ public static final String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "QUERY_SORT_DIRECTION_" }, value = {
+ QUERY_SORT_DIRECTION_ASCENDING,
+ QUERY_SORT_DIRECTION_DESCENDING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SortDirection {}
+ public static final int QUERY_SORT_DIRECTION_ASCENDING = 0;
+ public static final int QUERY_SORT_DIRECTION_DESCENDING = 1;
+
+ /**
+ * @see {@link java.text.Collector} for details on respective collation strength.
+ * @hide
+ */
+ @IntDef(flag = false, value = {
+ java.text.Collator.PRIMARY,
+ java.text.Collator.SECONDARY,
+ java.text.Collator.TERTIARY,
+ java.text.Collator.IDENTICAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface QueryCollator {}
+
+ /**
+ * Specifies the offset row index within a Cursor.
+ */
+ public static final String QUERY_ARG_OFFSET = "android:query-arg-offset";
+
+ /**
+ * Specifies the max number of rows to include in a Cursor.
+ */
+ public static final String QUERY_ARG_LIMIT = "android:query-arg-limit";
+
+ /**
+ * Added to {@link Cursor} extras {@link Bundle} to indicate total row count of
+ * recordset when paging is supported. Providers must include this when
+ * implementing paging support.
+ *
+ * <p>A provider may return -1 that row count of the recordset is unknown.
+ *
+ * <p>Providers having returned -1 in a previous query are recommended to
+ * send content change notification once (if) full recordset size becomes
+ * known.
+ */
+ public static final String EXTRA_TOTAL_COUNT = "android.content.extra.TOTAL_COUNT";
+
+ /**
+ * This is the Android platform's base MIME type for a content: URI
+ * containing a Cursor of a single item. Applications should use this
+ * as the base type along with their own sub-type of their content: URIs
+ * that represent a particular item. For example, hypothetical IMAP email
+ * client may have a URI
+ * <code>content://com.company.provider.imap/inbox/1</code> for a particular
+ * message in the inbox, whose MIME type would be reported as
+ * <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code>
+ *
+ * <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}.
+ */
+ public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+
+ /**
+ * This is the Android platform's base MIME type for a content: URI
+ * containing a Cursor of zero or more items. Applications should use this
+ * as the base type along with their own sub-type of their content: URIs
+ * that represent a directory of items. For example, hypothetical IMAP email
+ * client may have a URI
+ * <code>content://com.company.provider.imap/inbox</code> for all of the
+ * messages in its inbox, whose MIME type would be reported as
+ * <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code>
+ *
+ * <p>Note how the base MIME type varies between this and
+ * {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is
+ * one single item or multiple items in the data set, while the sub-type
+ * remains the same because in either case the data structure contained
+ * in the cursor is the same.
+ */
+ public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
+
+ /**
+ * This is the Android platform's generic MIME type to match any MIME
+ * type of the form "{@link #CURSOR_ITEM_BASE_TYPE}/{@code SUB_TYPE}".
+ * {@code SUB_TYPE} is the sub-type of the application-dependent
+ * content, e.g., "audio", "video", "playlist".
+ */
+ public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+
+ /** {@hide} */
+ @Deprecated
+ public static final String MIME_TYPE_DEFAULT = ClipDescription.MIMETYPE_UNKNOWN;
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
+ /** @hide */
+ public static final int SYNC_ERROR_AUTHENTICATION = 2;
+ /** @hide */
+ public static final int SYNC_ERROR_IO = 3;
+ /** @hide */
+ public static final int SYNC_ERROR_PARSE = 4;
+ /** @hide */
+ public static final int SYNC_ERROR_CONFLICT = 5;
+ /** @hide */
+ public static final int SYNC_ERROR_TOO_MANY_DELETIONS = 6;
+ /** @hide */
+ public static final int SYNC_ERROR_TOO_MANY_RETRIES = 7;
+ /** @hide */
+ public static final int SYNC_ERROR_INTERNAL = 8;
+
+ private static final String[] SYNC_ERROR_NAMES = new String[] {
+ "already-in-progress",
+ "authentication-error",
+ "io-error",
+ "parse-error",
+ "conflict",
+ "too-many-deletions",
+ "too-many-retries",
+ "internal-error",
+ };
+
+ /** @hide */
+ public static String syncErrorToString(int error) {
+ if (error < 1 || error > SYNC_ERROR_NAMES.length) {
+ return String.valueOf(error);
+ }
+ return SYNC_ERROR_NAMES[error - 1];
+ }
+
+ /** @hide */
+ public static int syncErrorStringToInt(String error) {
+ for (int i = 0, n = SYNC_ERROR_NAMES.length; i < n; i++) {
+ if (SYNC_ERROR_NAMES[i].equals(error)) {
+ return i + 1;
+ }
+ }
+ if (error != null) {
+ try {
+ return Integer.parseInt(error);
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "error parsing sync error: " + error);
+ }
+ }
+ return 0;
+ }
+
+ public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0;
+ public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1;
+ public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int SYNC_OBSERVER_TYPE_STATUS = 1<<3;
+ /** @hide */
+ public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "NOTIFY_" }, value = {
+ NOTIFY_SYNC_TO_NETWORK,
+ NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS,
+ NOTIFY_INSERT,
+ NOTIFY_UPDATE,
+ NOTIFY_DELETE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NotifyFlags {}
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: attempt to sync the change
+ * to the network.
+ */
+ public static final int NOTIFY_SYNC_TO_NETWORK = 1<<0;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: if set, this notification
+ * will be skipped if it is being delivered to the root URI of a ContentObserver that is
+ * using "notify for descendants." The purpose of this is to allow the provide to send
+ * a general notification of "something under X" changed that observers of that specific
+ * URI can receive, while also sending a specific URI under X. It would use this flag
+ * when sending the former, so that observers of "X and descendants" only see the latter.
+ */
+ public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of an {@link ContentProvider#insert} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_INSERT = 1 << 2;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of an {@link ContentProvider#update} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_UPDATE = 1 << 3;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of a {@link ContentProvider#delete} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_DELETE = 1 << 4;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification should
+ * not be subject to any delays when dispatching to apps running in the
+ * background.
+ * <p>
+ * Using this flag may negatively impact system health and performance, and
+ * should be used sparingly.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_NO_DELAY = 1 << 15;
+
+ /**
+ * No exception, throttled by app standby normally.
+ * @hide
+ */
+ public static final int SYNC_EXEMPTION_NONE = 0;
+
+ /**
+ * Exemption given to a sync request made by a foreground app (including
+ * PROCESS_STATE_IMPORTANT_FOREGROUND).
+ *
+ * At the schedule time, we promote the sync adapter app for a higher bucket:
+ * - If the device is not dozing (so the sync will start right away)
+ * promote to ACTIVE for 1 hour.
+ * - If the device is dozing (so the sync *won't* start right away),
+ * promote to WORKING_SET for 4 hours, so it'll get a higher chance to be started once the
+ * device comes out of doze.
+ * - When the sync actually starts, we promote the sync adapter app to ACTIVE for 10 minutes,
+ * so it can schedule and start more syncs without getting throttled, even when the first
+ * operation was canceled and now we're retrying.
+ *
+ *
+ * @hide
+ */
+ public static final int SYNC_EXEMPTION_PROMOTE_BUCKET = 1;
+
+ /**
+ * In addition to {@link #SYNC_EXEMPTION_PROMOTE_BUCKET}, we put the sync adapter app in the
+ * temp allowlist for 10 minutes, so that even RARE apps can run syncs right away.
+ * @hide
+ */
+ public static final int SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP = 2;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SYNC_EXEMPTION_" }, value = {
+ SYNC_EXEMPTION_NONE,
+ SYNC_EXEMPTION_PROMOTE_BUCKET,
+ SYNC_EXEMPTION_PROMOTE_BUCKET_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;
+ private static final int SLOW_THRESHOLD_MILLIS = 500 * Build.HW_TIMEOUT_MULTIPLIER;
+ private final Random mRandom = new Random(); // guarded by itself
+
+ /** @hide */
+ public static final String REMOTE_CALLBACK_RESULT = "result";
+
+ /** @hide */
+ public static final String REMOTE_CALLBACK_ERROR = "error";
+
+ /**
+ * How long we wait for an attached process to publish its content providers
+ * before we decide it must be hung.
+ * @hide
+ */
+ public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS =
+ 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /**
+ * How long we wait for an provider to be published. Should be longer than
+ * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}.
+ * @hide
+ */
+ public static final int CONTENT_PROVIDER_READY_TIMEOUT_MILLIS =
+ CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ // Timeout given a ContentProvider that has already been started and connected to.
+ private static final int CONTENT_PROVIDER_TIMEOUT_MILLIS =
+ 3 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how
+ // long ActivityManagerService is giving a content provider to get published if a new process
+ // needs to be started for that.
+ private static final int REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS =
+ CONTENT_PROVIDER_READY_TIMEOUT_MILLIS + CONTENT_PROVIDER_TIMEOUT_MILLIS;
+
+ /**
+ * Note: passing a {@code null} context here could lead to unexpected behavior in certain
+ * ContentResolver APIs so it is highly recommended to pass a non-null context here.
+ */
+ public ContentResolver(@Nullable Context context) {
+ this(context, null);
+ }
+
+ /** {@hide} */
+ public ContentResolver(@Nullable Context context, @Nullable ContentInterface wrapped) {
+ mContext = context != null ? context : ActivityThread.currentApplication();
+ mPackageName = mContext.getOpPackageName();
+ mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ mWrapped = wrapped;
+ }
+
+ /** {@hide} */
+ public static @NonNull ContentResolver wrap(@NonNull ContentInterface wrapped) {
+ Objects.requireNonNull(wrapped);
+
+ return new ContentResolver(null, wrapped) {
+ @Override
+ public void unstableProviderDied(IContentProvider icp) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean releaseUnstableProvider(IContentProvider icp) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean releaseProvider(IContentProvider icp) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ protected IContentProvider acquireUnstableProvider(Context c, String name) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ protected IContentProvider acquireProvider(Context c, String name) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * Create a {@link ContentResolver} instance that redirects all its methods
+ * to the given {@link ContentProvider}.
+ */
+ public static @NonNull ContentResolver wrap(@NonNull ContentProvider wrapped) {
+ return wrap((ContentInterface) wrapped);
+ }
+
+ /**
+ * Create a {@link ContentResolver} instance that redirects all its methods
+ * to the given {@link ContentProviderClient}.
+ */
+ public static @NonNull ContentResolver wrap(@NonNull ContentProviderClient wrapped) {
+ return wrap((ContentInterface) wrapped);
+ }
+
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ protected abstract IContentProvider acquireProvider(Context c, String name);
+
+ /**
+ * Providing a default implementation of this, to avoid having to change a
+ * lot of other things, but implementations of ContentResolver should
+ * implement it.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ protected IContentProvider acquireExistingProvider(Context c, String name) {
+ return acquireProvider(c, name);
+ }
+
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean releaseProvider(IContentProvider icp);
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ protected abstract IContentProvider acquireUnstableProvider(Context c, String name);
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean releaseUnstableProvider(IContentProvider icp);
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void unstableProviderDied(IContentProvider icp);
+
+ /** @hide */
+ public void appNotRespondingViaProvider(IContentProvider icp) {
+ throw new UnsupportedOperationException("appNotRespondingViaProvider");
+ }
+
+ /**
+ * Return the MIME type of the given content URL.
+ *
+ * @param url A Uri identifying content (either a list or specific type),
+ * using the content:// scheme.
+ * @return A MIME type for the content, or null if the URL is invalid or the type is unknown
+ */
+ @Override
+ public final @Nullable String getType(@NonNull Uri url) {
+ Objects.requireNonNull(url, "url");
+
+ try {
+ if (mWrapped != null) return mWrapped.getType(url);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ // XXX would like to have an acquireExistingUnstableProvider for this.
+ IContentProvider provider = acquireExistingProvider(url);
+ if (provider != null) {
+ try {
+ final StringResultListener resultListener = new StringResultListener();
+ provider.getTypeAsync(url, new RemoteCallback(resultListener));
+ resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ if (resultListener.exception != null) {
+ throw resultListener.exception;
+ }
+ return resultListener.result;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } catch (java.lang.Exception e) {
+ Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")");
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ if (!SCHEME_CONTENT.equals(url.getScheme())) {
+ return null;
+ }
+
+ try {
+ final StringResultListener resultListener = new StringResultListener();
+ ActivityManager.getService().getProviderMimeTypeAsync(
+ ContentProvider.getUriWithoutUserId(url),
+ resolveUserId(url),
+ new RemoteCallback(resultListener));
+ resultListener.waitForResult(REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ if (resultListener.exception != null) {
+ throw resultListener.exception;
+ }
+ return resultListener.result;
+ } catch (RemoteException e) {
+ // We just failed to send a oneway request to the System Server. Nothing to do.
+ return null;
+ } catch (java.lang.Exception e) {
+ Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")");
+ return null;
+ }
+ }
+
+ private abstract static class ResultListener<T> implements RemoteCallback.OnResultListener {
+ @GuardedBy("this")
+ public boolean done;
+
+ @GuardedBy("this")
+ public T result;
+
+ @GuardedBy("this")
+ public RuntimeException exception;
+
+ @Override
+ public void onResult(Bundle result) {
+ synchronized (this) {
+ ParcelableException e = result.getParcelable(REMOTE_CALLBACK_ERROR);
+ if (e != null) {
+ Throwable t = e.getCause();
+ if (t instanceof RuntimeException) {
+ this.exception = (RuntimeException) t;
+ } else {
+ this.exception = new RuntimeException(t);
+ }
+ } else {
+ this.result = getResultFromBundle(result);
+ }
+ done = true;
+ notifyAll();
+ }
+ }
+
+ protected abstract T getResultFromBundle(Bundle result);
+
+ public void waitForResult(long timeout) {
+ synchronized (this) {
+ if (!done) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+ }
+
+ private static class StringResultListener extends ResultListener<String> {
+ @Override
+ protected String getResultFromBundle(Bundle result) {
+ return result.getString(REMOTE_CALLBACK_RESULT);
+ }
+ }
+
+ private static class UriResultListener extends ResultListener<Uri> {
+ @Override
+ protected Uri getResultFromBundle(Bundle result) {
+ return result.getParcelable(REMOTE_CALLBACK_RESULT);
+ }
+ }
+
+ /**
+ * Query for the possible MIME types for the representations the given
+ * content URL can be returned when opened as as stream with
+ * {@link #openTypedAssetFileDescriptor}. Note that the types here are
+ * not necessarily a superset of the type returned by {@link #getType} --
+ * many content providers cannot return a raw stream for the structured
+ * data that they contain.
+ *
+ * @param url A Uri identifying content (either a list or specific type),
+ * using the content:// scheme.
+ * @param mimeTypeFilter The desired MIME type. This may be a pattern,
+ * such as */*, to query for all available MIME types that match the
+ * pattern.
+ * @return Returns an array of MIME type strings for all available
+ * data streams that match the given mimeTypeFilter. If there are none,
+ * null is returned.
+ */
+ @Override
+ public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter) {
+ Objects.requireNonNull(url, "url");
+ Objects.requireNonNull(mimeTypeFilter, "mimeTypeFilter");
+
+ try {
+ if (mWrapped != null) return mWrapped.getStreamTypes(url, mimeTypeFilter);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ return provider.getStreamTypes(url, mimeTypeFilter);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Query the given URI, returning a {@link Cursor} over the result set.
+ * <p>
+ * For best performance, the caller should follow these guidelines:
+ * <ul>
+ * <li>Provide an explicit projection, to prevent
+ * reading data from storage that aren't going to be used.</li>
+ * <li>Use question mark parameter markers such as 'phone=?' instead of
+ * explicit values in the {@code selection} parameter, so that queries
+ * that differ only by those values will be recognized as the same
+ * for caching purposes.</li>
+ * </ul>
+ * </p>
+ *
+ * @param uri The URI, using the content:// scheme, for the content to
+ * retrieve.
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is inefficient.
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null will
+ * return all rows for the given URI.
+ * @param selectionArgs You may include ?s in selection, which will be
+ * replaced by the values from selectionArgs, in the order that they
+ * appear in the selection. The values will be bound as Strings.
+ * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use the
+ * default sort order, which may be unordered.
+ * @return A Cursor object, which is positioned before the first entry. May return
+ * <code>null</code> if the underlying content provider returns <code>null</code>,
+ * or if it crashes.
+ * @see Cursor
+ */
+ public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
+ @Nullable String[] projection, @Nullable String selection,
+ @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+ return query(uri, projection, selection, selectionArgs, sortOrder, null);
+ }
+
+ /**
+ * Query the given URI, returning a {@link Cursor} over the result set
+ * with optional support for cancellation.
+ * <p>
+ * For best performance, the caller should follow these guidelines:
+ * <ul>
+ * <li>Provide an explicit projection, to prevent
+ * reading data from storage that aren't going to be used.</li>
+ * <li>Use question mark parameter markers such as 'phone=?' instead of
+ * explicit values in the {@code selection} parameter, so that queries
+ * that differ only by those values will be recognized as the same
+ * for caching purposes.</li>
+ * </ul>
+ * </p>
+ *
+ * @param uri The URI, using the content:// scheme, for the content to
+ * retrieve.
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is inefficient.
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null will
+ * return all rows for the given URI.
+ * @param selectionArgs You may include ?s in selection, which will be
+ * replaced by the values from selectionArgs, in the order that they
+ * appear in the selection. The values will be bound as Strings.
+ * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use the
+ * default sort order, which may be unordered.
+ * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+ * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+ * when the query is executed.
+ * @return A Cursor object, which is positioned before the first entry. May return
+ * <code>null</code> if the underlying content provider returns <code>null</code>,
+ * or if it crashes.
+ * @see Cursor
+ */
+ public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
+ @Nullable String[] projection, @Nullable String selection,
+ @Nullable String[] selectionArgs, @Nullable String sortOrder,
+ @Nullable CancellationSignal cancellationSignal) {
+ Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
+ return query(uri, projection, queryArgs, cancellationSignal);
+ }
+
+ /**
+ * Query the given URI, returning a {@link Cursor} over the result set
+ * with support for cancellation.
+ *
+ * <p>For best performance, the caller should follow these guidelines:
+ *
+ * <li>Provide an explicit projection, to prevent reading data from storage
+ * that aren't going to be used.
+ *
+ * Provider must identify which QUERY_ARG_SORT* arguments were honored during
+ * the preparation of the result set by including the respective argument keys
+ * in the {@link Cursor} extras {@link Bundle}. See {@link #EXTRA_HONORED_ARGS}
+ * for details.
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS
+ * @see #QUERY_ARG_SORT_DIRECTION
+ * @see #QUERY_ARG_SORT_COLLATION
+ *
+ * @param uri The URI, using the content:// scheme, for the content to
+ * retrieve.
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is inefficient.
+ * @param queryArgs A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+ * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+ * when the query is executed.
+ * @return A Cursor object, which is positioned before the first entry. May return
+ * <code>null</code> if the underlying content provider returns <code>null</code>,
+ * or if it crashes.
+ * @see Cursor
+ */
+ @Override
+ public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
+ @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable CancellationSignal cancellationSignal) {
+ Objects.requireNonNull(uri, "uri");
+
+ try {
+ if (mWrapped != null) {
+ return mWrapped.query(uri, projection, queryArgs, cancellationSignal);
+ }
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider unstableProvider = acquireUnstableProvider(uri);
+ if (unstableProvider == null) {
+ return null;
+ }
+ IContentProvider stableProvider = null;
+ Cursor qCursor = null;
+ try {
+ long startTime = SystemClock.uptimeMillis();
+
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = unstableProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ try {
+ qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,
+ queryArgs, remoteCancellationSignal);
+ } catch (DeadObjectException e) {
+ // The remote process has died... but we only hold an unstable
+ // reference though, so we might recover!!! Let's try!!!!
+ // This is exciting!!1!!1!!!!1
+ unstableProviderDied(unstableProvider);
+ stableProvider = acquireProvider(uri);
+ if (stableProvider == null) {
+ return null;
+ }
+ qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
+ queryArgs, remoteCancellationSignal);
+ }
+ if (qCursor == null) {
+ return null;
+ }
+
+ // Force query execution. Might fail and throw a runtime exception here.
+ qCursor.getCount();
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
+
+ // Wrap the cursor object into CursorWrapperInner object.
+ final IContentProvider provider = (stableProvider != null) ? stableProvider
+ : acquireProvider(uri);
+ final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
+ stableProvider = null;
+ qCursor = null;
+ return wrapper;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ if (qCursor != null) {
+ qCursor.close();
+ }
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(null);
+ }
+ if (unstableProvider != null) {
+ releaseUnstableProvider(unstableProvider);
+ }
+ if (stableProvider != null) {
+ releaseProvider(stableProvider);
+ }
+ }
+ }
+
+ /** {@hide} */
+ public final @NonNull Uri canonicalizeOrElse(@NonNull Uri uri) {
+ final Uri res = canonicalize(uri);
+ return (res != null) ? res : uri;
+ }
+
+ /**
+ * Transform the given <var>url</var> to a canonical representation of
+ * its referenced resource, which can be used across devices, persisted,
+ * backed up and restored, etc. The returned Uri is still a fully capable
+ * Uri for use with its content provider, allowing you to do all of the
+ * same content provider operations as with the original Uri --
+ * {@link #query}, {@link #openInputStream(android.net.Uri)}, etc. The
+ * only difference in behavior between the original and new Uris is that
+ * the content provider may need to do some additional work at each call
+ * using it to resolve it to the correct resource, especially if the
+ * canonical Uri has been moved to a different environment.
+ *
+ * <p>If you are moving a canonical Uri between environments, you should
+ * perform another call to {@link #canonicalize} with that original Uri to
+ * re-canonicalize it for the current environment. Alternatively, you may
+ * want to use {@link #uncanonicalize} to transform it to a non-canonical
+ * Uri that works only in the current environment but potentially more
+ * efficiently than the canonical representation.</p>
+ *
+ * @param url The {@link Uri} that is to be transformed to a canonical
+ * representation. Like all resolver calls, the input can be either
+ * a non-canonical or canonical Uri.
+ *
+ * @return Returns the official canonical representation of <var>url</var>,
+ * or null if the content provider does not support a canonical representation
+ * of the given Uri. Many providers may not support canonicalization of some
+ * or all of their Uris.
+ *
+ * @see #uncanonicalize
+ */
+ @Override
+ public final @Nullable Uri canonicalize(@NonNull Uri url) {
+ Objects.requireNonNull(url, "url");
+
+ try {
+ if (mWrapped != null) return mWrapped.canonicalize(url);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ final UriResultListener resultListener = new UriResultListener();
+ provider.canonicalizeAsync(mContext.getAttributionSource(), url,
+ new RemoteCallback(resultListener));
+ resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ if (resultListener.exception != null) {
+ throw resultListener.exception;
+ }
+ return resultListener.result;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Given a canonical Uri previously generated by {@link #canonicalize}, convert
+ * it to its local non-canonical form. This can be useful in some cases where
+ * you know that you will only be using the Uri in the current environment and
+ * want to avoid any possible overhead when using it with the content
+ * provider or want to verify that the referenced data exists at all in the
+ * new environment.
+ *
+ * @param url The canonical {@link Uri} that is to be convered back to its
+ * non-canonical form.
+ *
+ * @return Returns the non-canonical representation of <var>url</var>. This will
+ * return null if data identified by the canonical Uri can not be found in
+ * the current environment; callers must always check for null and deal with
+ * that by appropriately falling back to an alternative.
+ *
+ * @see #canonicalize
+ */
+ @Override
+ public final @Nullable Uri uncanonicalize(@NonNull Uri url) {
+ Objects.requireNonNull(url, "url");
+
+ try {
+ if (mWrapped != null) return mWrapped.uncanonicalize(url);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ final UriResultListener resultListener = new UriResultListener();
+ provider.uncanonicalizeAsync(mContext.getAttributionSource(), url,
+ new RemoteCallback(resultListener));
+ resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ if (resultListener.exception != null) {
+ throw resultListener.exception;
+ }
+ return resultListener.result;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * This allows clients to request an explicit refresh of content identified
+ * by {@code uri}.
+ * <p>
+ * Client code should only invoke this method when there is a strong
+ * indication (such as a user initiated pull to refresh gesture) that the
+ * content is stale.
+ * <p>
+ *
+ * @param url The Uri identifying the data to refresh.
+ * @param extras Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or {@code null} if none. For example, if you called refresh on
+ * a particular uri, you should call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the refresh request.
+ * @return true if the provider actually tried refreshing.
+ */
+ @Override
+ public final boolean refresh(@NonNull Uri url, @Nullable Bundle extras,
+ @Nullable CancellationSignal cancellationSignal) {
+ Objects.requireNonNull(url, "url");
+
+ try {
+ if (mWrapped != null) return mWrapped.refresh(url, extras, cancellationSignal);
+ } catch (RemoteException e) {
+ return false;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return false;
+ }
+
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = provider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ return provider.refresh(mContext.getAttributionSource(), url, extras,
+ remoteCancellationSignal);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return false;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Perform a detailed internal check on a {@link Uri} to determine if a UID
+ * is able to access it with specific mode flags.
+ * <p>
+ * This method is typically used when the provider implements more dynamic
+ * access controls that cannot be expressed with {@code <path-permission>}
+ * style static rules.
+ * <p>
+ * Because validation of these dynamic access controls has significant
+ * system health impact, this feature is only available to providers that
+ * are built into the system.
+ *
+ * @param uri the {@link Uri} to perform an access check on.
+ * @param uid the UID to check the permission for.
+ * @param modeFlags the access flags to use for the access check, such as
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+ * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
+ * otherwise {@link PackageManager#PERMISSION_DENIED}.
+ * @hide
+ */
+ @Override
+ @SystemApi
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
+ Objects.requireNonNull(uri, "uri");
+
+ try {
+ if (mWrapped != null) return mWrapped.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ try (ContentProviderClient client = acquireUnstableContentProviderClient(uri)) {
+ return client.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * Open a stream on to the content associated with a content URI. If there
+ * is no data associated with the URI, FileNotFoundException is thrown.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
+ * @param uri The desired URI.
+ * @return InputStream or {@code null} if the provider recently crashed.
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ * @see #openAssetFileDescriptor(Uri, String)
+ */
+ public final @Nullable InputStream openInputStream(@NonNull Uri uri)
+ throws FileNotFoundException {
+ Objects.requireNonNull(uri, "uri");
+ String scheme = uri.getScheme();
+ if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ // Note: left here to avoid breaking compatibility. May be removed
+ // with sufficient testing.
+ OpenResourceIdResult r = getResourceId(uri);
+ try {
+ InputStream stream = r.r.openRawResource(r.id);
+ return stream;
+ } catch (Resources.NotFoundException ex) {
+ throw new FileNotFoundException("Resource does not exist: " + uri);
+ }
+ } else if (SCHEME_FILE.equals(scheme)) {
+ // Note: left here to avoid breaking compatibility. May be removed
+ // with sufficient testing.
+ return new FileInputStream(uri.getPath());
+ } else {
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r", null);
+ try {
+ return fd != null ? fd.createInputStream() : null;
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
+ }
+ }
+ }
+
+ /**
+ * Synonym for {@link #openOutputStream(Uri, String)
+ * openOutputStream(uri, "w")}.
+ *
+ * @param uri The desired URI.
+ * @return an OutputStream or {@code null} if the provider recently crashed.
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ */
+ public final @Nullable OutputStream openOutputStream(@NonNull Uri uri)
+ throws FileNotFoundException {
+ return openOutputStream(uri, "w");
+ }
+
+ /**
+ * Open a stream on to the content associated with a content URI. If there
+ * is no data associated with the URI, FileNotFoundException is thrown.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
+ * @param uri The desired URI.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @return an OutputStream or {@code null} if the provider recently crashed.
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ * @see #openAssetFileDescriptor(Uri, String)
+ */
+ public final @Nullable OutputStream openOutputStream(@NonNull Uri uri, @NonNull String mode)
+ throws FileNotFoundException {
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode, null);
+ try {
+ return fd != null ? fd.createOutputStream() : null;
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
+ }
+ }
+
+ @Override
+ public final @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws FileNotFoundException {
+ try {
+ if (mWrapped != null) return mWrapped.openFile(uri, mode, signal);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ return openFileDescriptor(uri, mode, signal);
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a URI. This
+ * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
+ * underlying {@link ContentProvider#openFile}
+ * ContentProvider.openFile()} method, so will <em>not</em> work with
+ * providers that return sub-sections of files. If at all possible,
+ * you should use {@link #openAssetFileDescriptor(Uri, String)}. You
+ * will receive a FileNotFoundException exception if the provider returns a
+ * sub-section of a file.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ * <p>
+ * If opening with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor could be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" mode implies a file on disk that supports
+ * seeking. If possible, always use an exclusive mode to give the underlying
+ * {@link ContentProvider} the most flexibility.
+ * <p>
+ * If you are writing a file, and need to communicate an error to the
+ * provider, use {@link ParcelFileDescriptor#closeWithError(String)}.
+ *
+ * @param uri The desired URI to open.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the
+ * provider recently crashed. You own this descriptor and are responsible for closing it
+ * when done.
+ * @throws FileNotFoundException Throws FileNotFoundException if no
+ * file exists under the URI or the mode is invalid.
+ * @see #openAssetFileDescriptor(Uri, String)
+ */
+ public final @Nullable ParcelFileDescriptor openFileDescriptor(@NonNull Uri uri,
+ @NonNull String mode) throws FileNotFoundException {
+ return openFileDescriptor(uri, mode, null);
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a URI. This
+ * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
+ * underlying {@link ContentProvider#openFile}
+ * ContentProvider.openFile()} method, so will <em>not</em> work with
+ * providers that return sub-sections of files. If at all possible,
+ * you should use {@link #openAssetFileDescriptor(Uri, String)}. You
+ * will receive a FileNotFoundException exception if the provider returns a
+ * sub-section of a file.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ * <p>
+ * If opening with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor could be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" mode implies a file on disk that supports
+ * seeking. If possible, always use an exclusive mode to give the underlying
+ * {@link ContentProvider} the most flexibility.
+ * <p>
+ * If you are writing a file, and need to communicate an error to the
+ * provider, use {@link ParcelFileDescriptor#closeWithError(String)}.
+ *
+ * @param uri The desired URI to open.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or null if none. If the operation is canceled, then
+ * {@link OperationCanceledException} will be thrown.
+ * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the
+ * provider recently crashed. You own this descriptor and are responsible for closing it
+ * when done.
+ * @throws FileNotFoundException Throws FileNotFoundException if no
+ * file exists under the URI or the mode is invalid.
+ * @see #openAssetFileDescriptor(Uri, String)
+ */
+ public final @Nullable ParcelFileDescriptor openFileDescriptor(@NonNull Uri uri,
+ @NonNull String mode, @Nullable CancellationSignal cancellationSignal)
+ throws FileNotFoundException {
+ try {
+ if (mWrapped != null) return mWrapped.openFile(uri, mode, cancellationSignal);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode, cancellationSignal);
+ if (afd == null) {
+ return null;
+ }
+
+ if (afd.getDeclaredLength() < 0) {
+ // This is a full file!
+ return afd.getParcelFileDescriptor();
+ }
+
+ // Client can't handle a sub-section of a file, so close what
+ // we got and bail with an exception.
+ try {
+ afd.close();
+ } catch (IOException e) {
+ }
+
+ throw new FileNotFoundException("Not a whole file");
+ }
+
+ @Override
+ public final @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws FileNotFoundException {
+ try {
+ if (mWrapped != null) return mWrapped.openAssetFile(uri, mode, signal);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ return openAssetFileDescriptor(uri, mode, signal);
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a URI. This
+ * interacts with the underlying {@link ContentProvider#openAssetFile}
+ * method of the provider associated with the given URI, to retrieve any file stored there.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
+ * <p>
+ * A Uri object can be used to reference a resource in an APK file. The
+ * Uri should be one of the following formats:
+ * <ul>
+ * <li><code>android.resource://package_name/id_number</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>id_number</code> is the int form of the ID.<br/>
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
+ * </li>
+ * <li><code>android.resource://package_name/type/name</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
+ * or <code>drawable</code>.
+ * <code>name</code> is the string form of the resource name. That is, whatever the file
+ * name was in your res directory, without the type extension.
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
+ * </li>
+ * </ul>
+ *
+ * <p>Note that if this function is called for read-only input (mode is "r")
+ * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+ * for you with a MIME type of "*/*". This allows such callers to benefit
+ * from any built-in data conversion that a provider implements.
+ *
+ * @param uri The desired URI to open.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the
+ * provider recently crashed. You own this descriptor and are responsible for closing it
+ * when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * file exists under the URI or the mode is invalid.
+ */
+ public final @Nullable AssetFileDescriptor openAssetFileDescriptor(@NonNull Uri uri,
+ @NonNull String mode) throws FileNotFoundException {
+ return openAssetFileDescriptor(uri, mode, null);
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a URI. This
+ * interacts with the underlying {@link ContentProvider#openAssetFile}
+ * method of the provider associated with the given URI, to retrieve any file stored there.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
+ * <p>
+ * A Uri object can be used to reference a resource in an APK file. The
+ * Uri should be one of the following formats:
+ * <ul>
+ * <li><code>android.resource://package_name/id_number</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>id_number</code> is the int form of the ID.<br/>
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
+ * </li>
+ * <li><code>android.resource://package_name/type/name</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
+ * or <code>drawable</code>.
+ * <code>name</code> is the string form of the resource name. That is, whatever the file
+ * name was in your res directory, without the type extension.
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
+ * </li>
+ * </ul>
+ *
+ * <p>Note that if this function is called for read-only input (mode is "r")
+ * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+ * for you with a MIME type of "*/*". This allows such callers to benefit
+ * from any built-in data conversion that a provider implements.
+ *
+ * @param uri The desired URI to open.
+ * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+ * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * @param cancellationSignal A signal to cancel the operation in progress, or null if
+ * none. If the operation is canceled, then
+ * {@link OperationCanceledException} will be thrown.
+ * @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the
+ * provider recently crashed. You own this descriptor and are responsible for closing it
+ * when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * file exists under the URI or the mode is invalid.
+ */
+ public final @Nullable AssetFileDescriptor openAssetFileDescriptor(@NonNull Uri uri,
+ @NonNull String mode, @Nullable CancellationSignal cancellationSignal)
+ throws FileNotFoundException {
+ Objects.requireNonNull(uri, "uri");
+ Objects.requireNonNull(mode, "mode");
+
+ try {
+ if (mWrapped != null) return mWrapped.openAssetFile(uri, mode, cancellationSignal);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ String scheme = uri.getScheme();
+ if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Can't write resources: " + uri);
+ }
+ OpenResourceIdResult r = getResourceId(uri);
+ try {
+ return r.r.openRawResourceFd(r.id);
+ } catch (Resources.NotFoundException ex) {
+ throw new FileNotFoundException("Resource does not exist: " + uri);
+ }
+ } else if (SCHEME_FILE.equals(scheme)) {
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+ new File(uri.getPath()), ParcelFileDescriptor.parseMode(mode));
+ return new AssetFileDescriptor(pfd, 0, -1);
+ } else {
+ if ("r".equals(mode)) {
+ return openTypedAssetFileDescriptor(uri, "*/*", null, cancellationSignal);
+ } else {
+ IContentProvider unstableProvider = acquireUnstableProvider(uri);
+ if (unstableProvider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ IContentProvider stableProvider = null;
+ AssetFileDescriptor fd = null;
+
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = unstableProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+
+ try {
+ fd = unstableProvider.openAssetFile(
+ mContext.getAttributionSource(), uri, mode,
+ remoteCancellationSignal);
+ if (fd == null) {
+ // The provider will be released by the finally{} clause
+ return null;
+ }
+ } catch (DeadObjectException e) {
+ // The remote process has died... but we only hold an unstable
+ // reference though, so we might recover!!! Let's try!!!!
+ // This is exciting!!1!!1!!!!1
+ unstableProviderDied(unstableProvider);
+ stableProvider = acquireProvider(uri);
+ if (stableProvider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ fd = stableProvider.openAssetFile(mContext.getAttributionSource(),
+ uri, mode, remoteCancellationSignal);
+ if (fd == null) {
+ // The provider will be released by the finally{} clause
+ return null;
+ }
+ }
+
+ if (stableProvider == null) {
+ stableProvider = acquireProvider(uri);
+ }
+ releaseUnstableProvider(unstableProvider);
+ unstableProvider = null;
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), stableProvider);
+
+ // Success! Don't release the provider when exiting, let
+ // ParcelFileDescriptorInner do that when it is closed.
+ stableProvider = null;
+
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+
+ } catch (RemoteException e) {
+ // Whatever, whatever, we'll go away.
+ throw new FileNotFoundException(
+ "Failed opening content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(null);
+ }
+ if (stableProvider != null) {
+ releaseProvider(stableProvider);
+ }
+ if (unstableProvider != null) {
+ releaseUnstableProvider(unstableProvider);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public final @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+ @NonNull String mimeTypeFilter, @Nullable Bundle opts,
+ @Nullable CancellationSignal signal) throws FileNotFoundException {
+ try {
+ if (mWrapped != null) {
+ return mWrapped.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
+ }
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ return openTypedAssetFileDescriptor(uri, mimeTypeFilter, opts, signal);
+ }
+
+ /**
+ * Open a raw file descriptor to access (potentially type transformed)
+ * data from a "content:" URI. This interacts with the underlying
+ * {@link ContentProvider#openTypedAssetFile} method of the provider
+ * associated with the given URI, to retrieve retrieve any appropriate
+ * data stream for the data stored there.
+ *
+ * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+ * with "content:" URIs, because content providers are the only facility
+ * with an associated MIME type to ensure that the returned data stream
+ * is of the desired type.
+ *
+ * <p>All text/* streams are encoded in UTF-8.
+ *
+ * @param uri The desired URI to open.
+ * @param mimeType The desired MIME type of the returned data. This can
+ * be a pattern such as */*, which will allow the content provider to
+ * select a type, though there is no way for you to determine what type
+ * it is returning.
+ * @param opts Additional provider-dependent options.
+ * @return Returns a new ParcelFileDescriptor from which you can read the
+ * data stream from the provider or {@code null} if the provider recently crashed.
+ * Note that this may be a pipe, meaning you can't seek in it. The only seek you
+ * should do is if the AssetFileDescriptor contains an offset, to move to that offset before
+ * reading. You own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * data of the desired type exists under the URI.
+ */
+ public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
+ @NonNull String mimeType, @Nullable Bundle opts) throws FileNotFoundException {
+ return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
+ }
+
+ /**
+ * Open a raw file descriptor to access (potentially type transformed)
+ * data from a "content:" URI. This interacts with the underlying
+ * {@link ContentProvider#openTypedAssetFile} method of the provider
+ * associated with the given URI, to retrieve retrieve any appropriate
+ * data stream for the data stored there.
+ *
+ * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+ * with "content:" URIs, because content providers are the only facility
+ * with an associated MIME type to ensure that the returned data stream
+ * is of the desired type.
+ *
+ * <p>All text/* streams are encoded in UTF-8.
+ *
+ * @param uri The desired URI to open.
+ * @param mimeType The desired MIME type of the returned data. This can
+ * be a pattern such as */*, which will allow the content provider to
+ * select a type, though there is no way for you to determine what type
+ * it is returning.
+ * @param opts Additional provider-dependent options.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or null if none. If the operation is canceled, then
+ * {@link OperationCanceledException} will be thrown.
+ * @return Returns a new ParcelFileDescriptor from which you can read the
+ * data stream from the provider or {@code null} if the provider recently crashed.
+ * Note that this may be a pipe, meaning you can't seek in it. The only seek you
+ * should do is if the AssetFileDescriptor contains an offset, to move to that offset before
+ * reading. You own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * data of the desired type exists under the URI.
+ */
+ public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
+ @NonNull String mimeType, @Nullable Bundle opts,
+ @Nullable CancellationSignal cancellationSignal) throws FileNotFoundException {
+ Objects.requireNonNull(uri, "uri");
+ Objects.requireNonNull(mimeType, "mimeType");
+
+ try {
+ if (mWrapped != null) return mWrapped.openTypedAssetFile(uri, mimeType, opts, cancellationSignal);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider unstableProvider = acquireUnstableProvider(uri);
+ if (unstableProvider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ IContentProvider stableProvider = null;
+ AssetFileDescriptor fd = null;
+
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = unstableProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+
+ try {
+ fd = unstableProvider.openTypedAssetFile(
+ mContext.getAttributionSource(), uri, mimeType, opts,
+ remoteCancellationSignal);
+ if (fd == null) {
+ // The provider will be released by the finally{} clause
+ return null;
+ }
+ } catch (DeadObjectException e) {
+ // The remote process has died... but we only hold an unstable
+ // reference though, so we might recover!!! Let's try!!!!
+ // This is exciting!!1!!1!!!!1
+ unstableProviderDied(unstableProvider);
+ stableProvider = acquireProvider(uri);
+ if (stableProvider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ fd = stableProvider.openTypedAssetFile(
+ mContext.getAttributionSource(), uri, mimeType, opts,
+ remoteCancellationSignal);
+ if (fd == null) {
+ // The provider will be released by the finally{} clause
+ return null;
+ }
+ }
+
+ if (stableProvider == null) {
+ stableProvider = acquireProvider(uri);
+ }
+ releaseUnstableProvider(unstableProvider);
+ unstableProvider = null;
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), stableProvider);
+
+ // Success! Don't release the provider when exiting, let
+ // ParcelFileDescriptorInner do that when it is closed.
+ stableProvider = null;
+
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength(), fd.getExtras());
+
+ } catch (RemoteException e) {
+ // Whatever, whatever, we'll go away.
+ throw new FileNotFoundException(
+ "Failed opening content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(null);
+ }
+ if (stableProvider != null) {
+ releaseProvider(stableProvider);
+ }
+ if (unstableProvider != null) {
+ releaseUnstableProvider(unstableProvider);
+ }
+ }
+ }
+
+ /**
+ * A resource identified by the {@link Resources} that contains it, and a resource id.
+ *
+ * @hide
+ */
+ public class OpenResourceIdResult {
+ @UnsupportedAppUsage
+ public Resources r;
+ @UnsupportedAppUsage
+ public int id;
+ }
+
+ /**
+ * Resolves an android.resource URI to a {@link Resources} and a resource id.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
+ String authority = uri.getAuthority();
+ Resources r;
+ if (TextUtils.isEmpty(authority)) {
+ throw new FileNotFoundException("No authority: " + uri);
+ } else {
+ try {
+ r = mContext.getPackageManager().getResourcesForApplication(authority);
+ } catch (NameNotFoundException ex) {
+ throw new FileNotFoundException("No package found for authority: " + uri);
+ }
+ }
+ List<String> path = uri.getPathSegments();
+ if (path == null) {
+ throw new FileNotFoundException("No path: " + uri);
+ }
+ int len = path.size();
+ int id;
+ if (len == 1) {
+ try {
+ id = Integer.parseInt(path.get(0));
+ } catch (NumberFormatException e) {
+ throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
+ }
+ } else if (len == 2) {
+ id = r.getIdentifier(path.get(1), path.get(0), authority);
+ } else {
+ throw new FileNotFoundException("More than two path segments: " + uri);
+ }
+ if (id == 0) {
+ throw new FileNotFoundException("No resource found for: " + uri);
+ }
+ OpenResourceIdResult res = new OpenResourceIdResult();
+ res.r = r;
+ res.id = id;
+ return res;
+ }
+
+ /**
+ * Inserts a row into a table at the given URL.
+ *
+ * If the content provider supports transactions the insertion will be atomic.
+ *
+ * @param url The URL of the table to insert into.
+ * @param values The initial values for the newly inserted row. The key is the column name for
+ * the field. Passing an empty ContentValues will create an empty row.
+ * @return the URL of the newly created row. May return <code>null</code> if the underlying
+ * content provider returns <code>null</code>, or if it crashes.
+ */
+ public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
+ @Nullable ContentValues values) {
+ return insert(url, values, null);
+ }
+
+ /**
+ * Inserts a row into a table at the given URL.
+ *
+ * If the content provider supports transactions the insertion will be atomic.
+ *
+ * @param url The URL of the table to insert into.
+ * @param values The initial values for the newly inserted row. The key is the column name for
+ * the field. Passing an empty ContentValues will create an empty row.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return the URL of the newly created row. May return <code>null</code> if the underlying
+ * content provider returns <code>null</code>, or if it crashes.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
+ @Nullable ContentValues values, @Nullable Bundle extras) {
+ Objects.requireNonNull(url, "url");
+
+ try {
+ if (mWrapped != null) return mWrapped.insert(url, values, extras);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ Uri createdRow = provider.insert(mContext.getAttributionSource(), url, values, extras);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
+ return createdRow;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Applies each of the {@link ContentProviderOperation} objects and returns an array
+ * of their results. Passes through OperationApplicationException, which may be thrown
+ * by the call to {@link ContentProviderOperation#apply}.
+ * If all the applications succeed then a {@link ContentProviderResult} array with the
+ * same number of elements as the operations will be returned. It is implementation-specific
+ * how many, if any, operations will have been successfully applied if a call to
+ * apply results in a {@link OperationApplicationException}.
+ * @param authority the authority of the ContentProvider to which this batch should be applied
+ * @param operations the operations to apply
+ * @return the results of the applications
+ * @throws OperationApplicationException thrown if an application fails.
+ * See {@link ContentProviderOperation#apply} for more information.
+ * @throws RemoteException thrown if a RemoteException is encountered while attempting
+ * to communicate with a remote provider.
+ */
+ @Override
+ public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ Objects.requireNonNull(authority, "authority");
+ Objects.requireNonNull(operations, "operations");
+
+ try {
+ if (mWrapped != null) return mWrapped.applyBatch(authority, operations);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ ContentProviderClient provider = acquireContentProviderClient(authority);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown authority " + authority);
+ }
+ try {
+ return provider.applyBatch(operations);
+ } finally {
+ provider.release();
+ }
+ }
+
+ /**
+ * Inserts multiple rows into a table at the given URL.
+ *
+ * This function make no guarantees about the atomicity of the insertions.
+ *
+ * @param url The URL of the table to insert into.
+ * @param values The initial values for the newly inserted rows. The key is the column name for
+ * the field. Passing null will create an empty row.
+ * @return the number of newly created rows.
+ */
+ @Override
+ public final int bulkInsert(@RequiresPermission.Write @NonNull Uri url,
+ @NonNull ContentValues[] values) {
+ Objects.requireNonNull(url, "url");
+ Objects.requireNonNull(values, "values");
+
+ try {
+ if (mWrapped != null) return mWrapped.bulkInsert(url, values);
+ } catch (RemoteException e) {
+ return 0;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ int rowsCreated = provider.bulkInsert(mContext.getAttributionSource(), url, values);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
+ return rowsCreated;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return 0;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Deletes row(s) specified by a content URI.
+ *
+ * If the content provider supports transactions, the deletion will be atomic.
+ *
+ * @param url The URL of the row to delete.
+ * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause
+ (excluding the WHERE itself).
+ * @return The number of rows deleted.
+ */
+ public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where,
+ @Nullable String[] selectionArgs) {
+ return delete(url, createSqlQueryBundle(where, selectionArgs));
+ }
+
+ /**
+ * Deletes row(s) specified by a content URI.
+ *
+ * If the content provider supports transactions, the deletion will be atomic.
+ *
+ * @param url The URL of the row to delete.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return The number of rows deleted.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable Bundle extras) {
+ Objects.requireNonNull(url, "url");
+
+ try {
+ if (mWrapped != null) return mWrapped.delete(url, extras);
+ } catch (RemoteException e) {
+ return 0;
+ }
+
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ int rowsDeleted = provider.delete(mContext.getAttributionSource(), url, extras);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "delete", null);
+ return rowsDeleted;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return -1;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Update row(s) in a content URI.
+ *
+ * If the content provider supports transactions the update will be atomic.
+ *
+ * @param uri The URI to modify.
+ * @param values The new field values. The key is the column name for the field.
+ A null value will remove an existing field value.
+ * @param where A filter to apply to rows before updating, formatted as an SQL WHERE clause
+ (excluding the WHERE itself).
+ * @return the number of rows updated.
+ * @throws NullPointerException if uri or values are null
+ */
+ public final int update(@RequiresPermission.Write @NonNull Uri uri,
+ @Nullable ContentValues values, @Nullable String where,
+ @Nullable String[] selectionArgs) {
+ return update(uri, values, createSqlQueryBundle(where, selectionArgs));
+ }
+
+ /**
+ * Update row(s) in a content URI.
+ *
+ * If the content provider supports transactions the update will be atomic.
+ *
+ * @param uri The URI to modify.
+ * @param values The new field values. The key is the column name for the field.
+ A null value will remove an existing field value.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @return the number of rows updated.
+ * @throws NullPointerException if uri or values are null
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
+ */
+ @Override
+ public final int update(@RequiresPermission.Write @NonNull Uri uri,
+ @Nullable ContentValues values, @Nullable Bundle extras) {
+ Objects.requireNonNull(uri, "uri");
+
+ try {
+ if (mWrapped != null) return mWrapped.update(uri, values, extras);
+ } catch (RemoteException e) {
+ return 0;
+ }
+
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ int rowsUpdated = provider.update(mContext.getAttributionSource(),
+ uri, values, extras);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, uri, "update", null);
+ return rowsUpdated;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return -1;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Call a provider-defined method. This can be used to implement
+ * read or write interfaces which are cheaper than using a Cursor and/or
+ * do not fit into the traditional table model.
+ *
+ * @param method provider-defined method name to call. Opaque to
+ * framework, but must be non-null.
+ * @param arg provider-defined String argument. May be null.
+ * @param extras provider-defined Bundle argument. May be null.
+ * @return a result Bundle, possibly null. Will be null if the ContentProvider
+ * does not implement call.
+ * @throws NullPointerException if uri or method is null
+ * @throws IllegalArgumentException if uri is not known
+ */
+ public final @Nullable Bundle call(@NonNull Uri uri, @NonNull String method,
+ @Nullable String arg, @Nullable Bundle extras) {
+ return call(uri.getAuthority(), method, arg, extras);
+ }
+
+ @Override
+ public final @Nullable Bundle call(@NonNull String authority, @NonNull String method,
+ @Nullable String arg, @Nullable Bundle extras) {
+ Objects.requireNonNull(authority, "authority");
+ Objects.requireNonNull(method, "method");
+
+ try {
+ if (mWrapped != null) return mWrapped.call(authority, method, arg, extras);
+ } catch (RemoteException e) {
+ return null;
+ }
+
+ IContentProvider provider = acquireProvider(authority);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown authority " + authority);
+ }
+ try {
+ final Bundle res = provider.call(mContext.getAttributionSource(),
+ authority, method, arg, extras);
+ Bundle.setDefusable(res, true);
+ return res;
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Returns the content provider for the given content URI.
+ *
+ * @param uri The URI to a content provider
+ * @return The ContentProvider for the given URI, or null if no content provider is found.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final IContentProvider acquireProvider(Uri uri) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+ return null;
+ }
+ final String auth = uri.getAuthority();
+ if (auth != null) {
+ return acquireProvider(mContext, auth);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the content provider for the given content URI if the process
+ * already has a reference on it.
+ *
+ * @param uri The URI to a content provider
+ * @return The ContentProvider for the given URI, or null if no content provider is found.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final IContentProvider acquireExistingProvider(Uri uri) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+ return null;
+ }
+ final String auth = uri.getAuthority();
+ if (auth != null) {
+ return acquireExistingProvider(mContext, auth);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final IContentProvider acquireProvider(String name) {
+ if (name == null) {
+ return null;
+ }
+ return acquireProvider(mContext, name);
+ }
+
+ /**
+ * Returns the content provider for the given content URI.
+ *
+ * @param uri The URI to a content provider
+ * @return The ContentProvider for the given URI, or null if no content provider is found.
+ * @hide
+ */
+ public final IContentProvider acquireUnstableProvider(Uri uri) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+ return null;
+ }
+ String auth = uri.getAuthority();
+ if (auth != null) {
+ return acquireUnstableProvider(mContext, uri.getAuthority());
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final IContentProvider acquireUnstableProvider(String name) {
+ if (name == null) {
+ return null;
+ }
+ return acquireUnstableProvider(mContext, name);
+ }
+
+ /**
+ * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * that services the content at uri, starting the provider if necessary. Returns
+ * null if there is no provider associated wih the uri. The caller must indicate that they are
+ * done with the provider by calling {@link ContentProviderClient#release} which will allow
+ * the system to release the provider if it determines that there is no other reason for
+ * keeping it active.
+ * @param uri specifies which provider should be acquired
+ * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * that services the content at uri or null if there isn't one.
+ */
+ public final @Nullable ContentProviderClient acquireContentProviderClient(@NonNull Uri uri) {
+ Objects.requireNonNull(uri, "uri");
+ IContentProvider provider = acquireProvider(uri);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, uri.getAuthority(), true);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * with the authority of name, starting the provider if necessary. Returns
+ * null if there is no provider associated wih the uri. The caller must indicate that they are
+ * done with the provider by calling {@link ContentProviderClient#release} which will allow
+ * the system to release the provider if it determines that there is no other reason for
+ * keeping it active.
+ * @param name specifies which provider should be acquired
+ * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * with the authority of name or null if there isn't one.
+ */
+ public final @Nullable ContentProviderClient acquireContentProviderClient(
+ @NonNull String name) {
+ Objects.requireNonNull(name, "name");
+ IContentProvider provider = acquireProvider(name);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, name, true);
+ }
+
+ return null;
+ }
+
+ /**
+ * Like {@link #acquireContentProviderClient(Uri)}, but for use when you do
+ * not trust the stability of the target content provider. This turns off
+ * the mechanism in the platform clean up processes that are dependent on
+ * a content provider if that content provider's process goes away. Normally
+ * you can safely assume that once you have acquired a provider, you can freely
+ * use it as needed and it won't disappear, even if your process is in the
+ * background. If using this method, you need to take care to deal with any
+ * failures when communicating with the provider, and be sure to close it
+ * so that it can be re-opened later. In particular, catching a
+ * {@link android.os.DeadObjectException} from the calls there will let you
+ * know that the content provider has gone away; at that point the current
+ * ContentProviderClient object is invalid, and you should release it. You
+ * can acquire a new one if you would like to try to restart the provider
+ * and perform new operations on it.
+ */
+ public final @Nullable ContentProviderClient acquireUnstableContentProviderClient(
+ @NonNull Uri uri) {
+ Objects.requireNonNull(uri, "uri");
+ IContentProvider provider = acquireUnstableProvider(uri);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, uri.getAuthority(), false);
+ }
+
+ return null;
+ }
+
+ /**
+ * Like {@link #acquireContentProviderClient(String)}, but for use when you do
+ * not trust the stability of the target content provider. This turns off
+ * the mechanism in the platform clean up processes that are dependent on
+ * a content provider if that content provider's process goes away. Normally
+ * you can safely assume that once you have acquired a provider, you can freely
+ * use it as needed and it won't disappear, even if your process is in the
+ * background. If using this method, you need to take care to deal with any
+ * failures when communicating with the provider, and be sure to close it
+ * so that it can be re-opened later. In particular, catching a
+ * {@link android.os.DeadObjectException} from the calls there will let you
+ * know that the content provider has gone away; at that point the current
+ * ContentProviderClient object is invalid, and you should release it. You
+ * can acquire a new one if you would like to try to restart the provider
+ * and perform new operations on it.
+ */
+ public final @Nullable ContentProviderClient acquireUnstableContentProviderClient(
+ @NonNull String name) {
+ Objects.requireNonNull(name, "name");
+ IContentProvider provider = acquireUnstableProvider(name);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, name, false);
+ }
+
+ return null;
+ }
+
+ /**
+ * Register an observer class that gets callbacks when data identified by a
+ * given content URI changes.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+ * notifications must be backed by a valid {@link ContentProvider}.
+ *
+ * @param uri The URI to watch for changes. This can be a specific row URI,
+ * or a base URI for a whole class of content.
+ * @param notifyForDescendants When false, the observer will be notified
+ * whenever a change occurs to the exact URI specified by
+ * <code>uri</code> or to one of the URI's ancestors in the path
+ * hierarchy. When true, the observer will also be notified
+ * whenever a change occurs to the URI's descendants in the path
+ * hierarchy.
+ * @param observer The object that receives callbacks when changes occur.
+ * @see #unregisterContentObserver
+ */
+ public final void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
+ @NonNull ContentObserver observer) {
+ Objects.requireNonNull(uri, "uri");
+ Objects.requireNonNull(observer, "observer");
+ registerContentObserver(
+ ContentProvider.getUriWithoutUserId(uri),
+ notifyForDescendants,
+ observer,
+ ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+ }
+
+ /** @hide - designated user version */
+ @UnsupportedAppUsage
+ public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ ContentObserver observer, @UserIdInt int userHandle) {
+ try {
+ getContentService().registerContentObserver(uri, notifyForDescendents,
+ observer.getContentObserver(), userHandle, mTargetSdkVersion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a change observer.
+ *
+ * @param observer The previously registered observer that is no longer needed.
+ * @see #registerContentObserver
+ */
+ public final void unregisterContentObserver(@NonNull ContentObserver observer) {
+ Objects.requireNonNull(observer, "observer");
+ try {
+ IContentObserver contentObserver = observer.releaseContentObserver();
+ if (contentObserver != null) {
+ getContentService().unregisterContentObserver(
+ contentObserver);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify registered observers that a row was updated and attempt to sync
+ * changes to the network.
+ * <p>
+ * To observe events sent through this call, use
+ * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+ * notifications must be backed by a valid {@link ContentProvider}.
+ *
+ * @param uri The uri of the content that was changed.
+ * @param observer The observer that originated the change, may be
+ * <code>null</null>. The observer that originated the change
+ * will only receive the notification if it has requested to
+ * receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return
+ * true.
+ */
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer) {
+ notifyChange(uri, observer, true /* sync to network */);
+ }
+
+ /**
+ * Notify registered observers that a row was updated.
+ * <p>
+ * To observe events sent through this call, use
+ * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+ * <p>
+ * If syncToNetwork is true, this will attempt to schedule a local sync
+ * using the sync adapter that's registered for the authority of the
+ * provided uri. No account will be passed to the sync adapter, so all
+ * matching accounts will be synchronized.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+ * notifications must be backed by a valid {@link ContentProvider}.
+ *
+ * @param uri The uri of the content that was changed.
+ * @param observer The observer that originated the change, may be
+ * <code>null</null>. The observer that originated the change
+ * will only receive the notification if it has requested to
+ * receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return
+ * true.
+ * @param syncToNetwork If true, same as {@link #NOTIFY_SYNC_TO_NETWORK}.
+ * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
+ * @deprecated callers should consider migrating to
+ * {@link #notifyChange(Uri, ContentObserver, int)}, as it
+ * offers support for many more options than just
+ * {@link #NOTIFY_SYNC_TO_NETWORK}.
+ */
+ @Deprecated
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
+ boolean syncToNetwork) {
+ notifyChange(uri, observer, syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0);
+ }
+
+ /**
+ * Notify registered observers that a row was updated.
+ * <p>
+ * To observe events sent through this call, use
+ * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+ * <p>
+ * If {@link #NOTIFY_SYNC_TO_NETWORK} is set, this will attempt to schedule
+ * a local sync using the sync adapter that's registered for the authority
+ * of the provided uri. No account will be passed to the sync adapter, so
+ * all matching accounts will be synchronized.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+ * notifications must be backed by a valid {@link ContentProvider}.
+ *
+ * @param uri The uri of the content that was changed.
+ * @param observer The observer that originated the change, may be
+ * <code>null</null>. The observer that originated the change
+ * will only receive the notification if it has requested to
+ * receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return
+ * true.
+ * @param flags Additional flags: {@link #NOTIFY_SYNC_TO_NETWORK}.
+ * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
+ */
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ Objects.requireNonNull(uri, "uri");
+ notifyChange(
+ ContentProvider.getUriWithoutUserId(uri),
+ observer,
+ flags,
+ ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+ }
+
+ /** @removed */
+ @Deprecated
+ public void notifyChange(@NonNull Iterable<Uri> uris, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ final Collection<Uri> asCollection = new ArrayList<>();
+ uris.forEach(asCollection::add);
+ notifyChange(asCollection, observer, flags);
+ }
+
+ /**
+ * Notify registered observers that several rows have been updated.
+ * <p>
+ * To observe events sent through this call, use
+ * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+ * <p>
+ * If {@link #NOTIFY_SYNC_TO_NETWORK} is set, this will attempt to schedule
+ * a local sync using the sync adapter that's registered for the authority
+ * of the provided uri. No account will be passed to the sync adapter, so
+ * all matching accounts will be synchronized.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+ * notifications must be backed by a valid {@link ContentProvider}.
+ *
+ * @param uris The uris of the content that was changed.
+ * @param observer The observer that originated the change, may be
+ * <code>null</null>. The observer that originated the change
+ * will only receive the notification if it has requested to
+ * receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return
+ * true.
+ * @param flags Flags such as {@link #NOTIFY_SYNC_TO_NETWORK} or
+ * {@link #NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS}.
+ */
+ public void notifyChange(@NonNull Collection<Uri> uris, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ Objects.requireNonNull(uris, "uris");
+
+ // Cluster based on user ID
+ final SparseArray<ArrayList<Uri>> clusteredByUser = new SparseArray<>();
+ for (Uri uri : uris) {
+ final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+ ArrayList<Uri> list = clusteredByUser.get(userId);
+ if (list == null) {
+ list = new ArrayList<>();
+ clusteredByUser.put(userId, list);
+ }
+ list.add(ContentProvider.getUriWithoutUserId(uri));
+ }
+
+ for (int i = 0; i < clusteredByUser.size(); i++) {
+ final int userId = clusteredByUser.keyAt(i);
+ final ArrayList<Uri> list = clusteredByUser.valueAt(i);
+ notifyChange(list.toArray(new Uri[list.size()]), observer, flags, userId);
+ }
+ }
+
+ /**
+ * Notify registered observers within the designated user(s) that a row was updated.
+ *
+ * @deprecated callers should consider migrating to
+ * {@link #notifyChange(Uri, ContentObserver, int)}, as it
+ * offers support for many more options than just
+ * {@link #NOTIFY_SYNC_TO_NETWORK}.
+ * @hide
+ */
+ @Deprecated
+ public void notifyChange(@NonNull Uri uri, ContentObserver observer, boolean syncToNetwork,
+ @UserIdInt int userHandle) {
+ notifyChange(uri, observer, syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0, userHandle);
+ }
+
+ /** {@hide} */
+ public void notifyChange(@NonNull Uri uri, ContentObserver observer, @NotifyFlags int flags,
+ @UserIdInt int userHandle) {
+ notifyChange(new Uri[] { uri }, observer, flags, userHandle);
+ }
+
+ /**
+ * Notify registered observers within the designated user(s) that a row was updated.
+ *
+ * @hide
+ */
+ public void notifyChange(@NonNull Uri[] uris, ContentObserver observer, @NotifyFlags int flags,
+ @UserIdInt int userHandle) {
+ try {
+ getContentService().notifyChange(
+ uris, observer == null ? null : observer.getContentObserver(),
+ observer != null && observer.deliverSelfNotifications(), flags,
+ userHandle, mTargetSdkVersion, mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Take a persistable URI permission grant that has been offered. Once
+ * taken, the permission grant will be remembered across device reboots.
+ * Only URI permissions granted with
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} can be persisted. If
+ * the grant has already been persisted, taking it again will touch
+ * {@link UriPermission#getPersistedTime()}.
+ *
+ * @see #getPersistedUriPermissions()
+ */
+ public void takePersistableUriPermission(@NonNull Uri uri,
+ @Intent.AccessUriMode int modeFlags) {
+ Objects.requireNonNull(uri, "uri");
+ try {
+ UriGrantsManager.getService().takePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, /* toPackage= */ null,
+ resolveUserId(uri));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void takePersistableUriPermission(@NonNull String toPackage, @NonNull Uri uri,
+ @Intent.AccessUriMode int modeFlags) {
+ Objects.requireNonNull(toPackage, "toPackage");
+ Objects.requireNonNull(uri, "uri");
+ try {
+ UriGrantsManager.getService().takePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, toPackage,
+ resolveUserId(uri));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Relinquish a persisted URI permission grant. The URI must have been
+ * previously made persistent with
+ * {@link #takePersistableUriPermission(Uri, int)}. Any non-persistent
+ * grants to the calling package will remain intact.
+ *
+ * @see #getPersistedUriPermissions()
+ */
+ public void releasePersistableUriPermission(@NonNull Uri uri,
+ @Intent.AccessUriMode int modeFlags) {
+ Objects.requireNonNull(uri, "uri");
+ try {
+ UriGrantsManager.getService().releasePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, /* toPackage= */ null,
+ resolveUserId(uri));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return list of all URI permission grants that have been persisted by the
+ * calling app. That is, the returned permissions have been granted
+ * <em>to</em> the calling app. Only persistable grants taken with
+ * {@link #takePersistableUriPermission(Uri, int)} are returned.
+ * <p>Note: Some of the returned URIs may not be usable until after the user is unlocked.
+ *
+ * @see #takePersistableUriPermission(Uri, int)
+ * @see #releasePersistableUriPermission(Uri, int)
+ */
+ public @NonNull List<UriPermission> getPersistedUriPermissions() {
+ try {
+ return UriGrantsManager.getService().getUriPermissions(
+ mPackageName, true /* incoming */, true /* persistedOnly */).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return list of all persisted URI permission grants that are hosted by the
+ * calling app. That is, the returned permissions have been granted
+ * <em>from</em> the calling app. Only grants taken with
+ * {@link #takePersistableUriPermission(Uri, int)} are returned.
+ * <p>Note: Some of the returned URIs may not be usable until after the user is unlocked.
+ */
+ public @NonNull List<UriPermission> getOutgoingPersistedUriPermissions() {
+ try {
+ return UriGrantsManager.getService().getUriPermissions(
+ mPackageName, false /* incoming */, true /* persistedOnly */).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public @NonNull List<UriPermission> getOutgoingUriPermissions() {
+ try {
+ return UriGrantsManager.getService().getUriPermissions(
+ mPackageName, false /* incoming */, false /* persistedOnly */).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start an asynchronous sync operation. If you want to monitor the progress
+ * of the sync you may register a SyncObserver. Only values of the following
+ * types may be used in the extras bundle:
+ * <ul>
+ * <li>Integer</li>
+ * <li>Long</li>
+ * <li>Boolean</li>
+ * <li>Float</li>
+ * <li>Double</li>
+ * <li>String</li>
+ * <li>Account</li>
+ * <li>null</li>
+ * </ul>
+ *
+ * @param uri the uri of the provider to sync or null to sync all providers.
+ * @param extras any extras to pass to the SyncAdapter.
+ * @deprecated instead use
+ * {@link #requestSync(android.accounts.Account, String, android.os.Bundle)}
+ */
+ @Deprecated
+ public void startSync(Uri uri, Bundle extras) {
+ Account account = null;
+ if (extras != null) {
+ String accountName = extras.getString(SYNC_EXTRAS_ACCOUNT);
+ if (!TextUtils.isEmpty(accountName)) {
+ // TODO: No references to Google in AOSP
+ account = new Account(accountName, "com.google");
+ }
+ extras.remove(SYNC_EXTRAS_ACCOUNT);
+ }
+ requestSync(account, uri != null ? uri.getAuthority() : null, extras);
+ }
+
+ /**
+ * Start an asynchronous sync operation. If you want to monitor the progress
+ * of the sync you may register a SyncObserver. Only values of the following
+ * types may be used in the extras bundle:
+ * <ul>
+ * <li>Integer</li>
+ * <li>Long</li>
+ * <li>Boolean</li>
+ * <li>Float</li>
+ * <li>Double</li>
+ * <li>String</li>
+ * <li>Account</li>
+ * <li>null</li>
+ * </ul>
+ *
+ * @param account which account should be synced
+ * @param authority which authority should be synced
+ * @param extras any extras to pass to the SyncAdapter.
+ */
+ public static void requestSync(Account account, String authority, Bundle extras) {
+ requestSyncAsUser(account, authority, UserHandle.myUserId(), extras);
+ }
+
+ /**
+ * @see #requestSync(Account, String, Bundle)
+ * @hide
+ */
+ public static void requestSyncAsUser(Account account, String authority, @UserIdInt int userId,
+ Bundle extras) {
+ if (extras == null) {
+ throw new IllegalArgumentException("Must specify extras.");
+ }
+ SyncRequest request =
+ new SyncRequest.Builder()
+ .setSyncAdapter(account, authority)
+ .setExtras(extras)
+ .syncOnce() // Immediate sync.
+ .build();
+ try {
+ // Note ActivityThread.currentPackageName() may not be accurate in a shared process
+ // case, but it's only for debugging.
+ getContentService().syncAsUser(request, userId, ActivityThread.currentPackageName());
+ } catch(RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register a sync with the SyncManager. These requests are built using the
+ * {@link SyncRequest.Builder}.
+ */
+ public static void requestSync(SyncRequest request) {
+ try {
+ // Note ActivityThread.currentPackageName() may not be accurate in a shared process
+ // case, but it's only for debugging.
+ getContentService().sync(request, ActivityThread.currentPackageName());
+ } catch(RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check that only values of the following types are in the Bundle:
+ * <ul>
+ * <li>Integer</li>
+ * <li>Long</li>
+ * <li>Boolean</li>
+ * <li>Float</li>
+ * <li>Double</li>
+ * <li>String</li>
+ * <li>Account</li>
+ * <li>null</li>
+ * </ul>
+ * @param extras the Bundle to check
+ */
+ public static void validateSyncExtrasBundle(Bundle extras) {
+ try {
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ if (value == null) continue;
+ if (value instanceof Long) continue;
+ if (value instanceof Integer) continue;
+ if (value instanceof Boolean) continue;
+ if (value instanceof Float) continue;
+ if (value instanceof Double) continue;
+ if (value instanceof String) continue;
+ if (value instanceof Account) continue;
+ throw new IllegalArgumentException("unexpected value type: "
+ + value.getClass().getName());
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (RuntimeException exc) {
+ throw new IllegalArgumentException("error unparceling Bundle", exc);
+ }
+ }
+
+ /**
+ * Cancel any active or pending syncs that match the Uri. If the uri is null then
+ * all syncs will be canceled.
+ *
+ * @param uri the uri of the provider to sync or null to sync all providers.
+ * @deprecated instead use {@link #cancelSync(android.accounts.Account, String)}
+ */
+ @Deprecated
+ public void cancelSync(Uri uri) {
+ cancelSync(null /* all accounts */, uri != null ? uri.getAuthority() : null);
+ }
+
+ /**
+ * Cancel any active or pending syncs that match account and authority. The account and
+ * authority can each independently be set to null, which means that syncs with any account
+ * or authority, respectively, will match.
+ *
+ * @param account filters the syncs that match by this account
+ * @param authority filters the syncs that match by this authority
+ */
+ public static void cancelSync(Account account, String authority) {
+ try {
+ getContentService().cancelSync(account, authority, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #cancelSync(Account, String)
+ * @hide
+ */
+ public static void cancelSyncAsUser(Account account, String authority, @UserIdInt int userId) {
+ try {
+ getContentService().cancelSyncAsUser(account, authority, null, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get information about the SyncAdapters that are known to the system.
+ * @return an array of SyncAdapters that have registered with the system
+ */
+ public static SyncAdapterType[] getSyncAdapterTypes() {
+ try {
+ return getContentService().getSyncAdapterTypes();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #getSyncAdapterTypes()
+ * @hide
+ */
+ public static SyncAdapterType[] getSyncAdapterTypesAsUser(@UserIdInt int userId) {
+ try {
+ return getContentService().getSyncAdapterTypesAsUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the package names of syncadapters that match a given user and authority.
+ */
+ @TestApi
+ public static String[] getSyncAdapterPackagesForAuthorityAsUser(String authority,
+ @UserIdInt int userId) {
+ try {
+ return getContentService().getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if the provider should be synced when a network tickle is received
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
+ *
+ * @param account the account whose setting we are querying
+ * @param authority the provider whose setting we are querying
+ * @return true if the provider should be synced when a network tickle is received
+ */
+ public static boolean getSyncAutomatically(Account account, String authority) {
+ try {
+ return getContentService().getSyncAutomatically(account, authority);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #getSyncAutomatically(Account, String)
+ * @hide
+ */
+ public static boolean getSyncAutomaticallyAsUser(Account account, String authority,
+ @UserIdInt int userId) {
+ try {
+ return getContentService().getSyncAutomaticallyAsUser(account, authority, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether or not the provider is synced when it receives a network tickle.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ *
+ * @param account the account whose setting we are querying
+ * @param authority the provider whose behavior is being controlled
+ * @param sync true if the provider should be synced when tickles are received for it
+ */
+ public static void setSyncAutomatically(Account account, String authority, boolean sync) {
+ setSyncAutomaticallyAsUser(account, authority, sync, UserHandle.myUserId());
+ }
+
+ /**
+ * @see #setSyncAutomatically(Account, String, boolean)
+ * @hide
+ */
+ public static void setSyncAutomaticallyAsUser(Account account, String authority, boolean sync,
+ @UserIdInt int userId) {
+ try {
+ getContentService().setSyncAutomaticallyAsUser(account, authority, sync, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * {@hide}
+ * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
+ * extras were set for a sync scheduled as an expedited job.
+ *
+ * @param extras bundle to validate.
+ */
+ public static boolean hasInvalidScheduleAsEjExtras(Bundle extras) {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED);
+ }
+
+ /**
+ * Specifies that a sync should be requested with the specified the account, authority,
+ * and extras at the given frequency. If there is already another periodic sync scheduled
+ * with the account, authority and extras then a new periodic sync won't be added, instead
+ * the frequency of the previous one will be updated.
+ * <p>
+ * These periodic syncs honor the "syncAutomatically" and "masterSyncAutomatically" settings.
+ * Although these sync are scheduled at the specified frequency, it may take longer for it to
+ * actually be started if other syncs are ahead of it in the sync operation queue. This means
+ * that the actual start time may drift.
+ * <p>
+ * Periodic syncs are not allowed to have any of {@link #SYNC_EXTRAS_DO_NOT_RETRY},
+ * {@link #SYNC_EXTRAS_IGNORE_BACKOFF}, {@link #SYNC_EXTRAS_IGNORE_SETTINGS},
+ * {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE},
+ * {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL},
+ * {@link #SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB} set to true.
+ * If any are supplied then an {@link IllegalArgumentException} will be thrown.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ * <p>The bundle for a periodic sync can be queried by applications with the correct
+ * permissions using
+ * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
+ * sensitive data should be transferred here.
+ *
+ * @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.
+ * 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 (invalidPeriodicExtras(extras)) {
+ throw new IllegalArgumentException("illegal extras were set");
+ }
+ try {
+ getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * {@hide}
+ * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
+ * extras were set for a periodic sync.
+ *
+ * @param extras bundle to validate.
+ */
+ public static boolean invalidPeriodicExtras(Bundle extras) {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false);
+ }
+
+ /**
+ * Remove a periodic sync. Has no affect if account, authority and extras don't match
+ * an existing periodic sync.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ *
+ * @param account the account of the periodic sync to remove
+ * @param authority the provider of the periodic sync to remove
+ * @param extras the extras of the periodic sync to remove
+ */
+ public static void removePeriodicSync(Account account, String authority, Bundle extras) {
+ validateSyncExtrasBundle(extras);
+ try {
+ getContentService().removePeriodicSync(account, authority, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove the specified sync. This will cancel any pending or active syncs. If the request is
+ * for a periodic sync, this call will remove any future occurrences.
+ * <p>
+ * If a periodic sync is specified, the caller must hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ *</p>
+ * It is possible to cancel a sync using a SyncRequest object that is not the same object
+ * with which you requested the sync. Do so by building a SyncRequest with the same
+ * adapter, frequency, <b>and</b> extras bundle.
+ *
+ * @param request SyncRequest object containing information about sync to cancel.
+ */
+ public static void cancelSync(SyncRequest request) {
+ if (request == null) {
+ throw new IllegalArgumentException("request cannot be null");
+ }
+ try {
+ getContentService().cancelRequest(request);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the list of information about the periodic syncs for the given account and authority.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
+ *
+ * @param account the account whose periodic syncs we are querying
+ * @param authority the provider whose periodic syncs we are querying
+ * @return a list of PeriodicSync objects. This list may be empty but will never be null.
+ */
+ public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
+ try {
+ return getContentService().getPeriodicSyncs(account, authority, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if this account/provider is syncable.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
+ * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
+ */
+ public static int getIsSyncable(Account account, String authority) {
+ try {
+ return getContentService().getIsSyncable(account, authority);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #getIsSyncable(Account, String)
+ * @hide
+ */
+ public static int getIsSyncableAsUser(Account account, String authority,
+ @UserIdInt int userId) {
+ try {
+ return getContentService().getIsSyncableAsUser(account, authority, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether this account/provider is syncable.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ * @param syncable >0 denotes syncable, 0 means not syncable, <0 means unknown
+ */
+ public static void setIsSyncable(Account account, String authority, int syncable) {
+ try {
+ getContentService().setIsSyncable(account, authority, syncable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #setIsSyncable(Account, String, int)
+ * @hide
+ */
+ public static void setIsSyncableAsUser(Account account, String authority, int syncable,
+ int userId) {
+ try {
+ getContentService().setIsSyncableAsUser(account, authority, syncable, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the global auto-sync setting that applies to all the providers and accounts.
+ * If this is false then the per-provider auto-sync setting is ignored.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
+ *
+ * @return the global auto-sync setting that applies to all the providers and accounts
+ */
+ public static boolean getMasterSyncAutomatically() {
+ try {
+ return getContentService().getMasterSyncAutomatically();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #getMasterSyncAutomatically()
+ * @hide
+ */
+ public static boolean getMasterSyncAutomaticallyAsUser(@UserIdInt int userId) {
+ try {
+ return getContentService().getMasterSyncAutomaticallyAsUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the global auto-sync setting that applies to all the providers and accounts.
+ * If this is false then the per-provider auto-sync setting is ignored.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ *
+ * @param sync the global auto-sync setting that applies to all the providers and accounts
+ */
+ public static void setMasterSyncAutomatically(boolean sync) {
+ setMasterSyncAutomaticallyAsUser(sync, UserHandle.myUserId());
+ }
+
+ /**
+ * @see #setMasterSyncAutomatically(boolean)
+ * @hide
+ */
+ public static void setMasterSyncAutomaticallyAsUser(boolean sync, @UserIdInt int userId) {
+ try {
+ getContentService().setMasterSyncAutomaticallyAsUser(sync, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if there is currently a sync operation for the given account or authority
+ * actively being processed.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_STATS}.
+ * @param account the account whose setting we are querying
+ * @param authority the provider whose behavior is being queried
+ * @return true if a sync is active for the given account or authority.
+ */
+ public static boolean isSyncActive(Account account, String authority) {
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+
+ try {
+ return getContentService().isSyncActive(account, authority, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * If a sync is active returns the information about it, otherwise returns null.
+ * <p>
+ * This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_STATS}.
+ * <p>
+ * @return the SyncInfo for the currently active sync or null if one is not active.
+ * @deprecated
+ * Since multiple concurrent syncs are now supported you should use
+ * {@link #getCurrentSyncs()} to get the accurate list of current syncs.
+ * This method returns the first item from the list of current syncs
+ * or null if there are none.
+ */
+ @Deprecated
+ public static SyncInfo getCurrentSync() {
+ try {
+ final List<SyncInfo> syncs = getContentService().getCurrentSyncs();
+ if (syncs.isEmpty()) {
+ return null;
+ }
+ return syncs.get(0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a list with information about all the active syncs. This list will be empty
+ * if there are no active syncs.
+ * <p>
+ * This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_STATS}.
+ * <p>
+ * @return a List of SyncInfo objects for the currently active syncs.
+ */
+ public static List<SyncInfo> getCurrentSyncs() {
+ try {
+ return getContentService().getCurrentSyncs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #getCurrentSyncs()
+ * @hide
+ */
+ public static List<SyncInfo> getCurrentSyncsAsUser(@UserIdInt int userId) {
+ try {
+ return getContentService().getCurrentSyncsAsUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the status that matches the authority.
+ * @param account the account whose setting we are querying
+ * @param authority the provider whose behavior is being queried
+ * @return the SyncStatusInfo for the authority, or null if none exists
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static SyncStatusInfo getSyncStatus(Account account, String authority) {
+ try {
+ return getContentService().getSyncStatus(account, authority, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #getSyncStatus(Account, String)
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+ @UserIdInt int userId) {
+ try {
+ return getContentService().getSyncStatusAsUser(account, authority, null, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return true if the pending status is true of any matching authorities.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#READ_SYNC_STATS}.
+ * @param account the account whose setting we are querying
+ * @param authority the provider whose behavior is being queried
+ * @return true if there is a pending sync with the matching account and authority
+ */
+ public static boolean isSyncPending(Account account, String authority) {
+ return isSyncPendingAsUser(account, authority, UserHandle.myUserId());
+ }
+
+ /**
+ * @see #requestSync(Account, String, Bundle)
+ * @hide
+ */
+ public static boolean isSyncPendingAsUser(Account account, String authority,
+ @UserIdInt int userId) {
+ try {
+ return getContentService().isSyncPendingAsUser(account, authority, null, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request notifications when the different aspects of the SyncManager change. The
+ * different items that can be requested are:
+ * <ul>
+ * <li> {@link #SYNC_OBSERVER_TYPE_PENDING}
+ * <li> {@link #SYNC_OBSERVER_TYPE_ACTIVE}
+ * <li> {@link #SYNC_OBSERVER_TYPE_SETTINGS}
+ * </ul>
+ * The caller can set one or more of the status types in the mask for any
+ * given listener registration.
+ * @param mask the status change types that will cause the callback to be invoked
+ * @param callback observer to be invoked when the status changes
+ * @return a handle that can be used to remove the listener at a later time
+ */
+ public static Object addStatusChangeListener(int mask, final SyncStatusObserver callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("you passed in a null callback");
+ }
+ try {
+ ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() {
+ @Override
+ public void onStatusChanged(int which) throws RemoteException {
+ callback.onStatusChanged(which);
+ }
+ };
+ getContentService().addStatusChangeListener(mask, observer);
+ return observer;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a previously registered status change listener.
+ * @param handle the handle that was returned by {@link #addStatusChangeListener}
+ */
+ public static void removeStatusChangeListener(Object handle) {
+ if (handle == null) {
+ throw new IllegalArgumentException("you passed in a null handle");
+ }
+ try {
+ getContentService().removeStatusChangeListener((ISyncStatusObserver.Stub) handle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Store the given {@link Bundle} as a long-lived cached object within the
+ * system. This can be useful to avoid expensive re-parsing when apps are
+ * restarted multiple times on low-RAM devices.
+ * <p>
+ * The {@link Bundle} is automatically invalidated when a
+ * {@link #notifyChange(Uri, ContentObserver)} event applies to the key.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CACHE_CONTENT)
+ public void putCache(@NonNull Uri key, @Nullable Bundle value) {
+ try {
+ getContentService().putCache(mContext.getPackageName(), key, value,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the last {@link Bundle} stored as a long-lived cached object
+ * within the system.
+ *
+ * @return {@code null} if no cached object has been stored, or if the
+ * stored object has been invalidated due to a
+ * {@link #notifyChange(Uri, ContentObserver)} event.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CACHE_CONTENT)
+ public @Nullable Bundle getCache(@NonNull Uri key) {
+ try {
+ final Bundle bundle = getContentService().getCache(mContext.getPackageName(), key,
+ mContext.getUserId());
+ if (bundle != null) bundle.setClassLoader(mContext.getClassLoader());
+ return bundle;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ /**
+ * Returns sampling percentage for a given duration.
+ *
+ * Always returns at least 1%.
+ */
+ private int samplePercentForDuration(long durationMillis) {
+ if (durationMillis >= SLOW_THRESHOLD_MILLIS) {
+ return 100;
+ }
+ return (int) (100 * durationMillis / SLOW_THRESHOLD_MILLIS) + 1;
+ }
+
+ private void maybeLogQueryToEventLog(
+ long durationMillis, Uri uri, String[] projection, @Nullable Bundle queryArgs) {
+ if (!ENABLE_CONTENT_SAMPLE) return;
+ int samplePercent = samplePercentForDuration(durationMillis);
+ if (samplePercent < 100) {
+ synchronized (mRandom) {
+ if (mRandom.nextInt(100) >= samplePercent) {
+ return;
+ }
+ }
+ }
+
+ // Ensure a non-null bundle.
+ queryArgs = (queryArgs != null) ? queryArgs : Bundle.EMPTY;
+
+ StringBuilder projectionBuffer = new StringBuilder(100);
+ if (projection != null) {
+ for (int i = 0; i < projection.length; ++i) {
+ // Note: not using a comma delimiter here, as the
+ // multiple arguments to EventLog.writeEvent later
+ // stringify with a comma delimiter, which would make
+ // parsing uglier later.
+ if (i != 0) projectionBuffer.append('/');
+ projectionBuffer.append(projection[i]);
+ }
+ }
+
+ // ActivityThread.currentPackageName() only returns non-null if the
+ // current thread is an application main thread. This parameter tells
+ // us whether an event loop is blocked, and if so, which app it is.
+ String blockingPackage = AppGlobals.getInitialPackage();
+
+ EventLog.writeEvent(
+ EventLogTags.CONTENT_QUERY_SAMPLE,
+ uri.toString(),
+ projectionBuffer.toString(),
+ queryArgs.getString(QUERY_ARG_SQL_SELECTION, ""),
+ queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER, ""),
+ durationMillis,
+ blockingPackage != null ? blockingPackage : "",
+ samplePercent);
+ }
+
+ private void maybeLogUpdateToEventLog(
+ long durationMillis, Uri uri, String operation, String selection) {
+ if (!ENABLE_CONTENT_SAMPLE) return;
+ int samplePercent = samplePercentForDuration(durationMillis);
+ if (samplePercent < 100) {
+ synchronized (mRandom) {
+ if (mRandom.nextInt(100) >= samplePercent) {
+ return;
+ }
+ }
+ }
+ String blockingPackage = AppGlobals.getInitialPackage();
+ EventLog.writeEvent(
+ EventLogTags.CONTENT_UPDATE_SAMPLE,
+ uri.toString(),
+ operation,
+ selection != null ? selection : "",
+ durationMillis,
+ blockingPackage != null ? blockingPackage : "",
+ samplePercent);
+ }
+
+ private final class CursorWrapperInner extends CrossProcessCursorWrapper {
+ private final IContentProvider mContentProvider;
+ private final AtomicBoolean mProviderReleased = new AtomicBoolean();
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ CursorWrapperInner(Cursor cursor, IContentProvider contentProvider) {
+ super(cursor);
+ mContentProvider = contentProvider;
+ mCloseGuard.open("close");
+ }
+
+ @Override
+ public void close() {
+ mCloseGuard.close();
+ super.close();
+
+ if (mProviderReleased.compareAndSet(false, true)) {
+ ContentResolver.this.releaseProvider(mContentProvider);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
+ private final IContentProvider mContentProvider;
+ private final AtomicBoolean mProviderReleased = new AtomicBoolean();
+
+ ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) {
+ super(pfd);
+ mContentProvider = icp;
+ }
+
+ @Override
+ public void releaseResources() {
+ if (mProviderReleased.compareAndSet(false, true)) {
+ ContentResolver.this.releaseProvider(mContentProvider);
+ }
+ }
+ }
+
+ /** @hide */
+ public static final String CONTENT_SERVICE_NAME = "content";
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static IContentService getContentService() {
+ if (sContentService != null) {
+ return sContentService;
+ }
+ IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
+ sContentService = IContentService.Stub.asInterface(b);
+ return sContentService;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public String getPackageName() {
+ return mContext.getOpPackageName();
+ }
+
+ /** @hide */
+ public @Nullable String getAttributionTag() {
+ return mContext.getAttributionTag();
+ }
+
+ /** @hide */
+ public @NonNull AttributionSource getAttributionSource() {
+ return mContext.getAttributionSource();
+ }
+
+ @UnsupportedAppUsage
+ private static volatile IContentService sContentService;
+ @UnsupportedAppUsage
+ private final Context mContext;
+
+ @Deprecated
+ @UnsupportedAppUsage
+ final String mPackageName;
+ final int mTargetSdkVersion;
+ final ContentInterface mWrapped;
+
+ private static final String TAG = "ContentResolver";
+
+ /** @hide */
+ public int resolveUserId(Uri uri) {
+ return ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+ }
+
+ /** @hide */
+ public int getUserId() {
+ return mContext.getUserId();
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public Drawable getTypeDrawable(String mimeType) {
+ return getTypeInfo(mimeType).getIcon().loadDrawable(mContext);
+ }
+
+ /**
+ * Return a detailed description of the given MIME type, including an icon
+ * and label that describe the type.
+ *
+ * @param mimeType Valid, concrete MIME type.
+ */
+ public final @NonNull MimeTypeInfo getTypeInfo(@NonNull String mimeType) {
+ Objects.requireNonNull(mimeType);
+ return MimeIconUtils.getTypeInfo(mimeType);
+ }
+
+ /**
+ * Detailed description of a specific MIME type, including an icon and label
+ * that describe the type.
+ */
+ public static final class MimeTypeInfo {
+ private final Icon mIcon;
+ private final CharSequence mLabel;
+ private final CharSequence mContentDescription;
+
+ /** {@hide} */
+ public MimeTypeInfo(@NonNull Icon icon, @NonNull CharSequence label,
+ @NonNull CharSequence contentDescription) {
+ mIcon = Objects.requireNonNull(icon);
+ mLabel = Objects.requireNonNull(label);
+ mContentDescription = Objects.requireNonNull(contentDescription);
+ }
+
+ /**
+ * Return a visual representation of this MIME type. This can be styled
+ * using {@link Icon#setTint(int)} to match surrounding UI.
+ *
+ * @see Icon#loadDrawable(Context)
+ * @see android.widget.ImageView#setImageDrawable(Drawable)
+ */
+ public @NonNull Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Return a textual representation of this MIME type.
+ *
+ * @see android.widget.TextView#setText(CharSequence)
+ */
+ public @NonNull CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Return a content description for this MIME type.
+ *
+ * @see android.view.View#setContentDescription(CharSequence)
+ */
+ public @NonNull CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable Bundle createSqlQueryBundle(
+ @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ return createSqlQueryBundle(selection, selectionArgs, null);
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable Bundle createSqlQueryBundle(
+ @Nullable String selection,
+ @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
+
+ if (selection == null && selectionArgs == null && sortOrder == null) {
+ return null;
+ }
+
+ Bundle queryArgs = new Bundle();
+ if (selection != null) {
+ queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
+ }
+ if (selectionArgs != null) {
+ queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+ }
+ if (sortOrder != null) {
+ queryArgs.putString(QUERY_ARG_SQL_SORT_ORDER, sortOrder);
+ }
+ return queryArgs;
+ }
+
+ /** @hide */
+ public static @NonNull Bundle includeSqlSelectionArgs(@NonNull Bundle queryArgs,
+ @Nullable String selection, @Nullable String[] selectionArgs) {
+ if (selection != null) {
+ queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
+ }
+ if (selectionArgs != null) {
+ queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+ }
+ return queryArgs;
+ }
+
+ /**
+ * Returns structured sort args formatted as an SQL sort clause.
+ *
+ * NOTE: Collator clauses are suitable for use with non text fields. We might
+ * choose to omit any collation clause since we don't know the underlying
+ * type of data to be collated. Imperical testing shows that sqlite3 doesn't
+ * appear to care much about the presence of collate clauses in queries
+ * when ordering by numeric fields. For this reason we include collate
+ * clause unilaterally when {@link #QUERY_ARG_SORT_COLLATION} is present
+ * in query args bundle.
+ *
+ * TODO: Would be nice to explicitly validate that colums referenced in
+ * {@link #QUERY_ARG_SORT_COLUMNS} are present in the associated projection.
+ *
+ * @hide
+ */
+ public static String createSqlSortClause(Bundle queryArgs) {
+ String[] columns = queryArgs.getStringArray(QUERY_ARG_SORT_COLUMNS);
+ if (columns == null || columns.length == 0) {
+ throw new IllegalArgumentException("Can't create sort clause without columns.");
+ }
+
+ String query = TextUtils.join(", ", columns);
+
+ // Interpret PRIMARY and SECONDARY collation strength as no-case collation based
+ // on their javadoc descriptions.
+ int collation = queryArgs.getInt(
+ ContentResolver.QUERY_ARG_SORT_COLLATION, java.text.Collator.IDENTICAL);
+ if (collation == java.text.Collator.PRIMARY || collation == java.text.Collator.SECONDARY) {
+ query += " COLLATE NOCASE";
+ }
+
+ int sortDir = queryArgs.getInt(QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE);
+ if (sortDir != Integer.MIN_VALUE) {
+ switch (sortDir) {
+ case QUERY_SORT_DIRECTION_ASCENDING:
+ query += " ASC";
+ break;
+ case QUERY_SORT_DIRECTION_DESCENDING:
+ query += " DESC";
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported sort direction value."
+ + " See ContentResolver documentation for details.");
+ }
+ }
+ return query;
+ }
+
+ /**
+ * Convenience method that efficiently loads a visual thumbnail for the
+ * given {@link Uri}. Internally calls
+ * {@link ContentProvider#openTypedAssetFile} on the remote provider, but
+ * also defensively resizes any returned content to match the requested
+ * target size.
+ *
+ * @param uri The item that should be visualized as a thumbnail.
+ * @param size The target area on the screen where this thumbnail will be
+ * shown. This is passed to the provider as {@link #EXTRA_SIZE}
+ * to help it avoid downloading or generating heavy resources.
+ * @param signal A signal to cancel the operation in progress.
+ * @return Valid {@link Bitmap} which is a visual thumbnail.
+ * @throws IOException If any trouble was encountered while generating or
+ * loading the thumbnail, or if
+ * {@link CancellationSignal#cancel()} was invoked.
+ */
+ public @NonNull Bitmap loadThumbnail(@NonNull Uri uri, @NonNull Size size,
+ @Nullable CancellationSignal signal) throws IOException {
+ return loadThumbnail(this, uri, size, signal, ImageDecoder.ALLOCATOR_SOFTWARE);
+ }
+
+ /** {@hide} */
+ public static Bitmap loadThumbnail(@NonNull ContentInterface content, @NonNull Uri uri,
+ @NonNull Size size, @Nullable CancellationSignal signal, int allocator)
+ throws IOException {
+ Objects.requireNonNull(content);
+ Objects.requireNonNull(uri);
+ Objects.requireNonNull(size);
+
+ // Convert to Point, since that's what the API is defined as
+ final Bundle opts = new Bundle();
+ opts.putParcelable(EXTRA_SIZE, new Point(size.getWidth(), size.getHeight()));
+ final Int64Ref orientation = new Int64Ref(0);
+
+ Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> {
+ final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts,
+ signal);
+ final Bundle extras = afd.getExtras();
+ orientation.value = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
+ return afd;
+ }), (ImageDecoder decoder, ImageInfo info, Source source) -> {
+ decoder.setAllocator(allocator);
+
+ // One last-ditch check to see if we've been canceled.
+ if (signal != null) signal.throwIfCanceled();
+
+ // We requested a rough thumbnail size, but the remote size may have
+ // returned something giant, so defensively scale down as needed.
+ final int widthSample = info.getSize().getWidth() / size.getWidth();
+ final int heightSample = info.getSize().getHeight() / size.getHeight();
+ final int sample = Math.max(widthSample, heightSample);
+ if (sample > 1) {
+ decoder.setTargetSampleSize(sample);
+ }
+ });
+
+ // Transform the bitmap if requested. We use a side-channel to
+ // communicate the orientation, since EXIF thumbnails don't contain
+ // the rotation flags of the original image.
+ if (orientation.value != 0) {
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+
+ final Matrix m = new Matrix();
+ m.setRotate(orientation.value, width / 2, height / 2);
+ bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
+ }
+
+ return bitmap;
+ }
+
+ /** {@hide} */
+ public static void onDbCorruption(String tag, String message, Throwable stacktrace) {
+ try {
+ getContentService().onDbCorruption(tag, message, Log.getStackTraceString(stacktrace));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Decode a path generated by {@link #encodeToFile(Uri)} back into
+ * the original {@link Uri}.
+ * <p>
+ * This is used to offer a way to intercept filesystem calls in
+ * {@link ContentProvider} unaware code and redirect them to a
+ * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+ * that are otherwise deprecated.
+ *
+ * @hide
+ */
+ @SystemApi
+ // We can't accept an already-opened FD here, since these methods are
+ // rewriting actual filesystem paths
+ @SuppressLint("StreamFiles")
+ public static @NonNull Uri decodeFromFile(@NonNull File file) {
+ return translateDeprecatedDataPath(file.getAbsolutePath());
+ }
+
+ /**
+ * Encode a {@link Uri} into an opaque filesystem path which can then be
+ * resurrected by {@link #decodeFromFile(File)}.
+ * <p>
+ * This is used to offer a way to intercept filesystem calls in
+ * {@link ContentProvider} unaware code and redirect them to a
+ * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+ * that are otherwise deprecated.
+ *
+ * @hide
+ */
+ @SystemApi
+ // We can't accept an already-opened FD here, since these methods are
+ // rewriting actual filesystem paths
+ @SuppressLint("StreamFiles")
+ public static @NonNull File encodeToFile(@NonNull Uri uri) {
+ return new File(translateDeprecatedDataPath(uri));
+ }
+
+ /** {@hide} */
+ public static @NonNull Uri translateDeprecatedDataPath(@NonNull String path) {
+ final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length());
+ return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT)
+ .encodedOpaquePart(ssp).build().toString());
+ }
+
+ /** {@hide} */
+ public static @NonNull String translateDeprecatedDataPath(@NonNull Uri uri) {
+ return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2);
+ }
+}
diff --git a/android/content/ContentUris.java b/android/content/ContentUris.java
new file mode 100644
index 0000000..767d3f6
--- /dev/null
+++ b/android/content/ContentUris.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2007 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.NonNull;
+import android.net.Uri;
+
+import java.util.List;
+
+/**
+* Utility methods useful for working with {@link android.net.Uri} objects
+* that use the "content" (content://) scheme.
+*
+*<p>
+* Content URIs have the syntax
+*</p>
+*<p>
+* <code>content://<em>authority</em>/<em>path</em>/<em>id</em></code>
+*</p>
+*<dl>
+* <dt>
+* <code>content:</code>
+* </dt>
+* <dd>
+* The scheme portion of the URI. This is always set to {@link
+* android.content.ContentResolver#SCHEME_CONTENT ContentResolver.SCHEME_CONTENT} (value
+* <code>content://</code>).
+* </dd>
+* <dt>
+* <em>authority</em>
+* </dt>
+* <dd>
+* A string that identifies the entire content provider. All the content URIs for the provider
+* start with this string. To guarantee a unique authority, providers should consider
+* using an authority that is the same as the provider class' package identifier.
+* </dd>
+* <dt>
+* <em>path</em>
+* </dt>
+* <dd>
+* Zero or more segments, separated by a forward slash (<code>/</code>), that identify
+* some subset of the provider's data. Most providers use the path part to identify
+* individual tables. Individual segments in the path are often called
+* "directories" although they do not refer to file directories. The right-most
+* segment in a path is often called a "twig"
+* </dd>
+* <dt>
+* <em>id</em>
+* </dt>
+* <dd>
+* A unique numeric identifier for a single row in the subset of data identified by the
+* preceding path part. Most providers recognize content URIs that contain an id part
+* and give them special handling. A table that contains a column named <code>_ID</code>
+* often expects the id part to be a particular value for that column.
+* </dd>
+*</dl>
+*
+*/
+public class ContentUris {
+
+ /**
+ * Converts the last path segment to a long.
+ *
+ * <p>This supports a common convention for content URIs where an ID is
+ * stored in the last segment.
+ *
+ * @throws UnsupportedOperationException if this isn't a hierarchical URI
+ * @throws NumberFormatException if the last segment isn't a number
+ *
+ * @return the long conversion of the last segment or -1 if the path is
+ * empty
+ */
+ public static long parseId(@NonNull Uri contentUri) {
+ String last = contentUri.getLastPathSegment();
+ return last == null ? -1 : Long.parseLong(last);
+ }
+
+ /**
+ * Appends the given ID to the end of the path.
+ *
+ * @param builder to append the ID to
+ * @param id to append
+ *
+ * @return the given builder
+ */
+ public static @NonNull Uri.Builder appendId(@NonNull Uri.Builder builder, long id) {
+ return builder.appendEncodedPath(String.valueOf(id));
+ }
+
+ /**
+ * Appends the given ID to the end of the path.
+ *
+ * @param contentUri to start with
+ * @param id to append
+ *
+ * @return a new URI with the given ID appended to the end of the path
+ */
+ public static @NonNull Uri withAppendedId(@NonNull Uri contentUri, long id) {
+ return appendId(contentUri.buildUpon(), id).build();
+ }
+
+ /**
+ * Removes any ID from the end of the path.
+ *
+ * @param contentUri that ends with an ID
+ * @return a new URI with the ID removed from the end of the path
+ * @throws IllegalArgumentException when the given URI has no ID to remove
+ * from the end of the path
+ */
+ public static @NonNull Uri removeId(@NonNull Uri contentUri) {
+ // Verify that we have a valid ID to actually remove
+ final String last = contentUri.getLastPathSegment();
+ if (last == null) {
+ throw new IllegalArgumentException("No path segments to remove");
+ } else {
+ Long.parseLong(last);
+ }
+
+ final List<String> segments = contentUri.getPathSegments();
+ final Uri.Builder builder = contentUri.buildUpon();
+ builder.path(null);
+ for (int i = 0; i < segments.size() - 1; i++) {
+ builder.appendPath(segments.get(i));
+ }
+ return builder.build();
+ }
+}
diff --git a/android/content/ContentValues.java b/android/content/ContentValues.java
new file mode 100644
index 0000000..02a5ba1
--- /dev/null
+++ b/android/content/ContentValues.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2007 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.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class is used to store a set of values that the {@link ContentResolver}
+ * can process.
+ */
+public final class ContentValues implements Parcelable {
+ public static final String TAG = "ContentValues";
+
+ /**
+ * @hide
+ * @deprecated kept around for lame people doing reflection
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ private HashMap<String, Object> mValues;
+
+ private final ArrayMap<String, Object> mMap;
+
+ /**
+ * Creates an empty set of values using the default initial size
+ */
+ public ContentValues() {
+ mMap = new ArrayMap<>();
+ }
+
+ /**
+ * Creates an empty set of values using the given initial size
+ *
+ * @param size the initial size of the set of values
+ */
+ public ContentValues(int size) {
+ Preconditions.checkArgumentNonnegative(size);
+ mMap = new ArrayMap<>(size);
+ }
+
+ /**
+ * Creates a set of values copied from the given set
+ *
+ * @param from the values to copy
+ */
+ public ContentValues(ContentValues from) {
+ Objects.requireNonNull(from);
+ mMap = new ArrayMap<>(from.mMap);
+ }
+
+ /**
+ * @hide
+ * @deprecated kept around for lame people doing reflection
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ private ContentValues(HashMap<String, Object> from) {
+ mMap = new ArrayMap<>();
+ mMap.putAll(from);
+ }
+
+ /** {@hide} */
+ private ContentValues(Parcel in) {
+ mMap = new ArrayMap<>(in.readInt());
+ in.readArrayMap(mMap, null);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (!(object instanceof ContentValues)) {
+ return false;
+ }
+ return mMap.equals(((ContentValues) object).mMap);
+ }
+
+ /** {@hide} */
+ public ArrayMap<String, Object> getValues() {
+ return mMap;
+ }
+
+ @Override
+ public int hashCode() {
+ return mMap.hashCode();
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, String value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds all values from the passed in ContentValues.
+ *
+ * @param other the ContentValues from which to copy
+ */
+ public void putAll(ContentValues other) {
+ mMap.putAll(other.mMap);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Byte value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Short value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Integer value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Long value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Float value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Double value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, Boolean value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a value to the set.
+ *
+ * @param key the name of the value to put
+ * @param value the data for the value to put
+ */
+ public void put(String key, byte[] value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Adds a null value to the set.
+ *
+ * @param key the name of the value to make null
+ */
+ public void putNull(String key) {
+ mMap.put(key, null);
+ }
+
+ /** {@hide} */
+ public void putObject(@Nullable String key, @Nullable Object value) {
+ if (value == null) {
+ putNull(key);
+ } else if (value instanceof String) {
+ put(key, (String) value);
+ } else if (value instanceof Byte) {
+ put(key, (Byte) value);
+ } else if (value instanceof Short) {
+ put(key, (Short) value);
+ } else if (value instanceof Integer) {
+ put(key, (Integer) value);
+ } else if (value instanceof Long) {
+ put(key, (Long) value);
+ } else if (value instanceof Float) {
+ put(key, (Float) value);
+ } else if (value instanceof Double) {
+ put(key, (Double) value);
+ } else if (value instanceof Boolean) {
+ put(key, (Boolean) value);
+ } else if (value instanceof byte[]) {
+ put(key, (byte[]) value);
+ } else {
+ throw new IllegalArgumentException("Unsupported type " + value.getClass());
+ }
+ }
+
+ /**
+ * Returns the number of values.
+ *
+ * @return the number of values
+ */
+ public int size() {
+ return mMap.size();
+ }
+
+ /**
+ * Indicates whether this collection is empty.
+ *
+ * @return true iff size == 0
+ */
+ public boolean isEmpty() {
+ return mMap.isEmpty();
+ }
+
+ /**
+ * Remove a single value.
+ *
+ * @param key the name of the value to remove
+ */
+ public void remove(String key) {
+ mMap.remove(key);
+ }
+
+ /**
+ * Removes all values.
+ */
+ public void clear() {
+ mMap.clear();
+ }
+
+ /**
+ * Returns true if this object has the named value.
+ *
+ * @param key the value to check for
+ * @return {@code true} if the value is present, {@code false} otherwise
+ */
+ public boolean containsKey(String key) {
+ return mMap.containsKey(key);
+ }
+
+ /**
+ * Gets a value. Valid value types are {@link String}, {@link Boolean},
+ * {@link Number}, and {@code byte[]} implementations.
+ *
+ * @param key the value to get
+ * @return the data for the value, or {@code null} if the value is missing or if {@code null}
+ * was previously added with the given {@code key}
+ */
+ public Object get(String key) {
+ return mMap.get(key);
+ }
+
+ /**
+ * Gets a value and converts it to a String.
+ *
+ * @param key the value to get
+ * @return the String for the value
+ */
+ public String getAsString(String key) {
+ Object value = mMap.get(key);
+ return value != null ? value.toString() : null;
+ }
+
+ /**
+ * Gets a value and converts it to a Long.
+ *
+ * @param key the value to get
+ * @return the Long value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Long getAsLong(String key) {
+ Object value = mMap.get(key);
+ try {
+ return value != null ? ((Number) value).longValue() : null;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ try {
+ return Long.valueOf(value.toString());
+ } catch (NumberFormatException e2) {
+ Log.e(TAG, "Cannot parse Long value for " + value + " at key " + key);
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Long: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to an Integer.
+ *
+ * @param key the value to get
+ * @return the Integer value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Integer getAsInteger(String key) {
+ Object value = mMap.get(key);
+ try {
+ return value != null ? ((Number) value).intValue() : null;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ try {
+ return Integer.valueOf(value.toString());
+ } catch (NumberFormatException e2) {
+ Log.e(TAG, "Cannot parse Integer value for " + value + " at key " + key);
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Integer: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Short.
+ *
+ * @param key the value to get
+ * @return the Short value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Short getAsShort(String key) {
+ Object value = mMap.get(key);
+ try {
+ return value != null ? ((Number) value).shortValue() : null;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ try {
+ return Short.valueOf(value.toString());
+ } catch (NumberFormatException e2) {
+ Log.e(TAG, "Cannot parse Short value for " + value + " at key " + key);
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Short: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Byte.
+ *
+ * @param key the value to get
+ * @return the Byte value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Byte getAsByte(String key) {
+ Object value = mMap.get(key);
+ try {
+ return value != null ? ((Number) value).byteValue() : null;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ try {
+ return Byte.valueOf(value.toString());
+ } catch (NumberFormatException e2) {
+ Log.e(TAG, "Cannot parse Byte value for " + value + " at key " + key);
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Byte: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Double.
+ *
+ * @param key the value to get
+ * @return the Double value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Double getAsDouble(String key) {
+ Object value = mMap.get(key);
+ try {
+ return value != null ? ((Number) value).doubleValue() : null;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ try {
+ return Double.valueOf(value.toString());
+ } catch (NumberFormatException e2) {
+ Log.e(TAG, "Cannot parse Double value for " + value + " at key " + key);
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Double: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Float.
+ *
+ * @param key the value to get
+ * @return the Float value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Float getAsFloat(String key) {
+ Object value = mMap.get(key);
+ try {
+ return value != null ? ((Number) value).floatValue() : null;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ try {
+ return Float.valueOf(value.toString());
+ } catch (NumberFormatException e2) {
+ Log.e(TAG, "Cannot parse Float value for " + value + " at key " + key);
+ return null;
+ }
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Float: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Boolean.
+ *
+ * @param key the value to get
+ * @return the Boolean value, or {@code null} if the value is missing or cannot be converted
+ */
+ public Boolean getAsBoolean(String key) {
+ Object value = mMap.get(key);
+ try {
+ return (Boolean) value;
+ } catch (ClassCastException e) {
+ if (value instanceof CharSequence) {
+ // Note that we also check against 1 here because SQLite's internal representation
+ // for booleans is an integer with a value of 0 or 1. Without this check, boolean
+ // values obtained via DatabaseUtils#cursorRowToContentValues will always return
+ // false.
+ return Boolean.valueOf(value.toString()) || "1".equals(value);
+ } else if (value instanceof Number) {
+ return ((Number) value).intValue() != 0;
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Boolean: " + value, e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value that is a byte array. Note that this method will not convert
+ * any other types to byte arrays.
+ *
+ * @param key the value to get
+ * @return the {@code byte[]} value, or {@code null} is the value is missing or not a
+ * {@code byte[]}
+ */
+ public byte[] getAsByteArray(String key) {
+ Object value = mMap.get(key);
+ if (value instanceof byte[]) {
+ return (byte[]) value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a set of all of the keys and values
+ *
+ * @return a set of all of the keys and values
+ */
+ public Set<Map.Entry<String, Object>> valueSet() {
+ return mMap.entrySet();
+ }
+
+ /**
+ * Returns a set of all of the keys
+ *
+ * @return a set of all of the keys
+ */
+ public Set<String> keySet() {
+ return mMap.keySet();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ContentValues> CREATOR =
+ new Parcelable.Creator<ContentValues>() {
+ @Override
+ public ContentValues createFromParcel(Parcel in) {
+ return new ContentValues(in);
+ }
+
+ @Override
+ public ContentValues[] newArray(int size) {
+ return new ContentValues[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mMap.size());
+ parcel.writeArrayMap(mMap);
+ }
+
+ /**
+ * Unsupported, here until we get proper bulk insert APIs.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void putStringArrayList(String key, ArrayList<String> value) {
+ mMap.put(key, value);
+ }
+
+ /**
+ * Unsupported, here until we get proper bulk insert APIs.
+ * {@hide}
+ */
+ @SuppressWarnings("unchecked")
+ @Deprecated
+ @UnsupportedAppUsage
+ public ArrayList<String> getStringArrayList(String key) {
+ return (ArrayList<String>) mMap.get(key);
+ }
+
+ /**
+ * Returns a string containing a concise, human-readable description of this object.
+ * @return a printable representation of this object.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (String name : mMap.keySet()) {
+ String value = getAsString(name);
+ if (sb.length() > 0) sb.append(" ");
+ sb.append(name + "=" + value);
+ }
+ return sb.toString();
+ }
+
+ /** {@hide} */
+ public static boolean isSupportedValue(Object value) {
+ if (value == null) {
+ return true;
+ } else if (value instanceof String) {
+ return true;
+ } else if (value instanceof Byte) {
+ return true;
+ } else if (value instanceof Short) {
+ return true;
+ } else if (value instanceof Integer) {
+ return true;
+ } else if (value instanceof Long) {
+ return true;
+ } else if (value instanceof Float) {
+ return true;
+ } else if (value instanceof Double) {
+ return true;
+ } else if (value instanceof Boolean) {
+ return true;
+ } else if (value instanceof byte[]) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/android/content/Context.java b/android/content/Context.java
new file mode 100644
index 0000000..2702772
--- /dev/null
+++ b/android/content/Context.java
@@ -0,0 +1,6920 @@
+/*
+ * Copyright (C) 2006 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.AttrRes;
+import android.annotation.CallbackExecutor;
+import android.annotation.CheckResult;
+import android.annotation.ColorInt;
+import android.annotation.ColorRes;
+import android.annotation.DisplayContext;
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
+import android.annotation.StyleableRes;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.GameManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.app.VrManager;
+import android.app.people.PeopleManager;
+import android.app.time.TimeManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+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;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.StatFs;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.provider.MediaStore;
+import android.telephony.TelephonyRegistryManager;
+import android.util.AttributeSet;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.autofill.AutofillManager.AutofillClient;
+import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
+import android.view.textclassifier.TextClassificationManager;
+import android.window.WindowContext;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.compat.IPlatformCompatNative;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to global information about an application environment. This is
+ * an abstract class whose implementation is provided by
+ * the Android system. It
+ * allows access to application-specific resources and classes, as well as
+ * up-calls for application-level operations such as launching activities,
+ * broadcasting and receiving intents, etc.
+ */
+public abstract class Context {
+ /** @hide */
+ @IntDef(flag = true, prefix = { "MODE_" }, value = {
+ MODE_PRIVATE,
+ MODE_WORLD_READABLE,
+ MODE_WORLD_WRITEABLE,
+ MODE_APPEND,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FileMode {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "MODE_" }, value = {
+ MODE_PRIVATE,
+ MODE_WORLD_READABLE,
+ MODE_WORLD_WRITEABLE,
+ MODE_MULTI_PROCESS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreferencesMode {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "MODE_" }, value = {
+ MODE_PRIVATE,
+ MODE_WORLD_READABLE,
+ MODE_WORLD_WRITEABLE,
+ MODE_ENABLE_WRITE_AHEAD_LOGGING,
+ MODE_NO_LOCALIZED_COLLATORS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DatabaseMode {}
+
+ /**
+ * File creation mode: the default mode, where the created file can only
+ * be accessed by the calling application (or all applications sharing the
+ * same user ID).
+ */
+ public static final int MODE_PRIVATE = 0x0000;
+
+ /**
+ * File creation mode: allow all other applications to have read access to
+ * the created file.
+ * <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#N}, attempting to use this
+ * mode throws a {@link SecurityException}.
+ *
+ * @deprecated Creating world-readable files is very dangerous, and likely
+ * to cause security holes in applications. It is strongly
+ * discouraged; instead, applications should use more formal
+ * mechanism for interactions such as {@link ContentProvider},
+ * {@link BroadcastReceiver}, and {@link android.app.Service}.
+ * There are no guarantees that this access mode will remain on
+ * a file, such as when it goes through a backup and restore.
+ * @see android.support.v4.content.FileProvider
+ * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ */
+ @Deprecated
+ public static final int MODE_WORLD_READABLE = 0x0001;
+
+ /**
+ * File creation mode: allow all other applications to have write access to
+ * the created file.
+ * <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#N}, attempting to use this
+ * mode will throw a {@link SecurityException}.
+ *
+ * @deprecated Creating world-writable files is very dangerous, and likely
+ * to cause security holes in applications. It is strongly
+ * discouraged; instead, applications should use more formal
+ * mechanism for interactions such as {@link ContentProvider},
+ * {@link BroadcastReceiver}, and {@link android.app.Service}.
+ * There are no guarantees that this access mode will remain on
+ * a file, such as when it goes through a backup and restore.
+ * @see android.support.v4.content.FileProvider
+ * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ */
+ @Deprecated
+ public static final int MODE_WORLD_WRITEABLE = 0x0002;
+
+ /**
+ * File creation mode: for use with {@link #openFileOutput}, if the file
+ * already exists then write data to the end of the existing file
+ * instead of erasing it.
+ * @see #openFileOutput
+ */
+ public static final int MODE_APPEND = 0x8000;
+
+ /**
+ * SharedPreference loading flag: when set, the file on disk will
+ * be checked for modification even if the shared preferences
+ * instance is already loaded in this process. This behavior is
+ * sometimes desired in cases where the application has multiple
+ * processes, all writing to the same SharedPreferences file.
+ * Generally there are better forms of communication between
+ * processes, though.
+ *
+ * <p>This was the legacy (but undocumented) behavior in and
+ * before Gingerbread (Android 2.3) and this flag is implied when
+ * targeting such releases. For applications targeting SDK
+ * versions <em>greater than</em> Android 2.3, this flag must be
+ * explicitly set if desired.
+ *
+ * @see #getSharedPreferences
+ *
+ * @deprecated MODE_MULTI_PROCESS does not work reliably in
+ * some versions of Android, and furthermore does not provide any
+ * mechanism for reconciling concurrent modifications across
+ * processes. Applications should not attempt to use it. Instead,
+ * they should use an explicit cross-process data management
+ * approach such as {@link android.content.ContentProvider ContentProvider}.
+ */
+ @Deprecated
+ public static final int MODE_MULTI_PROCESS = 0x0004;
+
+ /**
+ * Database open flag: when set, the database is opened with write-ahead
+ * logging enabled by default.
+ *
+ * @see #openOrCreateDatabase(String, int, CursorFactory)
+ * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler)
+ * @see SQLiteDatabase#enableWriteAheadLogging
+ */
+ public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008;
+
+ /**
+ * Database open flag: when set, the database is opened without support for
+ * localized collators.
+ *
+ * @see #openOrCreateDatabase(String, int, CursorFactory)
+ * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler)
+ * @see SQLiteDatabase#NO_LOCALIZED_COLLATORS
+ */
+ public static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "BIND_" }, value = {
+ BIND_AUTO_CREATE,
+ BIND_DEBUG_UNBIND,
+ BIND_NOT_FOREGROUND,
+ BIND_ABOVE_CLIENT,
+ BIND_ALLOW_OOM_MANAGEMENT,
+ BIND_WAIVE_PRIORITY,
+ BIND_IMPORTANT,
+ BIND_ADJUST_WITH_ACTIVITY,
+ BIND_NOT_PERCEPTIBLE,
+ BIND_INCLUDE_CAPABILITIES
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BindServiceFlags {}
+
+ /**
+ * Flag for {@link #bindService}: automatically create the service as long
+ * as the binding exists. Note that while this will create the service,
+ * its {@link android.app.Service#onStartCommand}
+ * method will still only be called due to an
+ * explicit call to {@link #startService}. Even without that, though,
+ * this still provides you with access to the service object while the
+ * service is created.
+ *
+ * <p>Note that prior to {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
+ * not supplying this flag would also impact how important the system
+ * consider's the target service's process to be. When set, the only way
+ * for it to be raised was by binding from a service in which case it will
+ * only be important when that activity is in the foreground. Now to
+ * achieve this behavior you must explicitly supply the new flag
+ * {@link #BIND_ADJUST_WITH_ACTIVITY}. For compatibility, old applications
+ * that don't specify {@link #BIND_AUTO_CREATE} will automatically have
+ * the flags {@link #BIND_WAIVE_PRIORITY} and
+ * {@link #BIND_ADJUST_WITH_ACTIVITY} set for them in order to achieve
+ * the same result.
+ */
+ public static final int BIND_AUTO_CREATE = 0x0001;
+
+ /**
+ * Flag for {@link #bindService}: include debugging help for mismatched
+ * calls to unbind. When this flag is set, the callstack of the following
+ * {@link #unbindService} call is retained, to be printed if a later
+ * incorrect unbind call is made. Note that doing this requires retaining
+ * information about the binding that was made for the lifetime of the app,
+ * resulting in a leak -- this should only be used for debugging.
+ */
+ public static final int BIND_DEBUG_UNBIND = 0x0002;
+
+ /**
+ * Flag for {@link #bindService}: don't allow this binding to raise
+ * the target service's process to the foreground scheduling priority.
+ * It will still be raised to at least the same memory priority
+ * as the client (so that its process will not be killable in any
+ * situation where the client is not killable), but for CPU scheduling
+ * purposes it may be left in the background. This only has an impact
+ * in the situation where the binding client is a foreground process
+ * and the target service is in a background process.
+ */
+ public static final int BIND_NOT_FOREGROUND = 0x0004;
+
+ /**
+ * Flag for {@link #bindService}: indicates that the client application
+ * binding to this service considers the service to be more important than
+ * the app itself. When set, the platform will try to have the out of
+ * memory killer kill the app before it kills the service it is bound to, though
+ * this is not guaranteed to be the case.
+ */
+ public static final int BIND_ABOVE_CLIENT = 0x0008;
+
+ /**
+ * Flag for {@link #bindService}: allow the process hosting the bound
+ * service to go through its normal memory management. It will be
+ * treated more like a running service, allowing the system to
+ * (temporarily) expunge the process if low on memory or for some other
+ * whim it may have, and being more aggressive about making it a candidate
+ * to be killed (and restarted) if running for a long time.
+ */
+ public static final int BIND_ALLOW_OOM_MANAGEMENT = 0x0010;
+
+ /**
+ * Flag for {@link #bindService}: don't impact the scheduling or
+ * memory management priority of the target service's hosting process.
+ * Allows the service's process to be managed on the background LRU list
+ * just like a regular application process in the background.
+ */
+ public static final int BIND_WAIVE_PRIORITY = 0x0020;
+
+ /**
+ * Flag for {@link #bindService}: this service is very important to
+ * the client, so should be brought to the foreground process level
+ * when the client is. Normally a process can only be raised to the
+ * visibility level by a client, even if that client is in the foreground.
+ */
+ public static final int BIND_IMPORTANT = 0x0040;
+
+ /**
+ * Flag for {@link #bindService}: If binding from an activity, allow the
+ * target service's process importance to be raised based on whether the
+ * activity is visible to the user, regardless whether another flag is
+ * used to reduce the amount that the client process's overall importance
+ * is used to impact it.
+ */
+ public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
+
+ /**
+ * Flag for {@link #bindService}: If binding from an app that is visible or user-perceptible,
+ * lower the target service's importance to below the perceptible level. This allows
+ * the system to (temporarily) expunge the bound process from memory to make room for more
+ * important user-perceptible processes.
+ */
+ public static final int BIND_NOT_PERCEPTIBLE = 0x00000100;
+
+ /**
+ * Flag for {@link #bindService}: If binding from an app that has specific capabilities
+ * due to its foreground state such as an activity or foreground service, then this flag will
+ * allow the bound app to get the same capabilities, as long as it has the required permissions
+ * as well.
+ *
+ * If binding from a top app and its target SDK version is at or above
+ * {@link android.os.Build.VERSION_CODES#R}, the app needs to
+ * explicitly use BIND_INCLUDE_CAPABILITIES flag to pass all capabilities to the service so the
+ * other app can have while-use-use access such as location, camera, microphone from background.
+ * If binding from a top app and its target SDK version is below
+ * {@link android.os.Build.VERSION_CODES#R}, BIND_INCLUDE_CAPABILITIES is implicit.
+ */
+ public static final int BIND_INCLUDE_CAPABILITIES = 0x000001000;
+
+ /*********** Public flags above this line ***********/
+ /*********** Hidden flags below this line ***********/
+
+ /**
+ * Flag for {@link #bindService}: This flag is only intended to be used by the system to
+ * indicate that a service binding is not considered as real package component usage and should
+ * not generate a {@link android.app.usage.UsageEvents.Event#APP_COMPONENT_USED} event in usage
+ * stats.
+ * @hide
+ */
+ public static final int BIND_NOT_APP_COMPONENT_USAGE = 0x00008000;
+
+ /**
+ * Flag for {@link #bindService}: allow the process hosting the target service to be treated
+ * as if it's as important as a perceptible app to the user and avoid the oom killer killing
+ * this process in low memory situations until there aren't any other processes left but the
+ * ones which are user-perceptible.
+ *
+ * @hide
+ */
+ public static final int BIND_ALMOST_PERCEPTIBLE = 0x000010000;
+
+ /**
+ * Flag for {@link #bindService}: allow the process hosting the target service to gain
+ * {@link ActivityManager#PROCESS_CAPABILITY_NETWORK}, which allows it be able
+ * to access network regardless of any power saving restrictions.
+ *
+ * @hide
+ */
+ public static final int BIND_BYPASS_POWER_NETWORK_RESTRICTIONS = 0x00020000;
+
+ /**
+ * Do not use. This flag is no longer needed nor used.
+ * @hide
+ */
+ @SystemApi
+ public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 0x00040000;
+
+ /**
+ * Flag for {@link #bindService}: This flag is intended to be used only by the system to adjust
+ * the scheduling policy for IMEs (and any other out-of-process user-visible components that
+ * work closely with the top app) so that UI hosted in such services can have the same
+ * scheduling policy (e.g. SCHED_FIFO when it is enabled and TOP_APP_PRIORITY_BOOST otherwise)
+ * as the actual top-app.
+ * @hide
+ */
+ public static final int BIND_SCHEDULE_LIKE_TOP_APP = 0x00080000;
+
+ /**
+ * Flag for {@link #bindService}: allow background activity starts from the bound service's
+ * process.
+ * This flag is only respected if the caller is holding
+ * {@link android.Manifest.permission#START_ACTIVITIES_FROM_BACKGROUND}.
+ * @hide
+ */
+ @SystemApi
+ public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 0x00100000;
+
+ /**
+ * @hide Flag for {@link #bindService}: the service being bound to represents a
+ * protected system component, so must have association restrictions applied to it.
+ * That is, a system config must have one or more allow-association tags limiting
+ * which packages it can interact with. If it does not have any such association
+ * restrictions, a default empty set will be created.
+ */
+ public static final int BIND_RESTRICT_ASSOCIATIONS = 0x00200000;
+
+ /**
+ * @hide Flag for {@link #bindService}: allows binding to a service provided
+ * by an instant app. Note that the caller may not have access to the instant
+ * app providing the service which is a violation of the instant app sandbox.
+ * This flag is intended ONLY for development/testing and should be used with
+ * great care. Only the system is allowed to use this flag.
+ */
+ public static final int BIND_ALLOW_INSTANT = 0x00400000;
+
+ /**
+ * @hide Flag for {@link #bindService}: like {@link #BIND_NOT_FOREGROUND}, but puts it
+ * up in to the important background state (instead of transient).
+ */
+ public static final int BIND_IMPORTANT_BACKGROUND = 0x00800000;
+
+ /**
+ * @hide Flag for {@link #bindService}: allows application hosting service to manage whitelists
+ * such as temporary allowing a {@code PendingIntent} to bypass Power Save mode.
+ */
+ public static final int BIND_ALLOW_WHITELIST_MANAGEMENT = 0x01000000;
+
+ /**
+ * @hide Flag for {@link #bindService}: Like {@link #BIND_FOREGROUND_SERVICE},
+ * but only applies while the device is awake.
+ */
+ public static final int BIND_FOREGROUND_SERVICE_WHILE_AWAKE = 0x02000000;
+
+ /**
+ * @hide Flag for {@link #bindService}: For only the case where the binding
+ * is coming from the system, set the process state to FOREGROUND_SERVICE
+ * instead of the normal maximum of IMPORTANT_FOREGROUND. That is, this is
+ * saying that the process shouldn't participate in the normal power reduction
+ * modes (removing network access etc).
+ */
+ public static final int BIND_FOREGROUND_SERVICE = 0x04000000;
+
+ /**
+ * @hide Flag for {@link #bindService}: Treat the binding as hosting
+ * an activity, an unbinding as the activity going in the background.
+ * That is, when unbinding, the process when empty will go on the activity
+ * LRU list instead of the regular one, keeping it around more aggressively
+ * than it otherwise would be. This is intended for use with IMEs to try
+ * to keep IME processes around for faster keyboard switching.
+ */
+ public static final int BIND_TREAT_LIKE_ACTIVITY = 0x08000000;
+
+ /**
+ * @hide An idea that is not yet implemented.
+ * Flag for {@link #bindService}: If binding from an activity, consider
+ * this service to be visible like the binding activity is. That is,
+ * it will be treated as something more important to keep around than
+ * invisible background activities. This will impact the number of
+ * recent activities the user can switch between without having them
+ * restart. There is no guarantee this will be respected, as the system
+ * tries to balance such requests from one app vs. the importance of
+ * keeping other apps around.
+ */
+ public static final int BIND_VISIBLE = 0x10000000;
+
+ /**
+ * @hide
+ * Flag for {@link #bindService}: Consider this binding to be causing the target
+ * process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes
+ * away.
+ */
+ public static final int BIND_SHOWING_UI = 0x20000000;
+
+ /**
+ * Flag for {@link #bindService}: Don't consider the bound service to be
+ * visible, even if the caller is visible.
+ * @hide
+ */
+ public static final int BIND_NOT_VISIBLE = 0x40000000;
+
+ /**
+ * Flag for {@link #bindService}: The service being bound is an
+ * {@link android.R.attr#isolatedProcess isolated},
+ * {@link android.R.attr#externalService external} service. This binds the service into the
+ * calling application's package, rather than the package in which the service is declared.
+ * <p>
+ * When using this flag, the code for the service being bound will execute under the calling
+ * application's package name and user ID. Because the service must be an isolated process,
+ * it will not have direct access to the application's data, though.
+ *
+ * The purpose of this flag is to allow applications to provide services that are attributed
+ * to the app using the service, rather than the application providing the service.
+ * </p>
+ */
+ public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
+
+ /**
+ * These bind flags reduce the strength of the binding such that we shouldn't
+ * consider it as pulling the process up to the level of the one that is bound to it.
+ * @hide
+ */
+ public static final int BIND_REDUCTION_FLAGS =
+ Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_NOT_PERCEPTIBLE | Context.BIND_NOT_VISIBLE;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "RECEIVER_VISIBLE_" }, value = {
+ RECEIVER_VISIBLE_TO_INSTANT_APPS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RegisterReceiverFlags {}
+
+ /**
+ * Flag for {@link #registerReceiver}: The receiver can receive broadcasts from Instant Apps.
+ */
+ public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 0x1;
+
+ /**
+ * Returns an AssetManager instance for the application's package.
+ * <p>
+ * <strong>Note:</strong> Implementations of this method should return
+ * an AssetManager instance that is consistent with the Resources instance
+ * returned by {@link #getResources()}. For example, they should share the
+ * same {@link Configuration} object.
+ *
+ * @return an AssetManager instance for the application's package
+ * @see #getResources()
+ */
+ public abstract AssetManager getAssets();
+
+ /**
+ * Returns a Resources instance for the application's package.
+ * <p>
+ * <strong>Note:</strong> Implementations of this method should return
+ * a Resources instance that is consistent with the AssetManager instance
+ * returned by {@link #getAssets()}. For example, they should share the
+ * same {@link Configuration} object.
+ *
+ * @return a Resources instance for the application's package
+ * @see #getAssets()
+ */
+ public abstract Resources getResources();
+
+ /** Return PackageManager instance to find global package information. */
+ public abstract PackageManager getPackageManager();
+
+ /** Return a ContentResolver instance for your application's package. */
+ public abstract ContentResolver getContentResolver();
+
+ /**
+ * Return the Looper for the main thread of the current process. This is
+ * the thread used to dispatch calls to application components (activities,
+ * services, etc).
+ * <p>
+ * By definition, this method returns the same result as would be obtained
+ * by calling {@link Looper#getMainLooper() Looper.getMainLooper()}.
+ * </p>
+ *
+ * @return The main looper.
+ */
+ public abstract Looper getMainLooper();
+
+ /**
+ * Return an {@link Executor} that will run enqueued tasks on the main
+ * thread associated with this context. This is the thread used to dispatch
+ * calls to application components (activities, services, etc).
+ */
+ public Executor getMainExecutor() {
+ // This is pretty inefficient, which is why ContextImpl overrides it
+ return new HandlerExecutor(new Handler(getMainLooper()));
+ }
+
+ /**
+ * Return the context of the single, global Application object of the
+ * current process. This generally should only be used if you need a
+ * Context whose lifecycle is separate from the current context, that is
+ * tied to the lifetime of the process rather than the current component.
+ *
+ * <p>Consider for example how this interacts with
+ * {@link #registerReceiver(BroadcastReceiver, IntentFilter)}:
+ * <ul>
+ * <li> <p>If used from an Activity context, the receiver is being registered
+ * within that activity. This means that you are expected to unregister
+ * before the activity is done being destroyed; in fact if you do not do
+ * so, the framework will clean up your leaked registration as it removes
+ * the activity and log an error. Thus, if you use the Activity context
+ * to register a receiver that is static (global to the process, not
+ * associated with an Activity instance) then that registration will be
+ * removed on you at whatever point the activity you used is destroyed.
+ * <li> <p>If used from the Context returned here, the receiver is being
+ * registered with the global state associated with your application. Thus
+ * it will never be unregistered for you. This is necessary if the receiver
+ * is associated with static data, not a particular component. However
+ * using the ApplicationContext elsewhere can easily lead to serious leaks
+ * if you forget to unregister, unbind, etc.
+ * </ul>
+ */
+ public abstract Context getApplicationContext();
+
+ /** Non-activity related autofill ids are unique in the app */
+ private static int sLastAutofillId = View.NO_ID;
+
+ /**
+ * Gets the next autofill ID.
+ *
+ * <p>All IDs will be smaller or the same as {@link View#LAST_APP_AUTOFILL_ID}. All IDs
+ * returned will be unique.
+ *
+ * @return A ID that is unique in the process
+ *
+ * {@hide}
+ */
+ public int getNextAutofillId() {
+ if (sLastAutofillId == View.LAST_APP_AUTOFILL_ID - 1) {
+ sLastAutofillId = View.NO_ID;
+ }
+
+ sLastAutofillId++;
+
+ return sLastAutofillId;
+ }
+
+ /**
+ * Add a new {@link ComponentCallbacks} to the base application of the
+ * Context, which will be called at the same times as the ComponentCallbacks
+ * methods of activities and other components are called. Note that you
+ * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
+ * appropriate in the future; this will not be removed for you.
+ * <p>
+ * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+ * via {@link #createWindowContext(int, Bundle)} or
+ * {@link #createWindowContext(Display, int, Bundle)} will receive
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
+ * than its base application. It is helpful if you want to handle UI components that
+ * associated with the Window Context when the Window Context has configuration changes.</p>
+ *
+ * @param callback The interface to call. This can be either a
+ * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ *
+ * @see Context#createWindowContext(int, Bundle)
+ */
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ getApplicationContext().registerComponentCallbacks(callback);
+ }
+
+ /**
+ * Remove a {@link ComponentCallbacks} object that was previously registered
+ * with {@link #registerComponentCallbacks(ComponentCallbacks)}.
+ */
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ getApplicationContext().unregisterComponentCallbacks(callback);
+ }
+
+ /**
+ * Return a localized, styled CharSequence from the application's package's
+ * default string table.
+ *
+ * @param resId Resource id for the CharSequence text
+ */
+ @NonNull
+ public final CharSequence getText(@StringRes int resId) {
+ return getResources().getText(resId);
+ }
+
+ /**
+ * Returns a localized string from the application's package's
+ * default string table.
+ *
+ * @param resId Resource id for the string
+ * @return The string data associated with the resource, stripped of styled
+ * text information.
+ */
+ @NonNull
+ public final String getString(@StringRes int resId) {
+ return getResources().getString(resId);
+ }
+
+ /**
+ * Returns a localized formatted string from the application's package's
+ * default string table, substituting the format arguments as defined in
+ * {@link java.util.Formatter} and {@link java.lang.String#format}.
+ *
+ * @param resId Resource id for the format string
+ * @param formatArgs The format arguments that will be used for
+ * substitution.
+ * @return The string data associated with the resource, formatted and
+ * stripped of styled text information.
+ */
+ @NonNull
+ public final String getString(@StringRes int resId, Object... formatArgs) {
+ return getResources().getString(resId, formatArgs);
+ }
+
+ /**
+ * Returns a color associated with a particular resource ID and styled for
+ * the current theme.
+ *
+ * @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 single color value in the form 0xAARRGGBB.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @ColorInt
+ public final int getColor(@ColorRes int id) {
+ return getResources().getColor(id, getTheme());
+ }
+
+ /**
+ * Returns a drawable object associated with a particular resource ID and
+ * styled for the current theme.
+ *
+ * @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.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @Nullable
+ public final Drawable getDrawable(@DrawableRes int id) {
+ return getResources().getDrawable(id, getTheme());
+ }
+
+ /**
+ * Returns a color state list associated with a particular resource ID and
+ * styled for the current theme.
+ *
+ * @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.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @NonNull
+ public final ColorStateList getColorStateList(@ColorRes int id) {
+ return getResources().getColorStateList(id, getTheme());
+ }
+
+ /**
+ * Set the base theme for this context. Note that this should be called
+ * before any views are instantiated in the Context (for example before
+ * calling {@link android.app.Activity#setContentView} or
+ * {@link android.view.LayoutInflater#inflate}).
+ *
+ * @param resid The style resource describing the theme.
+ */
+ public abstract void setTheme(@StyleRes int resid);
+
+ /** @hide Needed for some internal implementation... not public because
+ * you can't assume this actually means anything. */
+ @UnsupportedAppUsage
+ public int getThemeResId() {
+ return 0;
+ }
+
+ /**
+ * Return the Theme object associated with this Context.
+ */
+ @ViewDebug.ExportedProperty(deepExport = true)
+ public abstract Resources.Theme getTheme();
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(int[])}
+ * for more information.
+ *
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(int[])
+ */
+ @NonNull
+ public final TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[] attrs) {
+ return getTheme().obtainStyledAttributes(attrs);
+ }
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])}
+ * for more information.
+ *
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])
+ */
+ @NonNull
+ public final TypedArray obtainStyledAttributes(@StyleRes int resid,
+ @NonNull @StyleableRes int[] attrs) throws Resources.NotFoundException {
+ return getTheme().obtainStyledAttributes(resid, attrs);
+ }
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * for more information.
+ *
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ @NonNull
+ public final TypedArray obtainStyledAttributes(
+ @Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs) {
+ return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * for more information.
+ *
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ @NonNull
+ public final TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
+ @NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes) {
+ return getTheme().obtainStyledAttributes(
+ set, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Return a class loader you can use to retrieve classes in this package.
+ */
+ public abstract ClassLoader getClassLoader();
+
+ /** Return the name of this application's package. */
+ public abstract String getPackageName();
+
+ /**
+ * @hide Return the name of the base context this context is derived from.
+ * This is the same as {@link #getOpPackageName()} except in
+ * cases where system components are loaded into other app processes, in which
+ * case {@link #getOpPackageName()} will be the name of the primary package in
+ * that process (so that app ops uid verification will work with the name).
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract String getBasePackageName();
+
+ /**
+ * Return the package name that should be used for {@link android.app.AppOpsManager} calls from
+ * this context, so that app ops manager's uid verification will work with the name.
+ * <p>
+ * This is not generally intended for third party application developers.
+ */
+ @NonNull
+ public String getOpPackageName() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * <p>Attribution can be used in complex apps to logically separate parts of the app. E.g. a
+ * blogging app might also have a instant messaging app built in. In this case two separate tags
+ * can for used each sub-feature.
+ *
+ * @return the attribution tag this context is for or {@code null} if this is the default.
+ */
+ public @Nullable String getAttributionTag() {
+ return null;
+ }
+
+ /**
+ * @return The identity of this context for permission purposes.
+ *
+ * @see AttributionSource
+ */
+ public @NonNull AttributionSource getAttributionSource() {
+ return null;
+ }
+
+ // TODO moltmann: Remove
+ /**
+ * @removed
+ */
+ @Deprecated
+ public @Nullable String getFeatureId() {
+ return getAttributionTag();
+ }
+
+ /**
+ * Return the set of parameters which this Context was created with, if it
+ * was created via {@link #createContext(ContextParams)}.
+ */
+ public @Nullable ContextParams getParams() {
+ return null;
+ }
+
+ /** Return the full application info for this context's package. */
+ public abstract ApplicationInfo getApplicationInfo();
+
+ /**
+ * Return the full path to this context's primary Android package.
+ * The Android package is a ZIP file which contains the application's
+ * primary resources.
+ *
+ * <p>Note: this is not generally useful for applications, since they should
+ * not be directly accessing the file system.
+ *
+ * @return String Path to the resources.
+ */
+ public abstract String getPackageResourcePath();
+
+ /**
+ * Return the full path to this context's primary Android package.
+ * The Android package is a ZIP file which contains application's
+ * primary code and assets.
+ *
+ * <p>Note: this is not generally useful for applications, since they should
+ * not be directly accessing the file system.
+ *
+ * @return String Path to the code and assets.
+ */
+ public abstract String getPackageCodePath();
+
+ /**
+ * @hide
+ * @deprecated use {@link #getSharedPreferencesPath(String)}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public File getSharedPrefsFile(String name) {
+ return getSharedPreferencesPath(name);
+ }
+
+ /**
+ * Retrieve and hold the contents of the preferences file 'name', returning
+ * a SharedPreferences through which you can retrieve and modify its
+ * values. Only one instance of the SharedPreferences object is returned
+ * to any callers for the same name, meaning they will see each other's
+ * edits as soon as they are made.
+ *
+ * <p>This method is thread-safe.
+ *
+ * <p>If the preferences directory does not already exist, it will be created when this method
+ * is called.
+ *
+ * <p>If a preferences file by this name does not exist, it will be created when you retrieve an
+ * editor ({@link SharedPreferences#edit()}) and then commit changes ({@link
+ * SharedPreferences.Editor#commit()} or {@link SharedPreferences.Editor#apply()}).
+ *
+ * @param name Desired preferences file.
+ * @param mode Operating mode.
+ *
+ * @return The single {@link SharedPreferences} instance that can be used
+ * to retrieve and modify the preference values.
+ *
+ * @see #MODE_PRIVATE
+ */
+ public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
+
+ /**
+ * Retrieve and hold the contents of the preferences file, returning
+ * a SharedPreferences through which you can retrieve and modify its
+ * values. Only one instance of the SharedPreferences object is returned
+ * to any callers for the same name, meaning they will see each other's
+ * edits as soon as they are made.
+ *
+ * @param file Desired preferences file. If a preferences file by this name
+ * does not exist, it will be created when you retrieve an
+ * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+ * @param mode Operating mode.
+ *
+ * @return The single {@link SharedPreferences} instance that can be used
+ * to retrieve and modify the preference values.
+ *
+ * @see #getSharedPreferencesPath(String)
+ * @see #MODE_PRIVATE
+ * @removed
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract SharedPreferences getSharedPreferences(File file, @PreferencesMode int mode);
+
+ /**
+ * Move an existing shared preferences file from the given source storage
+ * context to this context. This is typically used to migrate data between
+ * storage locations after an upgrade, such as moving to device protected
+ * storage.
+ *
+ * @param sourceContext The source context which contains the existing
+ * shared preferences to move.
+ * @param name The name of the shared preferences file.
+ * @return {@code true} if the move was successful or if the shared
+ * preferences didn't exist in the source context, otherwise
+ * {@code false}.
+ * @see #createDeviceProtectedStorageContext()
+ */
+ public abstract boolean moveSharedPreferencesFrom(Context sourceContext, String name);
+
+ /**
+ * Delete an existing shared preferences file.
+ *
+ * @param name The name (unique in the application package) of the shared
+ * preferences file.
+ * @return {@code true} if the shared preferences file was successfully
+ * deleted; else {@code false}.
+ * @see #getSharedPreferences(String, int)
+ */
+ public abstract boolean deleteSharedPreferences(String name);
+
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void reloadSharedPreferences();
+
+ /**
+ * Open a private file associated with this Context's application package
+ * for reading.
+ *
+ * @param name The name of the file to open; can not contain path
+ * separators.
+ *
+ * @return The resulting {@link FileInputStream}.
+ *
+ * @see #openFileOutput
+ * @see #fileList
+ * @see #deleteFile
+ * @see java.io.FileInputStream#FileInputStream(String)
+ */
+ public abstract FileInputStream openFileInput(String name)
+ throws FileNotFoundException;
+
+ /**
+ * Open a private file associated with this Context's application package
+ * for writing. Creates the file if it doesn't already exist.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write the returned file.
+ *
+ * @param name The name of the file to open; can not contain path
+ * separators.
+ * @param mode Operating mode.
+ * @return The resulting {@link FileOutputStream}.
+ * @see #MODE_APPEND
+ * @see #MODE_PRIVATE
+ * @see #openFileInput
+ * @see #fileList
+ * @see #deleteFile
+ * @see java.io.FileOutputStream#FileOutputStream(String)
+ */
+ public abstract FileOutputStream openFileOutput(String name, @FileMode int mode)
+ throws FileNotFoundException;
+
+ /**
+ * Delete the given private file associated with this Context's
+ * application package.
+ *
+ * @param name The name of the file to delete; can not contain path
+ * separators.
+ *
+ * @return {@code true} if the file was successfully deleted; else
+ * {@code false}.
+ *
+ * @see #openFileInput
+ * @see #openFileOutput
+ * @see #fileList
+ * @see java.io.File#delete()
+ */
+ public abstract boolean deleteFile(String name);
+
+ /**
+ * Returns the absolute path on the filesystem where a file created with
+ * {@link #openFileOutput} is stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ *
+ * @param name The name of the file for which you would like to get
+ * its path.
+ *
+ * @return An absolute path to the given file.
+ *
+ * @see #openFileOutput
+ * @see #getFilesDir
+ * @see #getDir
+ */
+ public abstract File getFileStreamPath(String name);
+
+ /**
+ * Returns the absolute path on the filesystem where a file created with
+ * {@link #getSharedPreferences(String, int)} is stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ *
+ * @param name The name of the shared preferences for which you would like
+ * to get a path.
+ * @return An absolute path to the given file.
+ * @see #getSharedPreferences(String, int)
+ * @removed
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract File getSharedPreferencesPath(String name);
+
+ /**
+ * Returns the absolute path to the directory on the filesystem where all
+ * private files belonging to this app are stored. Apps should not use this
+ * path directly; they should instead use {@link #getFilesDir()},
+ * {@link #getCacheDir()}, {@link #getDir(String, int)}, or other storage
+ * APIs on this class.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *
+ * @see ApplicationInfo#dataDir
+ */
+ public abstract File getDataDir();
+
+ /**
+ * Returns the absolute path to the directory on the filesystem where files
+ * created with {@link #openFileOutput} are stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *
+ * @return The path of the directory holding application files.
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ */
+ public abstract File getFilesDir();
+
+ /**
+ * Returns the absolute path to the directory that is related to the crate on the filesystem.
+ * <p>
+ * The crateId require a validated file name. It can't contain any "..", ".",
+ * {@link File#separatorChar} etc..
+ * </p>
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * </p>
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *</p>
+ *
+ * @param crateId the relative validated file name under {@link Context#getDataDir()}/crates
+ * @return the crate directory file.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ public File getCrateDir(@NonNull String crateId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Returns the absolute path to the directory on the filesystem similar to
+ * {@link #getFilesDir()}. The difference is that files placed under this
+ * directory will be excluded from automatic backup to remote storage. See
+ * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
+ * of the automatic backup mechanism in Android.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *
+ * @return The path of the directory holding application files that will not
+ * be automatically backed up to remote storage.
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ * @see android.app.backup.BackupAgent
+ */
+ public abstract File getNoBackupFilesDir();
+
+ /**
+ * Returns the absolute path to the directory on the primary shared/external
+ * storage device where the application can place persistent files it owns.
+ * These files are internal to the applications, and not typically visible
+ * to the user as media.
+ * <p>
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
+ * <ul>
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directories returned
+ * by {@link #getFilesDir()}, etc.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+ * are required to read or write to the returned path; it's always
+ * accessible to the calling app. This only applies to paths generated for
+ * package name of the calling application. To access paths belonging to
+ * other packages,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+ * <p>
+ * On devices with multiple users (as described by {@link UserManager}),
+ * each user has their own isolated shared storage. Applications only have
+ * access to the shared storage for the user they're running as.
+ * <p>
+ * The returned path may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
+ * <p>
+ * Here is an example of typical code to manipulate a file in an
+ * application's shared storage:
+ * </p>
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * private_file}
+ * <p>
+ * If you supply a non-null <var>type</var> to this function, the returned
+ * file will be a path to a sub-directory of the given type. Though these
+ * files are not automatically scanned by the media scanner, you can
+ * explicitly add them to the media database with
+ * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[], android.media.MediaScannerConnection.OnScanCompletedListener)
+ * MediaScannerConnection.scanFile}. Note that this is not the same as
+ * {@link android.os.Environment#getExternalStoragePublicDirectory
+ * Environment.getExternalStoragePublicDirectory()}, which provides
+ * directories of media shared by all applications. The directories returned
+ * here are owned by the application, and their contents will be removed
+ * when the application is uninstalled. Unlike
+ * {@link android.os.Environment#getExternalStoragePublicDirectory
+ * Environment.getExternalStoragePublicDirectory()}, the directory returned
+ * here will be automatically created for you.
+ * <p>
+ * Here is an example of typical code to manipulate a picture in an
+ * application's shared storage and add it to the media database:
+ * </p>
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * private_picture}
+ *
+ * @param type The type of files directory to return. May be {@code null}
+ * for the root of the files directory or one of the following
+ * constants for a subdirectory:
+ * {@link android.os.Environment#DIRECTORY_MUSIC},
+ * {@link android.os.Environment#DIRECTORY_PODCASTS},
+ * {@link android.os.Environment#DIRECTORY_RINGTONES},
+ * {@link android.os.Environment#DIRECTORY_ALARMS},
+ * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+ * {@link android.os.Environment#DIRECTORY_PICTURES}, or
+ * {@link android.os.Environment#DIRECTORY_MOVIES}.
+ * @return the absolute path to application-specific directory. May return
+ * {@code null} if shared storage is not currently available.
+ * @see #getFilesDir
+ * @see #getExternalFilesDirs(String)
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ */
+ @Nullable
+ public abstract File getExternalFilesDir(@Nullable String type);
+
+ /**
+ * Returns absolute paths to application-specific directories on all
+ * shared/external storage devices where the application can place
+ * persistent files it owns. These files are internal to the application,
+ * and not typically visible to the user as media.
+ * <p>
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
+ * <ul>
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directories returned
+ * by {@link #getFilesDir()}, etc.
+ * <p>
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
+ * <p>
+ * The returned path may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
+ *
+ * @param type The type of files directory to return. May be {@code null}
+ * for the root of the files directory or one of the following
+ * constants for a subdirectory:
+ * {@link android.os.Environment#DIRECTORY_MUSIC},
+ * {@link android.os.Environment#DIRECTORY_PODCASTS},
+ * {@link android.os.Environment#DIRECTORY_RINGTONES},
+ * {@link android.os.Environment#DIRECTORY_ALARMS},
+ * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+ * {@link android.os.Environment#DIRECTORY_PICTURES}, or
+ * {@link android.os.Environment#DIRECTORY_MOVIES}.
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available. The first path returned is the same as
+ * {@link #getExternalFilesDir(String)}.
+ * @see #getExternalFilesDir(String)
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ */
+ public abstract File[] getExternalFilesDirs(String type);
+
+ /**
+ * Return the primary shared/external storage directory where this
+ * application's OBB files (if there are any) can be found. Note if the
+ * application does not have any OBB files, this directory may not exist.
+ * <p>
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
+ * <ul>
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+ * are required to read or write to the path that this method returns.
+ * However, starting from {@link android.os.Build.VERSION_CODES#M},
+ * to read the OBB expansion files, you must declare the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission in the app manifest and ask for
+ * permission at runtime as follows:
+ * </p>
+ * <p>
+ * {@code <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
+ * android:maxSdkVersion="23" />}
+ * </p>
+ * <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#N},
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}
+ * permission is not required, so don’t ask for this
+ * permission at runtime. To handle both cases, your app must first try to read the OBB file,
+ * and if it fails, you must request
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission at runtime.
+ * </p>
+ *
+ * <p>
+ * The following code snippet shows how to do this:
+ * </p>
+ *
+ * <pre>
+ * File obb = new File(obb_filename);
+ * boolean open_failed = false;
+ *
+ * try {
+ * BufferedReader br = new BufferedReader(new FileReader(obb));
+ * open_failed = false;
+ * ReadObbFile(br);
+ * } catch (IOException e) {
+ * open_failed = true;
+ * }
+ *
+ * if (open_failed) {
+ * // request READ_EXTERNAL_STORAGE permission before reading OBB file
+ * ReadObbFileWithPermission();
+ * }
+ * </pre>
+ *
+ * On devices with multiple users (as described by {@link UserManager}),
+ * multiple users may share the same OBB storage location. Applications
+ * should ensure that multiple instances running under different users don't
+ * interfere with each other.
+ *
+ * @return the absolute path to application-specific directory. May return
+ * {@code null} if shared storage is not currently available.
+ * @see #getObbDirs()
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ */
+ public abstract File getObbDir();
+
+ /**
+ * Returns absolute paths to application-specific directories on all
+ * shared/external storage devices where the application's OBB files (if
+ * there are any) can be found. Note if the application does not have any
+ * OBB files, these directories may not exist.
+ * <p>
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
+ * <ul>
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
+ *
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available. The first path returned is the same as
+ * {@link #getObbDir()}
+ * @see #getObbDir()
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ */
+ public abstract File[] getObbDirs();
+
+ /**
+ * Returns the absolute path to the application specific cache directory on
+ * the filesystem.
+ * <p>
+ * The system will automatically delete files in this directory as disk
+ * space is needed elsewhere on the device. The system will always delete
+ * older files first, as reported by {@link File#lastModified()}. If
+ * desired, you can exert more control over how files are deleted using
+ * {@link StorageManager#setCacheBehaviorGroup(File, boolean)} and
+ * {@link StorageManager#setCacheBehaviorTombstone(File, boolean)}.
+ * <p>
+ * Apps are strongly encouraged to keep their usage of cache space below the
+ * quota returned by
+ * {@link StorageManager#getCacheQuotaBytes(java.util.UUID)}. If your app
+ * goes above this quota, your cached files will be some of the first to be
+ * deleted when additional disk space is needed. Conversely, if your app
+ * stays under this quota, your cached files will be some of the last to be
+ * deleted when additional disk space is needed.
+ * <p>
+ * Note that your cache quota will change over time depending on how
+ * frequently the user interacts with your app, and depending on how much
+ * system-wide disk space is used.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * Apps require no extra permissions to read or write to the returned path,
+ * since this path lives in their private storage.
+ *
+ * @return The path of the directory holding application cache files.
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ * @see #getExternalCacheDir
+ */
+ public abstract File getCacheDir();
+
+ /**
+ * Returns the absolute path to the application specific cache directory on
+ * the filesystem designed for storing cached code.
+ * <p>
+ * The system will delete any files stored in this location both when your
+ * specific application is upgraded, and when the entire platform is
+ * upgraded.
+ * <p>
+ * This location is optimal for storing compiled or optimized code generated
+ * by your application at runtime.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * Apps require no extra permissions to read or write to the returned path,
+ * since this path lives in their private storage.
+ *
+ * @return The path of the directory holding application code cache files.
+ */
+ public abstract File getCodeCacheDir();
+
+ /**
+ * Returns absolute path to application-specific directory on the primary
+ * shared/external storage device where the application can place cache
+ * files it owns. These files are internal to the application, and not
+ * typically visible to the user as media.
+ * <p>
+ * This is like {@link #getCacheDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
+ * <ul>
+ * <li>The platform does not always monitor the space available in shared
+ * storage, and thus may not automatically delete these files. Apps should
+ * always manage the maximum space used in this location. Currently the only
+ * time files here will be deleted by the platform is when running on
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+ * {@link Environment#isExternalStorageEmulated(File)} returns true.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), its contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directory returned by
+ * {@link #getCacheDir()}.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+ * are required to read or write to the returned path; it's always
+ * accessible to the calling app. This only applies to paths generated for
+ * package name of the calling application. To access paths belonging to
+ * other packages,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+ * <p>
+ * On devices with multiple users (as described by {@link UserManager}),
+ * each user has their own isolated shared storage. Applications only have
+ * access to the shared storage for the user they're running as.
+ * <p>
+ * The returned path may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
+ *
+ * @return the absolute path to application-specific directory. May return
+ * {@code null} if shared storage is not currently available.
+ * @see #getCacheDir
+ * @see #getExternalCacheDirs()
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ */
+ @Nullable
+ public abstract File getExternalCacheDir();
+
+ /**
+ * Returns absolute path to application-specific directory in the preloaded cache.
+ * <p>Files stored in the cache directory can be deleted when the device runs low on storage.
+ * There is no guarantee when these files will be deleted.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @SystemApi
+ public abstract File getPreloadsFileCache();
+
+ /**
+ * Returns absolute paths to application-specific directories on all
+ * shared/external storage devices where the application can place cache
+ * files it owns. These files are internal to the application, and not
+ * typically visible to the user as media.
+ * <p>
+ * This is like {@link #getCacheDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
+ * <ul>
+ * <li>The platform does not always monitor the space available in shared
+ * storage, and thus may not automatically delete these files. Apps should
+ * always manage the maximum space used in this location. Currently the only
+ * time files here will be deleted by the platform is when running on
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+ * {@link Environment#isExternalStorageEmulated(File)} returns true.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directory returned by
+ * {@link #getCacheDir()}.
+ * <p>
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
+ * <p>
+ * The returned paths may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
+ *
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available. The first path returned is the same as
+ * {@link #getExternalCacheDir()}.
+ * @see #getExternalCacheDir()
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ */
+ public abstract File[] getExternalCacheDirs();
+
+ /**
+ * Returns absolute paths to application-specific directories on all
+ * shared/external storage devices where the application can place media
+ * files. These files are scanned and made available to other apps through
+ * {@link MediaStore}.
+ * <p>
+ * This is like {@link #getExternalFilesDirs} in that these files will be
+ * deleted when the application is uninstalled, however there are some
+ * important differences:
+ * <ul>
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
+ * </ul>
+ * <p>
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
+ * <p>
+ * The returned paths may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
+ *
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available.
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
+ * @deprecated These directories still exist and are scanned, but developers
+ * are encouraged to migrate to inserting content into a
+ * {@link MediaStore} collection directly, as any app can
+ * contribute new media to {@link MediaStore} with no
+ * permissions required, starting in
+ * {@link android.os.Build.VERSION_CODES#Q}.
+ */
+ @Deprecated
+ public abstract File[] getExternalMediaDirs();
+
+ /**
+ * Returns an array of strings naming the private files associated with
+ * this Context's application package.
+ *
+ * @return Array of strings naming the private files.
+ *
+ * @see #openFileInput
+ * @see #openFileOutput
+ * @see #deleteFile
+ */
+ public abstract String[] fileList();
+
+ /**
+ * Retrieve, creating if needed, a new directory in which the application
+ * can place its own custom data files. You can use the returned File
+ * object to create and access files in this directory. Note that files
+ * created through a File object will only be accessible by your own
+ * application; you can only set the mode of the entire directory, not
+ * of individual files.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * Apps require no extra permissions to read or write to the returned path,
+ * since this path lives in their private storage.
+ *
+ * @param name Name of the directory to retrieve. This is a directory
+ * that is created as part of your application data.
+ * @param mode Operating mode.
+ *
+ * @return A {@link File} object for the requested directory. The directory
+ * will have been created if it does not already exist.
+ *
+ * @see #openFileOutput(String, int)
+ */
+ public abstract File getDir(String name, @FileMode int mode);
+
+ /**
+ * Open a new private SQLiteDatabase associated with this Context's
+ * application package. Create the database file if it doesn't exist.
+ *
+ * @param name The name (unique in the application package) of the database.
+ * @param mode Operating mode.
+ * @param factory An optional factory class that is called to instantiate a
+ * cursor when query is called.
+ * @return The contents of a newly created database with the given name.
+ * @throws android.database.sqlite.SQLiteException if the database file
+ * could not be opened.
+ * @see #MODE_PRIVATE
+ * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING
+ * @see #MODE_NO_LOCALIZED_COLLATORS
+ * @see #deleteDatabase
+ */
+ public abstract SQLiteDatabase openOrCreateDatabase(String name,
+ @DatabaseMode int mode, CursorFactory factory);
+
+ /**
+ * Open a new private SQLiteDatabase associated with this Context's
+ * application package. Creates the database file if it doesn't exist.
+ * <p>
+ * Accepts input param: a concrete instance of {@link DatabaseErrorHandler}
+ * to be used to handle corruption when sqlite reports database corruption.
+ * </p>
+ *
+ * @param name The name (unique in the application package) of the database.
+ * @param mode Operating mode.
+ * @param factory An optional factory class that is called to instantiate a
+ * cursor when query is called.
+ * @param errorHandler the {@link DatabaseErrorHandler} to be used when
+ * sqlite reports database corruption. if null,
+ * {@link android.database.DefaultDatabaseErrorHandler} is
+ * assumed.
+ * @return The contents of a newly created database with the given name.
+ * @throws android.database.sqlite.SQLiteException if the database file
+ * could not be opened.
+ * @see #MODE_PRIVATE
+ * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING
+ * @see #MODE_NO_LOCALIZED_COLLATORS
+ * @see #deleteDatabase
+ */
+ public abstract SQLiteDatabase openOrCreateDatabase(String name,
+ @DatabaseMode int mode, CursorFactory factory,
+ @Nullable DatabaseErrorHandler errorHandler);
+
+ /**
+ * Move an existing database file from the given source storage context to
+ * this context. This is typically used to migrate data between storage
+ * locations after an upgrade, such as migrating to device protected
+ * storage.
+ * <p>
+ * The database must be closed before being moved.
+ *
+ * @param sourceContext The source context which contains the existing
+ * database to move.
+ * @param name The name of the database file.
+ * @return {@code true} if the move was successful or if the database didn't
+ * exist in the source context, otherwise {@code false}.
+ * @see #createDeviceProtectedStorageContext()
+ */
+ public abstract boolean moveDatabaseFrom(Context sourceContext, String name);
+
+ /**
+ * Delete an existing private SQLiteDatabase associated with this Context's
+ * application package.
+ *
+ * @param name The name (unique in the application package) of the
+ * database.
+ *
+ * @return {@code true} if the database was successfully deleted; else {@code false}.
+ *
+ * @see #openOrCreateDatabase
+ */
+ public abstract boolean deleteDatabase(String name);
+
+ /**
+ * Returns the absolute path on the filesystem where a database created with
+ * {@link #openOrCreateDatabase} is stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ *
+ * @param name The name of the database for which you would like to get
+ * its path.
+ *
+ * @return An absolute path to the given database.
+ *
+ * @see #openOrCreateDatabase
+ */
+ public abstract File getDatabasePath(String name);
+
+ /**
+ * Returns an array of strings naming the private databases associated with
+ * this Context's application package.
+ *
+ * @return Array of strings naming the private databases.
+ *
+ * @see #openOrCreateDatabase
+ * @see #deleteDatabase
+ */
+ public abstract String[] databaseList();
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#getDrawable
+ * WallpaperManager.get()} instead.
+ */
+ @Deprecated
+ public abstract Drawable getWallpaper();
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#peekDrawable
+ * WallpaperManager.peek()} instead.
+ */
+ @Deprecated
+ public abstract Drawable peekWallpaper();
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#getDesiredMinimumWidth()
+ * WallpaperManager.getDesiredMinimumWidth()} instead.
+ */
+ @Deprecated
+ public abstract int getWallpaperDesiredMinimumWidth();
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#getDesiredMinimumHeight()
+ * WallpaperManager.getDesiredMinimumHeight()} instead.
+ */
+ @Deprecated
+ public abstract int getWallpaperDesiredMinimumHeight();
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#setBitmap(Bitmap)
+ * WallpaperManager.set()} instead.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#SET_WALLPAPER}.
+ */
+ @Deprecated
+ public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#setStream(InputStream)
+ * WallpaperManager.set()} instead.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#SET_WALLPAPER}.
+ */
+ @Deprecated
+ public abstract void setWallpaper(InputStream data) throws IOException;
+
+ /**
+ * @deprecated Use {@link android.app.WallpaperManager#clear
+ * WallpaperManager.clear()} instead.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#SET_WALLPAPER}.
+ */
+ @Deprecated
+ public abstract void clearWallpaper() throws IOException;
+
+ /**
+ * Same as {@link #startActivity(Intent, Bundle)} with no options
+ * specified.
+ *
+ * @param intent The description of the activity to start.
+ *
+ * @throws ActivityNotFoundException
+ *`
+ * @see #startActivity(Intent, Bundle)
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivity(@RequiresPermission Intent intent);
+
+ /**
+ * Version of {@link #startActivity(Intent)} that allows you to specify the
+ * user the activity will be started for. This is not available to applications
+ * that are not pre-installed on the system image.
+ * @param intent The description of the activity to start.
+ * @param user The UserHandle of the user to start this activity for.
+ * @throws ActivityNotFoundException
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
+ @NonNull UserHandle user) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Launch a new activity. You will not receive any information about when
+ * the activity exits.
+ *
+ * <p>Note that if this method is being called from outside of an
+ * {@link android.app.Activity} Context, then the Intent must include
+ * the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag. This is because,
+ * without being started from an existing Activity, there is no existing
+ * task in which to place the new activity and thus it needs to be placed
+ * in its own separate task.
+ *
+ * <p>This method throws {@link ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intent The description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see #startActivity(Intent)
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivity(@RequiresPermission Intent intent,
+ @Nullable Bundle options);
+
+ /**
+ * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the
+ * user the activity will be started for. This is not available to applications
+ * that are not pre-installed on the system image.
+ * @param intent The description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ * @param userId The UserHandle of the user to start this activity for.
+ * @throws ActivityNotFoundException
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage
+ public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
+ UserHandle userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Version of {@link #startActivity(Intent, Bundle)} that returns a result to the caller. This
+ * is only supported for Views and Fragments.
+ * @param who The identifier for the calling element that will receive the result.
+ * @param intent The intent to start.
+ * @param requestCode The code that will be returned with onActivityResult() identifying this
+ * request.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void startActivityForResult(
+ @NonNull String who, Intent intent, int requestCode, @Nullable Bundle options) {
+ throw new RuntimeException("This method is only implemented for Activity-based Contexts. "
+ + "Check canStartActivityForResult() before calling.");
+ }
+
+ /**
+ * Identifies whether this Context instance will be able to process calls to
+ * {@link #startActivityForResult(String, Intent, int, Bundle)}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean canStartActivityForResult() {
+ return false;
+ }
+
+ /**
+ * Same as {@link #startActivities(Intent[], Bundle)} with no options
+ * specified.
+ *
+ * @param intents An array of Intents to be started.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see #startActivities(Intent[], Bundle)
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivities(@RequiresPermission Intent[] intents);
+
+ /**
+ * Launch multiple new activities. This is generally the same as calling
+ * {@link #startActivity(Intent)} for the first Intent in the array,
+ * that activity during its creation calling {@link #startActivity(Intent)}
+ * for the second entry, etc. Note that unlike that approach, generally
+ * none of the activities except the last in the array will be created
+ * at this point, but rather will be created when the user first visits
+ * them (due to pressing back from the activity on top).
+ *
+ * <p>This method throws {@link ActivityNotFoundException}
+ * if there was no Activity found for <em>any</em> given Intent. In this
+ * case the state of the activity stack is undefined (some Intents in the
+ * list may be on it, some not), so you probably want to avoid such situations.
+ *
+ * @param intents An array of Intents to be started.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see #startActivities(Intent[])
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivities(@RequiresPermission Intent[] intents, Bundle options);
+
+ /**
+ * @hide
+ * Launch multiple new activities. This is generally the same as calling
+ * {@link #startActivity(Intent)} for the first Intent in the array,
+ * that activity during its creation calling {@link #startActivity(Intent)}
+ * for the second entry, etc. Note that unlike that approach, generally
+ * none of the activities except the last in the array will be created
+ * at this point, but rather will be created when the user first visits
+ * them (due to pressing back from the activity on top).
+ *
+ * <p>This method throws {@link ActivityNotFoundException}
+ * if there was no Activity found for <em>any</em> given Intent. In this
+ * case the state of the activity stack is undefined (some Intents in the
+ * list may be on it, some not), so you probably want to avoid such situations.
+ *
+ * @param intents An array of Intents to be started.
+ * @param options Additional options for how the Activity should be started.
+ * @param userHandle The user for whom to launch the activities
+ * 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)
+ public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+ * with no options specified.
+ *
+ * @param intent The IntentSender to launch.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ *
+ * @see #startActivity(Intent)
+ * @see #startIntentSender(IntentSender, Intent, int, int, int, Bundle)
+ */
+ public abstract void startIntentSender(IntentSender intent, @Nullable Intent fillInIntent,
+ @Intent.MutableFlags int flagsMask, @Intent.MutableFlags int flagsValues,
+ int extraFlags) throws IntentSender.SendIntentException;
+
+ /**
+ * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
+ * to start. If the IntentSender is for an activity, that activity will be started
+ * as if you had called the regular {@link #startActivity(Intent)}
+ * here; otherwise, its associated action will be executed (such as
+ * sending a broadcast) as if you had called
+ * {@link IntentSender#sendIntent IntentSender.sendIntent} on it.
+ *
+ * @param intent The IntentSender to launch.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details. If options
+ * have also been supplied by the IntentSender, options given here will
+ * override any that conflict with those given by the IntentSender.
+ *
+ * @see #startActivity(Intent, Bundle)
+ * @see #startIntentSender(IntentSender, Intent, int, int, int)
+ */
+ public abstract void startIntentSender(IntentSender intent, @Nullable Intent fillInIntent,
+ @Intent.MutableFlags int flagsMask, @Intent.MutableFlags int flagsValues,
+ int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException;
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run. No results are propagated from
+ * receivers and receivers can not abort the broadcast. If you want
+ * to allow receivers to propagate results or abort the broadcast, you must
+ * send an ordered broadcast using
+ * {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendBroadcast(@RequiresPermission Intent intent);
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers, allowing
+ * an optional required permission to be enforced. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run. No results are propagated from
+ * receivers and receivers can not abort the broadcast. If you want
+ * to allow receivers to propagate results or abort the broadcast, you must
+ * send an ordered broadcast using
+ * {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission (optional) String naming a permission that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendBroadcast(@RequiresPermission Intent intent,
+ @Nullable String receiverPermission);
+
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers, allowing
+ * an array of required permissions to be enforced. This call is asynchronous; it returns
+ * immediately, and you will continue executing while the receivers are run. No results are
+ * propagated from receivers and receivers can not abort the broadcast. If you want to allow
+ * receivers to propagate results or abort the broadcast, you must send an ordered broadcast
+ * using {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermissions Array of names of permissions that a receiver must hold
+ * in order to receive your broadcast.
+ * If empty, no permissions are required.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ * @hide
+ */
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Like {@link #sendBroadcastMultiplePermissions(Intent, String[])}, but also allows
+ * specification of a list of excluded permissions. This allows sending a broadcast to an
+ * app that has the permissions in `receiverPermissions` but not `excludedPermissions`.
+ * @hide
+ */
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Version of {@link #sendBroadcastMultiplePermissions(Intent, String[])} that allows you to
+ * specify the {@link android.app.BroadcastOptions}.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermissions Array of names of permissions that a receiver must hold
+ * in order to receive your broadcast.
+ * If empty, no permissions are required.
+ * @param options Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @see #sendBroadcastMultiplePermissions(Intent, String[])
+ * @see android.app.BroadcastOptions
+ * @hide
+ */
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers, allowing
+ * an array of required permissions to be enforced. This call is asynchronous; it returns
+ * immediately, and you will continue executing while the receivers are run. No results are
+ * propagated from receivers and receivers can not abort the broadcast. If you want to allow
+ * receivers to propagate results or abort the broadcast, you must send an ordered broadcast
+ * using {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermissions Array of names of permissions that a receiver must hold
+ * in order to receive your broadcast.
+ * If empty, no permissions are required.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public void sendBroadcastWithMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions) {
+ sendBroadcastMultiplePermissions(intent, receiverPermissions);
+ }
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers, allowing
+ * an array of required permissions to be enforced. This call is asynchronous; it returns
+ * immediately, and you will continue executing while the receivers are run. No results are
+ * propagated from receivers and receivers can not abort the broadcast. If you want to allow
+ * receivers to propagate results or abort the broadcast, you must send an ordered broadcast
+ * using {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user The user to send the broadcast to.
+ * @param receiverPermissions Array of names of permissions that a receiver must hold
+ * in order to receive your broadcast.
+ * If null or empty, no permissions are required.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+ String[] receiverPermissions);
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers, allowing
+ * an optional required permission to be enforced. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run. No results are propagated from
+ * receivers and receivers can not abort the broadcast. If you want
+ * to allow receivers to propagate results or abort the broadcast, you must
+ * send an ordered broadcast using
+ * {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission (optional) String naming a permission that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ public abstract void sendBroadcast(Intent intent,
+ @Nullable String receiverPermission,
+ @Nullable Bundle options);
+
+ /**
+ * Like {@link #sendBroadcast(Intent, String)}, but also allows specification
+ * of an associated app op as per {@link android.app.AppOpsManager}.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void sendBroadcast(Intent intent,
+ String receiverPermission, int appOp);
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers, delivering
+ * them one at a time to allow more preferred receivers to consume the
+ * broadcast before it is delivered to less preferred receivers. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission (optional) String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ *
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendOrderedBroadcast(@RequiresPermission Intent intent,
+ @Nullable String receiverPermission);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent)} that allows you to
+ * receive data back from the broadcast. This is accomplished by
+ * supplying your own BroadcastReceiver when calling, which will be
+ * treated as a final receiver at the end of the broadcast -- its
+ * {@link BroadcastReceiver#onReceive} method will be called with
+ * the result values collected from the other receivers. The broadcast will
+ * be serialized in the same way as calling
+ * {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>Like {@link #sendBroadcast(Intent)}, this method is
+ * asynchronous; it will return before
+ * resultReceiver.onReceive() is called.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendBroadcast(Intent)
+ * @see #sendBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see android.app.Activity#RESULT_OK
+ */
+ public abstract void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent,
+ @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent)} that allows you to
+ * receive data back from the broadcast. This is accomplished by
+ * supplying your own BroadcastReceiver when calling, which will be
+ * treated as a final receiver at the end of the broadcast -- its
+ * {@link BroadcastReceiver#onReceive} method will be called with
+ * the result values collected from the other receivers. The broadcast will
+ * be serialized in the same way as calling
+ * {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>Like {@link #sendBroadcast(Intent)}, this method is
+ * asynchronous; it will return before
+ * resultReceiver.onReceive() is called.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ * @see #sendBroadcast(Intent)
+ * @see #sendBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see android.app.Activity#RESULT_OK
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ public abstract void sendOrderedBroadcast(@NonNull Intent intent,
+ @Nullable String receiverPermission, @Nullable Bundle options,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras);
+
+ /**
+ * Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler,
+ * int, String, android.os.Bundle)}, but also allows specification
+ * of an associated app op as per {@link android.app.AppOpsManager}.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ * @param intent The intent to broadcast
+ * @param user UserHandle to send the intent to.
+ * @see #sendBroadcast(Intent)
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent, String)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param receiverPermission (optional) String naming a permission that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ *
+ * @see #sendBroadcast(Intent, String)
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, @Nullable String receiverPermission);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent, String, Bundle)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param receiverPermission (optional) String naming a permission that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ *
+ * @see #sendBroadcast(Intent, String, Bundle)
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, @Nullable String receiverPermission, @Nullable Bundle options);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent, String)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param receiverPermission (optional) String naming a permission that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param appOp The app op associated with the broadcast.
+ *
+ * @see #sendBroadcast(Intent, String)
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, @Nullable String receiverPermission, int appOp);
+
+ /**
+ * Version of
+ * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)}
+ * that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public abstract void sendOrderedBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, @Nullable String receiverPermission, BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
+
+ /**
+ * Similar to above but takes an appOp as well, to enforce restrictions.
+ * @see #sendOrderedBroadcastAsUser(Intent, UserHandle, String,
+ * BroadcastReceiver, Handler, int, String, Bundle)
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
+
+ /**
+ * Similar to above but takes an appOp as well, to enforce restrictions, and an options Bundle.
+ * @see #sendOrderedBroadcastAsUser(Intent, UserHandle, String,
+ * BroadcastReceiver, Handler, int, String, Bundle)
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage
+ public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, int appOp, @Nullable Bundle options,
+ BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode,
+ @Nullable String initialData, @Nullable Bundle initialExtras);
+
+ /**
+ * Version of
+ * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String,
+ * Bundle)} that allows you to specify the App Op to enforce restrictions on which receivers
+ * the broadcast will be sent to.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param receiverAppOp The app op associated with the broadcast. If null, no appOp is
+ * required. If both receiverAppOp and receiverPermission are non-null,
+ * a receiver must have both of them to
+ * receive the broadcast
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public void sendOrderedBroadcast(@NonNull Intent intent, @Nullable String receiverPermission,
+ @Nullable String receiverAppOp, @Nullable BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Version of
+ * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String,
+ * Bundle)} that allows you to specify the App Op to enforce restrictions on which receivers
+ * the broadcast will be sent to as well as supply an optional sending options
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param receiverAppOp The app op associated with the broadcast. If null, no appOp is
+ * required. If both receiverAppOp and receiverPermission are non-null,
+ * a receiver must have both of them to
+ * receive the broadcast
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ * @see android.app.BroadcastOptions
+ * @hide
+ */
+ public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent, int initialCode,
+ @Nullable String receiverPermission, @Nullable String receiverAppOp,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ @Nullable String initialData, @Nullable Bundle initialExtras,
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
+ * Intent you are sending stays around after the broadcast is complete,
+ * so that others can quickly retrieve that data through the return
+ * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In
+ * all other ways, this behaves the same as
+ * {@link #sendBroadcast(Intent)}.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast, and the Intent will be held to
+ * be re-broadcast to future receivers.
+ *
+ * @see #sendBroadcast(Intent)
+ * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
+ public abstract void sendStickyBroadcast(@RequiresPermission Intent intent);
+
+ /**
+ * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
+ * Intent you are sending stays around after the broadcast is complete,
+ * so that others can quickly retrieve that data through the return
+ * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In
+ * all other ways, this behaves the same as
+ * {@link #sendBroadcast(Intent)}.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast, and the Intent will be held to
+ * be re-broadcast to future receivers.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ *
+ * @see #sendBroadcast(Intent)
+ * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
+ public void sendStickyBroadcast(@RequiresPermission @NonNull Intent intent,
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * <p>Version of {@link #sendStickyBroadcast} that allows you to
+ * receive data back from the broadcast. This is accomplished by
+ * supplying your own BroadcastReceiver when calling, which will be
+ * treated as a final receiver at the end of the broadcast -- its
+ * {@link BroadcastReceiver#onReceive} method will be called with
+ * the result values collected from the other receivers. The broadcast will
+ * be serialized in the same way as calling
+ * {@link #sendOrderedBroadcast(Intent, String)}.
+ *
+ * <p>Like {@link #sendBroadcast(Intent)}, this method is
+ * asynchronous; it will return before
+ * resultReceiver.onReceive() is called. Note that the sticky data
+ * stored is only the data you initially supply to the broadcast, not
+ * the result of any changes made by the receivers.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendBroadcast(Intent)
+ * @see #sendBroadcast(Intent, String)
+ * @see #sendOrderedBroadcast(Intent, String)
+ * @see #sendStickyBroadcast(Intent)
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see android.app.Activity#RESULT_OK
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
+ public abstract void sendStickyOrderedBroadcast(@RequiresPermission Intent intent,
+ BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
+
+ /**
+ * <p>Remove the data previously sent with {@link #sendStickyBroadcast},
+ * so that it is as if the sticky broadcast had never happened.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent that was previously broadcast.
+ *
+ * @see #sendStickyBroadcast
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
+ public abstract void removeStickyBroadcast(@RequiresPermission Intent intent);
+
+ /**
+ * <p>Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast, and the Intent will be held to
+ * be re-broadcast to future receivers.
+ * @param user UserHandle to send the intent to.
+ *
+ * @see #sendBroadcast(Intent)
+ */
+ @Deprecated
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.BROADCAST_STICKY
+ })
+ public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user);
+
+ /**
+ * @hide
+ * This is just here for sending CONNECTIVITY_ACTION.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.BROADCAST_STICKY
+ })
+ public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, Bundle options);
+
+ /**
+ * <p>Version of
+ * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)}
+ * that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ @Deprecated
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.BROADCAST_STICKY
+ })
+ public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
+
+ /**
+ * <p>Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image.
+ *
+ * <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY}
+ * permission in order to use this API. If you do not hold that
+ * permission, {@link SecurityException} will be thrown.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent that was previously broadcast.
+ * @param user UserHandle to remove the sticky broadcast from.
+ *
+ * @see #sendStickyBroadcastAsUser
+ */
+ @Deprecated
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.BROADCAST_STICKY
+ })
+ public abstract void removeStickyBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user);
+
+ /**
+ * Register a BroadcastReceiver to be run in the main activity thread. The
+ * <var>receiver</var> will be called with any broadcast Intent that
+ * matches <var>filter</var>, in the main application thread.
+ *
+ * <p>The system may broadcast Intents that are "sticky" -- these stay
+ * around after the broadcast has finished, to be sent to any later
+ * registrations. If your IntentFilter matches one of these sticky
+ * Intents, that Intent will be returned by this function
+ * <strong>and</strong> sent to your <var>receiver</var> as if it had just
+ * been broadcast.
+ *
+ * <p>There may be multiple sticky Intents that match <var>filter</var>,
+ * in which case each of these will be sent to <var>receiver</var>. In
+ * this case, only one of these can be returned directly by the function;
+ * which of these that is returned is arbitrarily decided by the system.
+ *
+ * <p>If you know the Intent your are registering for is sticky, you can
+ * supply null for your <var>receiver</var>. In this case, no receiver is
+ * registered -- the function simply returns the sticky Intent that
+ * matches <var>filter</var>. In the case of multiple matches, the same
+ * rules as described above apply.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
+ * registered with this method will correctly respect the
+ * {@link Intent#setPackage(String)} specified for an Intent being broadcast.
+ * Prior to that, it would be ignored and delivered to all matching registered
+ * receivers. Be careful if using this for security.</p>
+ *
+ * <p class="note">Note: this method <em>cannot be called from a
+ * {@link BroadcastReceiver} component;</em> that is, from a BroadcastReceiver
+ * that is declared in an application's manifest. It is okay, however, to call
+ * this method from another BroadcastReceiver that has itself been registered
+ * at run time with {@link #registerReceiver}, since the lifetime of such a
+ * registered BroadcastReceiver is tied to the object that registered it.</p>
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param filter Selects the Intent broadcasts to be received.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or null if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ */
+ @Nullable
+ public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
+ IntentFilter filter);
+
+ /**
+ * Register to receive intent broadcasts, with the receiver optionally being
+ * exposed to Instant Apps. See
+ * {@link #registerReceiver(BroadcastReceiver, IntentFilter)} for more
+ * information. By default Instant Apps cannot interact with receivers in other
+ * applications, this allows you to expose a receiver that Instant Apps can
+ * interact with.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
+ * registered with this method will correctly respect the
+ * {@link Intent#setPackage(String)} specified for an Intent being broadcast.
+ * Prior to that, it would be ignored and delivered to all matching registered
+ * receivers. Be careful if using this for security.</p>
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param filter Selects the Intent broadcasts to be received.
+ * @param flags Additional options for the receiver. May be 0 or
+ * {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or null if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter)
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ */
+ @Nullable
+ public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
+ IntentFilter filter,
+ @RegisterReceiverFlags int flags);
+
+ /**
+ * Register to receive intent broadcasts, to run in the context of
+ * <var>scheduler</var>. See
+ * {@link #registerReceiver(BroadcastReceiver, IntentFilter)} for more
+ * information. This allows you to enforce permissions on who can
+ * broadcast intents to your receiver, or have the receiver run in
+ * a different thread than the main application thread.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
+ * registered with this method will correctly respect the
+ * {@link Intent#setPackage(String)} specified for an Intent being broadcast.
+ * Prior to that, it would be ignored and delivered to all matching registered
+ * receivers. Be careful if using this for security.</p>
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param filter Selects the Intent broadcasts to be received.
+ * @param broadcastPermission String naming a permissions that a
+ * broadcaster must hold in order to send an Intent to you. If null,
+ * no permission is required.
+ * @param scheduler Handler identifying the thread that will receive
+ * the Intent. If null, the main thread of the process will be used.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or null if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter)
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ */
+ @Nullable
+ public abstract Intent registerReceiver(BroadcastReceiver receiver,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler);
+
+ /**
+ * Register to receive intent broadcasts, to run in the context of
+ * <var>scheduler</var>. See
+ * {@link #registerReceiver(BroadcastReceiver, IntentFilter, int)} and
+ * {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)}
+ * for more information.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers
+ * registered with this method will correctly respect the
+ * {@link Intent#setPackage(String)} specified for an Intent being broadcast.
+ * Prior to that, it would be ignored and delivered to all matching registered
+ * receivers. Be careful if using this for security.</p>
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param filter Selects the Intent broadcasts to be received.
+ * @param broadcastPermission String naming a permissions that a
+ * broadcaster must hold in order to send an Intent to you. If null,
+ * no permission is required.
+ * @param scheduler Handler identifying the thread that will receive
+ * the Intent. If null, the main thread of the process will be used.
+ * @param flags Additional options for the receiver. May be 0 or
+ * {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or null if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, int)
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ */
+ @Nullable
+ public abstract Intent registerReceiver(BroadcastReceiver receiver,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler, @RegisterReceiverFlags int flags);
+
+ /**
+ * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)}
+ * but this receiver will receive broadcasts that are sent to all users. The receiver can
+ * use {@link BroadcastReceiver#getSendingUser} to determine on which user the broadcast
+ * was sent.
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param filter Selects the Intent broadcasts to be received.
+ * @param broadcastPermission String naming a permissions that a
+ * broadcaster must hold in order to send an Intent to you. If {@code null},
+ * no permission is required.
+ * @param scheduler Handler identifying the thread that will receive
+ * the Intent. If {@code null}, the main thread of the process will be used.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or {@code null} if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @SystemApi
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * @hide
+ * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+ * but for a specific user. This receiver will receiver broadcasts that
+ * are sent to the requested user.
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param filter Selects the Intent broadcasts to be received.
+ * @param broadcastPermission String naming a permissions that a
+ * broadcaster must hold in order to send an Intent to you. If null,
+ * no permission is required.
+ * @param scheduler Handler identifying the thread that will receive
+ * the Intent. If null, the main thread of the process will be used.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or null if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @UnsupportedAppUsage
+ public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver,
+ UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler);
+
+ /**
+ * Unregister a previously registered BroadcastReceiver. <em>All</em>
+ * filters that have been registered for this BroadcastReceiver will be
+ * removed.
+ *
+ * @param receiver The BroadcastReceiver to unregister.
+ *
+ * @see #registerReceiver
+ */
+ public abstract void unregisterReceiver(BroadcastReceiver receiver);
+
+ /**
+ * Request that a given application service be started. The Intent
+ * should either contain the complete class name of a specific service
+ * implementation to start, or a specific package name to target. If the
+ * Intent is less specified, it logs a warning about this. In this case any of the
+ * multiple matching services may be used. If this service
+ * is not already running, it will be instantiated and started (creating a
+ * process for it if needed); if it is running then it remains running.
+ *
+ * <p>Every call to this method will result in a corresponding call to
+ * the target service's {@link android.app.Service#onStartCommand} method,
+ * with the <var>intent</var> given here. This provides a convenient way
+ * to submit jobs to a service without having to bind and call on to its
+ * interface.
+ *
+ * <p>Using startService() overrides the default service lifetime that is
+ * managed by {@link #bindService}: it requires the service to remain
+ * running until {@link #stopService} is called, regardless of whether
+ * any clients are connected to it. Note that calls to startService()
+ * do not nest: no matter how many times you call startService(),
+ * a single call to {@link #stopService} will stop it.
+ *
+ * <p>The system attempts to keep running services around as much as
+ * possible. The only time they should be stopped is if the current
+ * foreground application is using so many resources that the service needs
+ * to be killed. If any errors happen in the service's process, it will
+ * automatically be restarted.
+ *
+ * <p>This function will throw {@link SecurityException} if you do not
+ * have permission to start the given service.
+ *
+ * <div class="caution">
+ * <p><strong>Note:</strong> Each call to startService()
+ * results in significant work done by the system to manage service
+ * lifecycle surrounding the processing of the intent, which can take
+ * multiple milliseconds of CPU time. Due to this cost, startService()
+ * should not be used for frequent intent delivery to a service, and only
+ * for scheduling significant work. Use {@link #bindService bound services}
+ * for high frequency calls.
+ * </p>
+ *
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#O},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#O}
+ * or higher are not allowed to start background services from the background.
+ * See
+ * <a href="{@docRoot}/about/versions/oreo/background">
+ * Background Execution Limits</a>
+ * for more details.
+ *
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
+ * or higher are not allowed to start foreground services from the background.
+ * See
+ * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * Behavior changes: Apps targeting Android 12
+ * </a>
+ * for more details.
+ * </div>
+ *
+ * @param service Identifies the service to be started. The Intent must be
+ * fully explicit (supplying a component name). Additional values
+ * may be included in the Intent extras to supply arguments along with
+ * this specific start call.
+ *
+ * @return If the service is being started or is already running, the
+ * {@link ComponentName} of the actual service that was started is
+ * returned; else if the service does not exist null is returned.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ * or the service can not be found.
+ * @throws IllegalStateException
+ * Before Android {@link android.os.Build.VERSION_CODES#S},
+ * if the application is in a state where the service
+ * can not be started (such as not in the foreground in a state when services are allowed),
+ * {@link IllegalStateException} was thrown.
+ * @throws android.app.BackgroundServiceStartNotAllowedException
+ * On Android {@link android.os.Build.VERSION_CODES#S} and later,
+ * if the application is in a state where the service
+ * can not be started (such as not in the foreground in a state when services are allowed),
+ * {@link android.app.BackgroundServiceStartNotAllowedException} is thrown
+ * This excemption extends {@link IllegalStateException}, so apps can
+ * use {@code catch (IllegalStateException)} to catch both.
+ *
+ * @see #startForegroundService(Intent)
+ * @see #stopService
+ * @see #bindService
+ */
+ @Nullable
+ public abstract ComponentName startService(Intent service);
+
+ /**
+ * Similar to {@link #startService(Intent)}, but with an implicit promise that the
+ * Service will call {@link android.app.Service#startForeground(int, android.app.Notification)
+ * startForeground(int, android.app.Notification)} once it begins running. The service is given
+ * an amount of time comparable to the ANR interval to do this, otherwise the system
+ * will automatically stop the service and declare the app ANR.
+ *
+ * <p>Unlike the ordinary {@link #startService(Intent)}, this method can be used
+ * at any time, regardless of whether the app hosting the service is in a foreground
+ * state.
+ *
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
+ * or higher are not allowed to start foreground services from the background.
+ * See
+ * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * Behavior changes: Apps targeting Android 12
+ * </a>
+ * for more details.
+ * </div>
+ *
+ * @param service Identifies the service to be started. The Intent must be
+ * fully explicit (supplying a component name). Additional values
+ * may be included in the Intent extras to supply arguments along with
+ * this specific start call.
+ *
+ * @return If the service is being started or is already running, the
+ * {@link ComponentName} of the actual service that was started is
+ * returned; else if the service does not exist null is returned.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ * or the service can not be found.
+ *
+ * @throws android.app.ForegroundServiceStartNotAllowedException
+ * If the caller app's targeting API is
+ * {@link android.os.Build.VERSION_CODES#S} or later, and the foreground service is restricted
+ * from start due to background restriction.
+ *
+ * @see #stopService
+ * @see android.app.Service#startForeground(int, android.app.Notification)
+ */
+ @Nullable
+ public abstract ComponentName startForegroundService(Intent service);
+
+ /**
+ * @hide like {@link #startForegroundService(Intent)} but for a specific user.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public abstract ComponentName startForegroundServiceAsUser(Intent service, UserHandle user);
+
+ /**
+ * Request that a given application service be stopped. If the service is
+ * not running, nothing happens. Otherwise it is stopped. Note that calls
+ * to startService() are not counted -- this stops the service no matter
+ * how many times it was started.
+ *
+ * <p>Note that if a stopped service still has {@link ServiceConnection}
+ * objects bound to it with the {@link #BIND_AUTO_CREATE} set, it will
+ * not be destroyed until all of these bindings are removed. See
+ * the {@link android.app.Service} documentation for more details on a
+ * service's lifecycle.
+ *
+ * <p>This function will throw {@link SecurityException} if you do not
+ * have permission to stop the given service.
+ *
+ * @param service Description of the service to be stopped. The Intent must be either
+ * fully explicit (supplying a component name) or specify a specific package
+ * name it is targeted to.
+ *
+ * @return If there is a service matching the given Intent that is already
+ * running, then it is stopped and {@code true} is returned; else {@code false} is returned.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ * or the service can not be found.
+ * @throws IllegalStateException If the application is in a state where the service
+ * can not be started (such as not in the foreground in a state when services are allowed).
+ *
+ * @see #startService
+ */
+ public abstract boolean stopService(Intent service);
+
+ /**
+ * @hide like {@link #startService(Intent)} but for a specific user.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage
+ public abstract ComponentName startServiceAsUser(Intent service, UserHandle user);
+
+ /**
+ * @hide like {@link #stopService(Intent)} but for a specific user.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
+
+ /**
+ * Connect to an application service, creating it if needed. This defines
+ * a dependency between your application and the service. The given
+ * <var>conn</var> will receive the service object when it is created and be
+ * told if it dies and restarts. The service will be considered required
+ * by the system only for as long as the calling context exists. For
+ * example, if this Context is an Activity that is stopped, the service will
+ * not be required to continue running until the Activity is resumed.
+ *
+ * <p>If the service does not support binding, it may return {@code null} from
+ * its {@link android.app.Service#onBind(Intent) onBind()} method. If it does, then
+ * the ServiceConnection's
+ * {@link ServiceConnection#onNullBinding(ComponentName) onNullBinding()} method
+ * will be invoked instead of
+ * {@link ServiceConnection#onServiceConnected(ComponentName, IBinder) onServiceConnected()}.
+ *
+ * <p>This method will throw {@link SecurityException} if the calling app does not
+ * have permission to bind to the given service.
+ *
+ * <p class="note">Note: this method <em>cannot be called from a
+ * {@link BroadcastReceiver} component</em>. A pattern you can use to
+ * communicate from a BroadcastReceiver to a Service is to call
+ * {@link #startService} with the arguments containing the command to be
+ * sent, with the service calling its
+ * {@link android.app.Service#stopSelf(int)} method when done executing
+ * that command. See the API demo App/Service/Service Start Arguments
+ * Controller for an illustration of this. It is okay, however, to use
+ * this method from a BroadcastReceiver that has been registered with
+ * {@link #registerReceiver}, since the lifetime of this BroadcastReceiver
+ * is tied to another object (the one that registered it).</p>
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param flags Operation options for the binding. May be 0,
+ * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+ * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+ * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_IMPORTANT}, {@link #BIND_ADJUST_WITH_ACTIVITY},
+ * {@link #BIND_NOT_PERCEPTIBLE}, or {@link #BIND_INCLUDE_CAPABILITIES}.
+ * @return {@code true} if the system is in the process of bringing up a
+ * service that your client has permission to bind to; {@code false}
+ * if the system couldn't find the service or if your client doesn't
+ * have permission to bind to it. If this value is {@code true}, you
+ * should later call {@link #unbindService} to release the
+ * connection.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ * or the service can not be found.
+ *
+ * @see #unbindService
+ * @see #startService
+ * @see #BIND_AUTO_CREATE
+ * @see #BIND_DEBUG_UNBIND
+ * @see #BIND_NOT_FOREGROUND
+ * @see #BIND_ABOVE_CLIENT
+ * @see #BIND_ALLOW_OOM_MANAGEMENT
+ * @see #BIND_WAIVE_PRIORITY
+ * @see #BIND_IMPORTANT
+ * @see #BIND_ADJUST_WITH_ACTIVITY
+ * @see #BIND_NOT_PERCEPTIBLE
+ * @see #BIND_INCLUDE_CAPABILITIES
+ */
+ public abstract boolean bindService(@RequiresPermission Intent service,
+ @NonNull ServiceConnection conn, @BindServiceFlags int flags);
+
+ /**
+ * Same as {@link #bindService(Intent, ServiceConnection, int)} with executor to control
+ * ServiceConnection callbacks.
+ * @param executor Callbacks on ServiceConnection will be called on executor. Must use same
+ * instance for the same instance of ServiceConnection.
+ */
+ public boolean bindService(@RequiresPermission @NonNull Intent service,
+ @BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
+ @NonNull ServiceConnection conn) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Variation of {@link #bindService} that, in the specific case of isolated
+ * services, allows the caller to generate multiple instances of a service
+ * from a single component declaration. In other words, you can use this to bind
+ * to a service that has specified {@link android.R.attr#isolatedProcess} and, in
+ * addition to the existing behavior of running in an isolated process, you can
+ * also through the arguments here have the system bring up multiple concurrent
+ * processes hosting their own instances of that service. The <var>instanceName</var>
+ * you provide here identifies the different instances, and you can use
+ * {@link #updateServiceGroup(ServiceConnection, int, int)} to tell the system how it
+ * should manage each of these instances.
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param flags Operation options for the binding as per {@link #bindService}.
+ * @param instanceName Unique identifier for the service instance. Each unique
+ * name here will result in a different service instance being created. Identifiers
+ * must only contain ASCII letters, digits, underscores, and periods.
+ * @return Returns success of binding as per {@link #bindService}.
+ * @param executor Callbacks on ServiceConnection will be called on executor.
+ * Must use same instance for the same instance of ServiceConnection.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ * @throws IllegalArgumentException If the instanceName is invalid.
+ *
+ * @see #bindService
+ * @see #updateServiceGroup
+ * @see android.R.attr#isolatedProcess
+ */
+ public boolean bindIsolatedService(@RequiresPermission @NonNull Intent service,
+ @BindServiceFlags int flags, @NonNull String instanceName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull ServiceConnection conn) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Binds to a service in the given {@code user} in the same manner as
+ * {@link #bindService(Intent, ServiceConnection, int)}.
+ *
+ * <p>If the given {@code user} is in the same profile group and the target package is the
+ * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+ * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+ * for interacting with other users.
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param flags Operation options for the binding. May be 0,
+ * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+ * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+ * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_IMPORTANT}, or
+ * {@link #BIND_ADJUST_WITH_ACTIVITY}.
+ * @return {@code true} if the system is in the process of bringing up a
+ * service that your client has permission to bind to; {@code false}
+ * if the system couldn't find the service. If this value is {@code true}, you
+ * should later call {@link #unbindService} to release the
+ * connection.
+ *
+ * @throws SecurityException if the client does not have the required permission to bind.
+ */
+ @SuppressWarnings("unused")
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
+ public boolean bindServiceAsUser(
+ @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+ @NonNull UserHandle user) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Same as {@link #bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)}, but with an
+ * explicit non-null Handler to run the ServiceConnection callbacks on.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage(trackingBug = 136728678)
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ Handler handler, UserHandle user) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * For a service previously bound with {@link #bindService} or a related method, change
+ * how the system manages that service's process in relation to other processes. This
+ * doesn't modify the original bind flags that were passed in when binding, but adjusts
+ * how the process will be managed in some cases based on those flags. Currently only
+ * works on isolated processes (will be ignored for non-isolated processes).
+ *
+ * <p>Note that this call does not take immediate effect, but will be applied the next
+ * time the impacted process is adjusted for some other reason. Typically you would
+ * call this before then calling a new {@link #bindIsolatedService} on the service
+ * of interest, with that binding causing the process to be shuffled accordingly.</p>
+ *
+ * @param conn The connection interface previously supplied to bindService(). This
+ * parameter must not be null.
+ * @param group A group to put this connection's process in. Upon calling here, this
+ * will override any previous group that was set for that process. The group
+ * tells the system about processes that are logically grouped together, so
+ * should be managed as one unit of importance (such as when being considered
+ * a recently used app). All processes in the same app with the same group
+ * are considered to be related. Supplying 0 reverts to the default behavior
+ * of not grouping.
+ * @param importance Additional importance of the processes within a group. Upon calling
+ * here, this will override any previous importance that was set for that
+ * process. The most important process is 0, and higher values are
+ * successively less important. You can view this as describing how
+ * to order the processes in an array, with the processes at the end of
+ * the array being the least important. This value has no meaning besides
+ * indicating how processes should be ordered in that array one after the
+ * other. This provides a way to fine-tune the system's process killing,
+ * guiding it to kill processes at the end of the array first.
+ *
+ * @see #bindIsolatedService
+ */
+ public void updateServiceGroup(@NonNull ServiceConnection conn, int group,
+ int importance) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Disconnect from an application service. You will no longer receive
+ * calls as the service is restarted, and the service is now allowed to
+ * stop at any time.
+ *
+ * @param conn The connection interface previously supplied to
+ * bindService(). This parameter must not be null.
+ *
+ * @see #bindService
+ */
+ public abstract void unbindService(@NonNull ServiceConnection conn);
+
+ /**
+ * Start executing an {@link android.app.Instrumentation} class. The given
+ * Instrumentation component will be run by killing its target application
+ * (if currently running), starting the target process, instantiating the
+ * instrumentation component, and then letting it drive the application.
+ *
+ * <p>This function is not synchronous -- it returns as soon as the
+ * instrumentation has started and while it is running.
+ *
+ * <p>Instrumentation is normally only allowed to run against a package
+ * that is either unsigned or signed with a signature that the
+ * the instrumentation package is also signed with (ensuring the target
+ * trusts the instrumentation).
+ *
+ * @param className Name of the Instrumentation component to be run.
+ * @param profileFile Optional path to write profiling data as the
+ * instrumentation runs, or null for no profiling.
+ * @param arguments Additional optional arguments to pass to the
+ * instrumentation, or null.
+ *
+ * @return {@code true} if the instrumentation was successfully started,
+ * else {@code false} if it could not be found.
+ */
+ public abstract boolean startInstrumentation(@NonNull ComponentName className,
+ @Nullable String profileFile, @Nullable Bundle arguments);
+
+ /** @hide */
+ @StringDef(suffix = { "_SERVICE" }, value = {
+ POWER_SERVICE,
+ //@hide: POWER_STATS_SERVICE,
+ WINDOW_SERVICE,
+ LAYOUT_INFLATER_SERVICE,
+ ACCOUNT_SERVICE,
+ ACTIVITY_SERVICE,
+ ALARM_SERVICE,
+ NOTIFICATION_SERVICE,
+ ACCESSIBILITY_SERVICE,
+ CAPTIONING_SERVICE,
+ KEYGUARD_SERVICE,
+ LOCATION_SERVICE,
+ //@hide: COUNTRY_DETECTOR,
+ SEARCH_SERVICE,
+ SENSOR_SERVICE,
+ SENSOR_PRIVACY_SERVICE,
+ STORAGE_SERVICE,
+ STORAGE_STATS_SERVICE,
+ WALLPAPER_SERVICE,
+ TIME_ZONE_RULES_MANAGER_SERVICE,
+ VIBRATOR_MANAGER_SERVICE,
+ VIBRATOR_SERVICE,
+ //@hide: STATUS_BAR_SERVICE,
+ CONNECTIVITY_SERVICE,
+ PAC_PROXY_SERVICE,
+ VCN_MANAGEMENT_SERVICE,
+ //@hide: IP_MEMORY_STORE_SERVICE,
+ IPSEC_SERVICE,
+ VPN_MANAGEMENT_SERVICE,
+ TEST_NETWORK_SERVICE,
+ //@hide: UPDATE_LOCK_SERVICE,
+ //@hide: NETWORKMANAGEMENT_SERVICE,
+ NETWORK_STATS_SERVICE,
+ //@hide: NETWORK_POLICY_SERVICE,
+ WIFI_SERVICE,
+ WIFI_AWARE_SERVICE,
+ WIFI_P2P_SERVICE,
+ WIFI_SCANNING_SERVICE,
+ //@hide: LOWPAN_SERVICE,
+ //@hide: WIFI_RTT_SERVICE,
+ //@hide: ETHERNET_SERVICE,
+ WIFI_RTT_RANGING_SERVICE,
+ NSD_SERVICE,
+ AUDIO_SERVICE,
+ AUTH_SERVICE,
+ FINGERPRINT_SERVICE,
+ //@hide: FACE_SERVICE,
+ BIOMETRIC_SERVICE,
+ MEDIA_ROUTER_SERVICE,
+ TELEPHONY_SERVICE,
+ TELEPHONY_SUBSCRIPTION_SERVICE,
+ CARRIER_CONFIG_SERVICE,
+ EUICC_SERVICE,
+ //@hide: MMS_SERVICE,
+ TELECOM_SERVICE,
+ CLIPBOARD_SERVICE,
+ INPUT_METHOD_SERVICE,
+ TEXT_SERVICES_MANAGER_SERVICE,
+ TEXT_CLASSIFICATION_SERVICE,
+ APPWIDGET_SERVICE,
+ //@hide: VOICE_INTERACTION_MANAGER_SERVICE,
+ //@hide: BACKUP_SERVICE,
+ REBOOT_READINESS_SERVICE,
+ ROLLBACK_SERVICE,
+ DROPBOX_SERVICE,
+ //@hide: DEVICE_IDLE_CONTROLLER,
+ //@hide: POWER_WHITELIST_MANAGER,
+ DEVICE_POLICY_SERVICE,
+ UI_MODE_SERVICE,
+ DOWNLOAD_SERVICE,
+ NFC_SERVICE,
+ BLUETOOTH_SERVICE,
+ //@hide: SIP_SERVICE,
+ USB_SERVICE,
+ LAUNCHER_APPS_SERVICE,
+ //@hide: SERIAL_SERVICE,
+ //@hide: HDMI_CONTROL_SERVICE,
+ INPUT_SERVICE,
+ DISPLAY_SERVICE,
+ //@hide COLOR_DISPLAY_SERVICE,
+ USER_SERVICE,
+ RESTRICTIONS_SERVICE,
+ APP_OPS_SERVICE,
+ ROLE_SERVICE,
+ //@hide ROLE_CONTROLLER_SERVICE,
+ CAMERA_SERVICE,
+ //@hide: PLATFORM_COMPAT_SERVICE,
+ //@hide: PLATFORM_COMPAT_NATIVE_SERVICE,
+ PRINT_SERVICE,
+ CONSUMER_IR_SERVICE,
+ //@hide: TRUST_SERVICE,
+ TV_INPUT_SERVICE,
+ //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
+ //@hide: NETWORK_SCORE_SERVICE,
+ USAGE_STATS_SERVICE,
+ MEDIA_SESSION_SERVICE,
+ MEDIA_COMMUNICATION_SERVICE,
+ BATTERY_SERVICE,
+ JOB_SCHEDULER_SERVICE,
+ //@hide: PERSISTENT_DATA_BLOCK_SERVICE,
+ //@hide: OEM_LOCK_SERVICE,
+ MEDIA_PROJECTION_SERVICE,
+ MIDI_SERVICE,
+ RADIO_SERVICE,
+ HARDWARE_PROPERTIES_SERVICE,
+ //@hide: SOUND_TRIGGER_SERVICE,
+ SHORTCUT_SERVICE,
+ //@hide: CONTEXTHUB_SERVICE,
+ SYSTEM_HEALTH_SERVICE,
+ //@hide: INCIDENT_SERVICE,
+ //@hide: INCIDENT_COMPANION_SERVICE,
+ //@hide: STATS_COMPANION_SERVICE,
+ COMPANION_DEVICE_SERVICE,
+ CROSS_PROFILE_APPS_SERVICE,
+ //@hide: SYSTEM_UPDATE_SERVICE,
+ //@hide: TIME_DETECTOR_SERVICE,
+ //@hide: TIME_ZONE_DETECTOR_SERVICE,
+ PERMISSION_SERVICE,
+ LIGHTS_SERVICE,
+ //@hide: PEOPLE_SERVICE,
+ //@hide: DEVICE_STATE_SERVICE,
+ //@hide: SPEECH_RECOGNITION_SERVICE,
+ UWB_SERVICE,
+ MEDIA_METRICS_SERVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ServiceName {}
+
+ /**
+ * Return the handle to a system-level service by name. The class of the
+ * returned object varies by the requested name. Currently available names
+ * are:
+ *
+ * <dl>
+ * <dt> {@link #WINDOW_SERVICE} ("window")
+ * <dd> The top-level window manager in which you can place custom
+ * windows. The returned object is a {@link android.view.WindowManager}. Must only be obtained
+ * from a visual context such as Activity or a Context created with
+ * {@link #createWindowContext(int, Bundle)}, which are adjusted to the configuration and
+ * visual bounds of an area on screen.
+ * <dt> {@link #LAYOUT_INFLATER_SERVICE} ("layout_inflater")
+ * <dd> A {@link android.view.LayoutInflater} for inflating layout resources
+ * in this context. Must only be obtained from a visual context such as Activity or a Context
+ * created with {@link #createWindowContext(int, Bundle)}, which are adjusted to the
+ * configuration and visual bounds of an area on screen.
+ * <dt> {@link #ACTIVITY_SERVICE} ("activity")
+ * <dd> A {@link android.app.ActivityManager} for interacting with the
+ * global activity state of the system.
+ * <dt> {@link #WALLPAPER_SERVICE} ("wallpaper")
+ * <dd> A {@link android.service.wallpaper.WallpaperService} for accessing wallpapers in this
+ * context. Must only be obtained from a visual context such as Activity or a Context created
+ * with {@link #createWindowContext(int, Bundle)}, which are adjusted to the configuration and
+ * visual bounds of an area on screen.
+ * <dt> {@link #POWER_SERVICE} ("power")
+ * <dd> A {@link android.os.PowerManager} for controlling power
+ * management.
+ * <dt> {@link #ALARM_SERVICE} ("alarm")
+ * <dd> A {@link android.app.AlarmManager} for receiving intents at the
+ * time of your choosing.
+ * <dt> {@link #NOTIFICATION_SERVICE} ("notification")
+ * <dd> A {@link android.app.NotificationManager} for informing the user
+ * of background events.
+ * <dt> {@link #KEYGUARD_SERVICE} ("keyguard")
+ * <dd> A {@link android.app.KeyguardManager} for controlling keyguard.
+ * <dt> {@link #LOCATION_SERVICE} ("location")
+ * <dd> A {@link android.location.LocationManager} for controlling location
+ * (e.g., GPS) updates.
+ * <dt> {@link #SEARCH_SERVICE} ("search")
+ * <dd> A {@link android.app.SearchManager} for handling search.
+ * <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager")
+ * <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting
+ * with individual ones and playing synchronized effects on multiple vibrators.
+ * <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
+ * <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware.
+ * <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity")
+ * <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
+ * handling management of network connections.
+ * <dt> {@link #IPSEC_SERVICE} ("ipsec")
+ * <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
+ * sockets and networks.
+ * <dt> {@link #WIFI_SERVICE} ("wifi")
+ * <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
+ * connectivity. On releases before NYC, it should only be obtained from an application
+ * context, and not from any other derived context to avoid memory leaks within the calling
+ * process.
+ * <dt> {@link #WIFI_AWARE_SERVICE} ("wifiaware")
+ * <dd> A {@link android.net.wifi.aware.WifiAwareManager WifiAwareManager} for management of
+ * Wi-Fi Aware discovery and connectivity.
+ * <dt> {@link #WIFI_P2P_SERVICE} ("wifip2p")
+ * <dd> A {@link android.net.wifi.p2p.WifiP2pManager WifiP2pManager} for management of
+ * Wi-Fi Direct connectivity.
+ * <dt> {@link #INPUT_METHOD_SERVICE} ("input_method")
+ * <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager}
+ * for management of input methods.
+ * <dt> {@link #UI_MODE_SERVICE} ("uimode")
+ * <dd> An {@link android.app.UiModeManager} for controlling UI modes.
+ * <dt> {@link #DOWNLOAD_SERVICE} ("download")
+ * <dd> A {@link android.app.DownloadManager} for requesting HTTP downloads
+ * <dt> {@link #BATTERY_SERVICE} ("batterymanager")
+ * <dd> A {@link android.os.BatteryManager} for managing battery state
+ * <dt> {@link #JOB_SCHEDULER_SERVICE} ("taskmanager")
+ * <dd> A {@link android.app.job.JobScheduler} for managing scheduled tasks
+ * <dt> {@link #NETWORK_STATS_SERVICE} ("netstats")
+ * <dd> A {@link android.app.usage.NetworkStatsManager NetworkStatsManager} for querying network
+ * usage statistics.
+ * <dt> {@link #HARDWARE_PROPERTIES_SERVICE} ("hardware_properties")
+ * <dd> A {@link android.os.HardwarePropertiesManager} for accessing hardware properties.
+ * <dt> {@link #DOMAIN_VERIFICATION_SERVICE} ("domain_verification")
+ * <dd> A {@link android.content.pm.verify.domain.DomainVerificationManager} for accessing
+ * web domain approval state.
+ * </dl>
+ *
+ * <p>Note: System services obtained via this API may be closely associated with
+ * the Context in which they are obtained from. In general, do not share the
+ * service objects between various different contexts (Activities, Applications,
+ * Services, Providers, etc.)
+ *
+ * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+ * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+ * {@link #FINGERPRINT_SERVICE}, {@link #KEYGUARD_SERVICE}, {@link #SHORTCUT_SERVICE},
+ * {@link #USB_SERVICE}, {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE},
+ * {@link #WIFI_SERVICE}, {@link #WIFI_AWARE_SERVICE}. For these services this method will
+ * return <code>null</code>. Generally, if you are running as an instant app you should always
+ * check whether the result of this method is {@code null}.
+ *
+ * <p>Note: When implementing this method, keep in mind that new services can be added on newer
+ * Android releases, so if you're looking for just the explicit names mentioned above, make sure
+ * to return {@code null} when you don't recognize the name — if you throw a
+ * {@link RuntimeException} exception instead, you're app might break on new Android releases.
+ *
+ * @param name The name of the desired service.
+ *
+ * @return The service or {@code null} if the name does not exist.
+ *
+ * @see #WINDOW_SERVICE
+ * @see android.view.WindowManager
+ * @see #LAYOUT_INFLATER_SERVICE
+ * @see android.view.LayoutInflater
+ * @see #ACTIVITY_SERVICE
+ * @see android.app.ActivityManager
+ * @see #POWER_SERVICE
+ * @see android.os.PowerManager
+ * @see #ALARM_SERVICE
+ * @see android.app.AlarmManager
+ * @see #NOTIFICATION_SERVICE
+ * @see android.app.NotificationManager
+ * @see #KEYGUARD_SERVICE
+ * @see android.app.KeyguardManager
+ * @see #LOCATION_SERVICE
+ * @see android.location.LocationManager
+ * @see #SEARCH_SERVICE
+ * @see android.app.SearchManager
+ * @see #SENSOR_SERVICE
+ * @see android.hardware.SensorManager
+ * @see #STORAGE_SERVICE
+ * @see android.os.storage.StorageManager
+ * @see #VIBRATOR_MANAGER_SERVICE
+ * @see android.os.VibratorManager
+ * @see #VIBRATOR_SERVICE
+ * @see android.os.Vibrator
+ * @see #CONNECTIVITY_SERVICE
+ * @see android.net.ConnectivityManager
+ * @see #WIFI_SERVICE
+ * @see android.net.wifi.WifiManager
+ * @see #AUDIO_SERVICE
+ * @see android.media.AudioManager
+ * @see #MEDIA_ROUTER_SERVICE
+ * @see android.media.MediaRouter
+ * @see #TELEPHONY_SERVICE
+ * @see android.telephony.TelephonyManager
+ * @see #TELEPHONY_SUBSCRIPTION_SERVICE
+ * @see android.telephony.SubscriptionManager
+ * @see #CARRIER_CONFIG_SERVICE
+ * @see android.telephony.CarrierConfigManager
+ * @see #EUICC_SERVICE
+ * @see android.telephony.euicc.EuiccManager
+ * @see android.telephony.MmsManager
+ * @see #INPUT_METHOD_SERVICE
+ * @see android.view.inputmethod.InputMethodManager
+ * @see #UI_MODE_SERVICE
+ * @see android.app.UiModeManager
+ * @see #DOWNLOAD_SERVICE
+ * @see android.app.DownloadManager
+ * @see #BATTERY_SERVICE
+ * @see android.os.BatteryManager
+ * @see #JOB_SCHEDULER_SERVICE
+ * @see android.app.job.JobScheduler
+ * @see #NETWORK_STATS_SERVICE
+ * @see android.app.usage.NetworkStatsManager
+ * @see android.os.HardwarePropertiesManager
+ * @see #HARDWARE_PROPERTIES_SERVICE
+ * @see #DOMAIN_VERIFICATION_SERVICE
+ * @see android.content.pm.verify.domain.DomainVerificationManager
+ */
+ public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
+
+ /**
+ * Return the handle to a system-level service by class.
+ * <p>
+ * Currently available classes are:
+ * {@link android.view.WindowManager}, {@link android.view.LayoutInflater},
+ * {@link android.app.ActivityManager}, {@link android.os.PowerManager},
+ * {@link android.app.AlarmManager}, {@link android.app.NotificationManager},
+ * {@link android.app.KeyguardManager}, {@link android.location.LocationManager},
+ * {@link android.app.SearchManager}, {@link android.os.Vibrator},
+ * {@link android.net.ConnectivityManager},
+ * {@link android.net.wifi.WifiManager},
+ * {@link android.media.AudioManager}, {@link android.media.MediaRouter},
+ * {@link android.telephony.TelephonyManager}, {@link android.telephony.SubscriptionManager},
+ * {@link android.view.inputmethod.InputMethodManager},
+ * {@link android.app.UiModeManager}, {@link android.app.DownloadManager},
+ * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler},
+ * {@link android.app.usage.NetworkStatsManager},
+ * {@link android.content.pm.verify.domain.DomainVerificationManager}.
+ * </p>
+ *
+ * <p>
+ * Note: System services obtained via this API may be closely associated with
+ * the Context in which they are obtained from. In general, do not share the
+ * service objects between various different contexts (Activities, Applications,
+ * Services, Providers, etc.)
+ * </p>
+ *
+ * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+ * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+ * {@link #FINGERPRINT_SERVICE}, {@link #KEYGUARD_SERVICE}, {@link #SHORTCUT_SERVICE},
+ * {@link #USB_SERVICE}, {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE},
+ * {@link #WIFI_SERVICE}, {@link #WIFI_AWARE_SERVICE}. For these services this method will
+ * return {@code null}. Generally, if you are running as an instant app you should always
+ * check whether the result of this method is {@code null}.
+ * </p>
+ *
+ * @param serviceClass The class of the desired service.
+ * @return The service or {@code null} if the class is not a supported system service. Note:
+ * <b>never</b> throw a {@link RuntimeException} if the name is not supported.
+ */
+ @SuppressWarnings("unchecked")
+ public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) {
+ // Because subclasses may override getSystemService(String) we cannot
+ // perform a lookup by class alone. We must first map the class to its
+ // service name then invoke the string-based method.
+ String serviceName = getSystemServiceName(serviceClass);
+ return serviceName != null ? (T)getSystemService(serviceName) : null;
+ }
+
+ /**
+ * Gets the name of the system-level service that is represented by the specified class.
+ *
+ * @param serviceClass The class of the desired service.
+ * @return The service name or null if the class is not a supported system service.
+ */
+ public abstract @Nullable String getSystemServiceName(@NonNull Class<?> serviceClass);
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.PowerManager} for controlling power management,
+ * including "wake locks," which let you keep the device on while
+ * you're running long tasks.
+ */
+ public static final String POWER_SERVICE = "power";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.PowerStatsService} for accessing power stats
+ * service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String POWER_STATS_SERVICE = "powerstats";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.RecoverySystem} for accessing the recovery system
+ * service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String RECOVERY_SERVICE = "recovery";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.SystemUpdateManager} for accessing the system update
+ * manager service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String SYSTEM_UPDATE_SERVICE = "system_update";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.view.WindowManager} for accessing the system's window
+ * manager.
+ *
+ * @see #getSystemService(String)
+ * @see android.view.WindowManager
+ */
+ @UiContext
+ public static final String WINDOW_SERVICE = "window";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.view.LayoutInflater} for inflating layout resources in this
+ * context.
+ *
+ * @see #getSystemService(String)
+ * @see android.view.LayoutInflater
+ */
+ @UiContext
+ public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.accounts.AccountManager} for receiving intents at a
+ * time of your choosing.
+ *
+ * @see #getSystemService(String)
+ * @see android.accounts.AccountManager
+ */
+ public static final String ACCOUNT_SERVICE = "account";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.ActivityManager} for interacting with the global
+ * system state.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.ActivityManager
+ */
+ public static final String ACTIVITY_SERVICE = "activity";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.ActivityTaskManager} for interacting with the global system state.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.ActivityTaskManager
+ * @hide
+ */
+ public static final String ACTIVITY_TASK_SERVICE = "activity_task";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.UriGrantsManager} for interacting with the global system state.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.UriGrantsManager
+ * @hide
+ */
+ public static final String URI_GRANTS_SERVICE = "uri_grants";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.AlarmManager} for receiving intents at a
+ * time of your choosing.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.AlarmManager
+ */
+ public static final String ALARM_SERVICE = "alarm";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.NotificationManager} for informing the user of
+ * background events.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.NotificationManager
+ */
+ public static final String NOTIFICATION_SERVICE = "notification";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.view.accessibility.AccessibilityManager} for giving the user
+ * feedback for UI events through the registered event listeners.
+ *
+ * @see #getSystemService(String)
+ * @see android.view.accessibility.AccessibilityManager
+ */
+ public static final String ACCESSIBILITY_SERVICE = "accessibility";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.view.accessibility.CaptioningManager} for obtaining
+ * captioning properties and listening for changes in captioning
+ * preferences.
+ *
+ * @see #getSystemService(String)
+ * @see android.view.accessibility.CaptioningManager
+ */
+ public static final String CAPTIONING_SERVICE = "captioning";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.KeyguardManager} for controlling keyguard.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.KeyguardManager
+ */
+ public static final String KEYGUARD_SERVICE = "keyguard";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.location.LocationManager} for controlling location
+ * updates.
+ *
+ * @see #getSystemService(String)
+ * @see android.location.LocationManager
+ */
+ public static final String LOCATION_SERVICE = "location";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.location.CountryDetector} for detecting the country that
+ * the user is in.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static final String COUNTRY_DETECTOR = "country_detector";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.SearchManager} for handling searches.
+ *
+ * <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";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.hardware.SensorManager} for accessing sensors.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.SensorManager
+ */
+ public static final String SENSOR_SERVICE = "sensor";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.hardware.SensorPrivacyManager} for accessing sensor privacy
+ * functions.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.SensorPrivacyManager
+ *
+ * @hide
+ */
+ public static final String SENSOR_PRIVACY_SERVICE = "sensor_privacy";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.os.storage.StorageManager} for accessing system storage
+ * functions.
+ *
+ * @see #getSystemService(String)
+ * @see android.os.storage.StorageManager
+ */
+ public static final String STORAGE_SERVICE = "storage";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.usage.StorageStatsManager} for accessing system storage
+ * statistics.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.usage.StorageStatsManager
+ */
+ public static final String STORAGE_STATS_SERVICE = "storagestats";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * com.android.server.WallpaperService for accessing wallpapers.
+ *
+ * @see #getSystemService(String)
+ */
+ @UiContext
+ public static final String WALLPAPER_SERVICE = "wallpaper";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager}
+ * for accessing the device vibrators, interacting with individual ones and playing synchronized
+ * effects on multiple vibrators.
+ *
+ * @see #getSystemService(String)
+ * @see android.os.VibratorManager
+ */
+ @SuppressLint("ServiceName")
+ public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for
+ * interacting with the vibration hardware.
+ *
+ * @deprecated Use {@link android.os.VibratorManager} to retrieve the default system vibrator.
+ * @see #getSystemService(String)
+ * @see android.os.Vibrator
+ */
+ @Deprecated
+ public static final String VIBRATOR_SERVICE = "vibrator";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.StatusBarManager} for interacting with the status bar.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.StatusBarManager
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String STATUS_BAR_SERVICE = "statusbar";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.ConnectivityManager} for handling management of
+ * network connections.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.ConnectivityManager
+ */
+ public static final String CONNECTIVITY_SERVICE = "connectivity";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.PacProxyManager} for handling management of
+ * pac proxy information.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.PacProxyManager
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String PAC_PROXY_SERVICE = "pac_proxy";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.vcn.VcnManager}
+ * for managing Virtual Carrier Networks
+ *
+ * @see #getSystemService(String)
+ * @see android.net.vcn.VcnManager
+ * @hide
+ */
+ public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.INetd} for communicating with the network stack
+ * @hide
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String NETD_SERVICE = "netd";
+
+ /**
+ * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+ * {@link INetworkStackConnector} IBinder for communicating with the network stack
+ * @hide
+ * @see NetworkStackClient
+ */
+ public static final String NETWORK_STACK_SERVICE = "network_stack";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.TetheringManager}
+ * for managing tethering functions.
+ * @hide
+ * @see android.net.TetheringManager
+ */
+ @SystemApi
+ public static final String TETHERING_SERVICE = "tethering";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
+ * IPSec.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String IPSEC_SERVICE = "ipsec";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.VpnManager} to
+ * manage profiles for the platform built-in VPN.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.ConnectivityDiagnosticsManager} for performing network connectivity diagnostics
+ * as well as receiving network connectivity information from the system.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.ConnectivityDiagnosticsManager
+ */
+ public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.TestNetworkManager} for building TUNs and limited-use Networks
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String TEST_NETWORK_SERVICE = "test_network";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.os.IUpdateLock} for managing runtime sequences that
+ * must not be interrupted by headless OTA application or similar.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ * @see android.os.UpdateLock
+ */
+ public static final String UPDATE_LOCK_SERVICE = "updatelock";
+
+ /**
+ * Constant for the internal network management service, not really a Context service.
+ * @hide
+ */
+ public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link com.android.server.slice.SliceManagerService} for managing slices.
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SLICE_SERVICE = "slice";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.usage.NetworkStatsManager} for querying network usage stats.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.usage.NetworkStatsManager
+ */
+ public static final String NETWORK_STATS_SERVICE = "netstats";
+ /** {@hide} */
+ public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+ /** {@hide} */
+ public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.WifiManager} for handling management of
+ * Wi-Fi access.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.WifiManager
+ */
+ public static final String WIFI_SERVICE = "wifi";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.wifi.wificond.WifiNl80211Manager} for handling management of the
+ * Wi-Fi nl802.11 daemon (wificond).
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.wificond.WifiNl80211Manager
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String WIFI_NL80211_SERVICE = "wifinl80211";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.p2p.WifiP2pManager} for handling management of
+ * Wi-Fi peer-to-peer connections.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.p2p.WifiP2pManager
+ */
+ public static final String WIFI_P2P_SERVICE = "wifip2p";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.wifi.aware.WifiAwareManager} for handling management of
+ * Wi-Fi Aware.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.aware.WifiAwareManager
+ */
+ public static final String WIFI_AWARE_SERVICE = "wifiaware";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.WifiScanner} for scanning the wifi universe
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.WifiScanner
+ * @hide
+ */
+ @SystemApi
+ public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.RttManager} for ranging devices with wifi
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.RttManager
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ public static final String WIFI_RTT_SERVICE = "rttmanager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.rtt.WifiRttManager
+ */
+ public static final String WIFI_RTT_RANGING_SERVICE = "wifirtt";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.lowpan.LowpanManager} for handling management of
+ * LoWPAN access.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.lowpan.LowpanManager
+ *
+ * @hide
+ */
+ public static final String LOWPAN_SERVICE = "lowpan";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.EthernetManager}
+ * for handling management of Ethernet access.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.EthernetManager
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ETHERNET_SERVICE = "ethernet";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.nsd.NsdManager} for handling management of network service
+ * discovery
+ *
+ * @see #getSystemService(String)
+ * @see android.net.nsd.NsdManager
+ */
+ public static final String NSD_SERVICE = "servicediscovery";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.AudioManager} for handling management of volume,
+ * ringer modes and audio routing.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.AudioManager
+ */
+ public static final String AUDIO_SERVICE = "audio";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.media.MediaTranscodingManager} for transcoding media.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ * @see android.media.MediaTranscodingManager
+ */
+ @SystemApi
+ public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
+
+ /**
+ * AuthService orchestrates biometric and PIN/pattern/password authentication.
+ *
+ * BiometricService was split into two services, AuthService and BiometricService, where
+ * AuthService is the high level service that orchestrates all types of authentication, and
+ * BiometricService is a lower layer responsible only for biometric authentication.
+ *
+ * Ideally we should have renamed BiometricManager to AuthManager, because it logically
+ * corresponds to AuthService. However, because BiometricManager is a public API, we kept
+ * the old name but changed the internal implementation to use AuthService.
+ *
+ * As of now, the AUTH_SERVICE constant is only used to identify the service in
+ * SystemServiceRegistry and SELinux. To obtain the manager for AUTH_SERVICE, one should use
+ * BIOMETRIC_SERVICE with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.biometrics.BiometricManager}
+ *
+ * Map of the two services and their managers:
+ * [Service] [Manager]
+ * AuthService BiometricManager
+ * BiometricService N/A
+ *
+ * @hide
+ */
+ public static final String AUTH_SERVICE = "auth";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.fingerprint.FingerprintManager} for handling management
+ * of fingerprints.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.fingerprint.FingerprintManager
+ */
+ public static final String FINGERPRINT_SERVICE = "fingerprint";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.face.FaceManager} for handling management
+ * of face authentication.
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.hardware.face.FaceManager
+ */
+ public static final String FACE_SERVICE = "face";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.iris.IrisManager} for handling management
+ * of iris authentication.
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.hardware.iris.IrisManager
+ */
+ public static final String IRIS_SERVICE = "iris";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.biometrics.BiometricManager} for handling
+ * biometric and PIN/pattern/password authentication.
+ *
+ * @see #getSystemService
+ * @see android.hardware.biometrics.BiometricManager
+ */
+ public static final String BIOMETRIC_SERVICE = "biometric";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.MediaCommunicationManager}
+ * for managing {@link android.media.MediaSession2}.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.MediaCommunicationManager
+ */
+ public static final String MEDIA_COMMUNICATION_SERVICE = "media_communication";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.MediaRouter} for controlling and managing
+ * routing of media.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.MediaRouter
+ */
+ public static final String MEDIA_ROUTER_SERVICE = "media_router";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.session.MediaSessionManager} for managing media Sessions.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.session.MediaSessionManager
+ */
+ public static final String MEDIA_SESSION_SERVICE = "media_session";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.TelephonyManager} for handling management the
+ * telephony features of the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.TelephonyManager
+ */
+ public static final String TELEPHONY_SERVICE = "phone";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.SubscriptionManager} for handling management the
+ * telephony subscriptions of the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.SubscriptionManager
+ */
+ public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telecom.TelecomManager} to manage telecom-related features
+ * of the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.telecom.TelecomManager
+ */
+ public static final String TELECOM_SERVICE = "telecom";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.CarrierConfigManager} for reading carrier configuration values.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.CarrierConfigManager
+ */
+ public static final String CARRIER_CONFIG_SERVICE = "carrier_config";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.euicc.EuiccManager} to manage the device eUICC (embedded SIM).
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.euicc.EuiccManager
+ */
+ public static final String EUICC_SERVICE = "euicc";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.euicc.EuiccCardManager} to access the device eUICC (embedded SIM).
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.euicc.EuiccCardManager
+ * @hide
+ */
+ @SystemApi
+ public static final String EUICC_CARD_SERVICE = "euicc_card";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.MmsManager} to send/receive MMS messages.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.MmsManager
+ * @hide
+ */
+ public static final String MMS_SERVICE = "mms";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.content.ClipboardManager} for accessing and modifying
+ * the contents of the global clipboard.
+ *
+ * @see #getSystemService(String)
+ * @see android.content.ClipboardManager
+ */
+ public static final String CLIPBOARD_SERVICE = "clipboard";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link TextClassificationManager} for text classification services.
+ *
+ * @see #getSystemService(String)
+ * @see TextClassificationManager
+ */
+ public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.graphics.fonts.FontManager} for font services.
+ *
+ * @see #getSystemService(String)
+ * @see android.graphics.fonts.FontManager
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String FONT_SERVICE = "font";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link com.android.server.attention.AttentionManagerService} for attention services.
+ *
+ * @see #getSystemService(String)
+ * @see android.server.attention.AttentionManagerService
+ * @hide
+ */
+ public static final String ATTENTION_SERVICE = "attention";
+
+ /**
+ * Official published name of the (internal) rotation resolver service.
+ *
+ * // TODO(b/178151184): change it back to rotation resolver before S release.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String ROTATION_RESOLVER_SERVICE = "resolver";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.view.inputmethod.InputMethodManager} for accessing input
+ * methods.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String INPUT_METHOD_SERVICE = "input_method";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.view.textservice.TextServicesManager} for accessing
+ * text services.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String APPWIDGET_SERVICE = "appwidget";
+
+ /**
+ * Official published name of the (internal) voice interaction manager service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
+
+ /**
+ * Official published name of the (internal) autofill service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
+
+ /**
+ * Official published name of the (internal) text to speech manager service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
+
+ /**
+ * Official published name of the content capture service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @TestApi
+ @SuppressLint("ServiceName") // TODO: This should be renamed to CONTENT_CAPTURE_SERVICE
+ public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
+
+ /**
+ * Official published name of the translation service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String TRANSLATION_MANAGER_SERVICE = "translation";
+
+ /**
+ * Official published name of the translation service which supports ui translation function.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String UI_TRANSLATION_SERVICE = "ui_translation";
+
+ /**
+ * Used for getting content selections and classifications for task snapshots.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
+
+ /**
+ * Official published name of the app prediction service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(APP_PREDICTION_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String APP_PREDICTION_SERVICE = "app_prediction";
+
+ /**
+ * Official published name of the search ui service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(SEARCH_UI_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String SEARCH_UI_SERVICE = "search_ui";
+
+ /**
+ * Used for getting the smartspace service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(SMARTSPACE_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String SMARTSPACE_SERVICE = "smartspace";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access the
+ * {@link com.android.server.voiceinteraction.SoundTriggerService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access the
+ * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
+
+ /**
+ * Used to access {@link MusicRecognitionManagerService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String MUSIC_RECOGNITION_SERVICE = "music_recognition";
+
+ /**
+ * Official published name of the (internal) permission service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String PERMISSION_SERVICE = "permission";
+
+ /**
+ * Official published name of the legacy (internal) permission service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ //@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String LEGACY_PERMISSION_SERVICE = "legacy_permission";
+
+ /**
+ * Official published name of the (internal) permission controller service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
+
+ /**
+ * Official published name of the (internal) permission checker service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
+
+ /**
+ * Use with {@link #getSystemService(String) to retrieve an
+ * {@link android.apphibernation.AppHibernationManager}} for
+ * communicating with the hibernation service.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.backup.IBackupManager IBackupManager} for communicating
+ * with the backup mechanism.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String BACKUP_SERVICE = "backup";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.content.rollback.RollbackManager} for communicating
+ * with the rollback manager
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String ROLLBACK_SERVICE = "rollback";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.scheduling.RebootReadinessManager} for communicating
+ * with the reboot readiness detector.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.DropBoxManager} instance for recording
+ * diagnostic logs.
+ * @see #getSystemService(String)
+ */
+ public static final String DROPBOX_SERVICE = "dropbox";
+
+ /**
+ * System service name for the DeviceIdleManager.
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("ServiceName") // TODO: This should be renamed to DEVICE_IDLE_SERVICE
+ public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
+
+ /**
+ * System service name for the PowerWhitelistManager.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @TestApi
+ @Deprecated
+ @SuppressLint("ServiceName")
+ public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
+
+ /**
+ * System service name for the PowerExemptionManager.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @TestApi
+ public static final String POWER_EXEMPTION_SERVICE = "power_exemption";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.admin.DevicePolicyManager} for working with global
+ * device policy management.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String DEVICE_POLICY_SERVICE = "device_policy";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.UiModeManager} for controlling UI modes.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String UI_MODE_SERVICE = "uimode";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.DownloadManager} for requesting HTTP downloads.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String DOWNLOAD_SERVICE = "download";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.BatteryManager} for managing battery state.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String BATTERY_SERVICE = "batterymanager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.nfc.NfcManager} for using NFC.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String NFC_SERVICE = "nfc";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.bluetooth.BluetoothManager} for using Bluetooth.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String BLUETOOTH_SERVICE = "bluetooth";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.sip.SipManager} for accessing the SIP related service.
+ *
+ * @see #getSystemService(String)
+ */
+ /** @hide */
+ public static final String SIP_SERVICE = "sip";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.hardware.usb.UsbManager} for access to USB devices (as a USB host)
+ * and for controlling this device's behavior as a USB device.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.usb.UsbManager
+ */
+ public static final String USB_SERVICE = "usb";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.debug.AdbManager} for access to ADB debug functions.
+ *
+ * @see #getSystemService(String)
+ * @see android.debug.AdbManager
+ *
+ * @hide
+ */
+ public static final String ADB_SERVICE = "adb";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.hardware.SerialManager} for access to serial ports.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.SerialManager
+ *
+ * @hide
+ */
+ public static final String SERIAL_SERVICE = "serial";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.hdmi.HdmiControlManager} for controlling and managing
+ * HDMI-CEC protocol.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.hdmi.HdmiControlManager
+ * @hide
+ */
+ @SystemApi
+ public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.input.InputManager} for interacting with input devices.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.input.InputManager
+ */
+ public static final String INPUT_SERVICE = "input";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.display.DisplayManager} for interacting with display devices.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.display.DisplayManager
+ */
+ public static final String DISPLAY_SERVICE = "display";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.display.ColorDisplayManager} for controlling color transforms.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.display.ColorDisplayManager
+ * @hide
+ */
+ public static final String COLOR_DISPLAY_SERVICE = "color_display";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.UserManager} for managing users on devices that support multiple users.
+ *
+ * @see #getSystemService(String)
+ * @see android.os.UserManager
+ */
+ public static final String USER_SERVICE = "user";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.content.pm.LauncherApps} for querying and monitoring launchable apps across
+ * profiles of a user.
+ *
+ * @see #getSystemService(String)
+ * @see android.content.pm.LauncherApps
+ */
+ public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.content.RestrictionsManager} for retrieving application restrictions
+ * and requesting permissions for restricted operations.
+ * @see #getSystemService(String)
+ * @see android.content.RestrictionsManager
+ */
+ public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.AppOpsManager} for tracking application operations
+ * on the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.AppOpsManager
+ */
+ public static final String APP_OPS_SERVICE = "appops";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.app.role.RoleManager}
+ * for managing roles.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.role.RoleManager
+ */
+ public static final String ROLE_SERVICE = "role";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.camera2.CameraManager} for interacting with
+ * camera devices.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.camera2.CameraManager
+ */
+ public static final String CAMERA_SERVICE = "camera";
+
+ /**
+ * {@link android.print.PrintManager} for printing and managing
+ * printers and print tasks.
+ *
+ * @see #getSystemService(String)
+ * @see android.print.PrintManager
+ */
+ public static final String PRINT_SERVICE = "print";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.companion.CompanionDeviceManager} for managing companion devices
+ *
+ * @see #getSystemService(String)
+ * @see android.companion.CompanionDeviceManager
+ */
+ public static final String COMPANION_DEVICE_SERVICE = "companiondevice";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.ConsumerIrManager} for transmitting infrared
+ * signals from the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.ConsumerIrManager
+ */
+ public static final String CONSUMER_IR_SERVICE = "consumer_ir";
+
+ /**
+ * {@link android.app.trust.TrustManager} for managing trust agents.
+ * @see #getSystemService(String)
+ * @see android.app.trust.TrustManager
+ * @hide
+ */
+ public static final String TRUST_SERVICE = "trust";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.tv.TvInputManager} for interacting with TV inputs
+ * on the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.tv.TvInputManager
+ */
+ public static final String TV_INPUT_SERVICE = "tv_input";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.tv.TunerResourceManager} for interacting with TV
+ * tuner resources on the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.tv.TunerResourceManager
+ * @hide
+ */
+ public static final String TV_TUNER_RESOURCE_MGR_SERVICE = "tv_tuner_resource_mgr";
+
+ /**
+ * {@link android.net.NetworkScoreManager} for managing network scoring.
+ * @see #getSystemService(String)
+ * @see android.net.NetworkScoreManager
+ * @hide
+ */
+ @SystemApi
+ public static final String NETWORK_SCORE_SERVICE = "network_score";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.usage.UsageStatsManager} for querying device usage stats.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.usage.UsageStatsManager
+ */
+ public static final String USAGE_STATS_SERVICE = "usagestats";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.job.JobScheduler} instance for managing occasional
+ * background tasks.
+ * @see #getSystemService(String)
+ * @see android.app.job.JobScheduler
+ */
+ public static final String JOB_SCHEDULER_SERVICE = "jobscheduler";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.service.persistentdata.PersistentDataBlockManager} instance
+ * for interacting with a storage device that lives across factory resets.
+ *
+ * @see #getSystemService(String)
+ * @see android.service.persistentdata.PersistentDataBlockManager
+ * @hide
+ */
+ @SystemApi
+ public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.service.oemlock.OemLockManager} instance for managing the OEM lock.
+ *
+ * @see #getSystemService(String)
+ * @see android.service.oemlock.OemLockManager
+ * @hide
+ */
+ @SystemApi
+ public static final String OEM_LOCK_SERVICE = "oem_lock";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.media.projection.MediaProjectionManager} instance for managing
+ * media projection sessions.
+ * @see #getSystemService(String)
+ * @see android.media.projection.MediaProjectionManager
+ */
+ public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.midi.MidiManager} for accessing the MIDI service.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String MIDI_SERVICE = "midi";
+
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.radio.RadioManager} for accessing the broadcast radio service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String RADIO_SERVICE = "broadcastradio";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.HardwarePropertiesManager} for accessing the hardware properties service.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.ThermalService} for accessing the thermal service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String THERMAL_SERVICE = "thermalservice";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.PerformanceHintManager} for accessing the performance hinting service.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service.
+ *
+ * @see #getSystemService(String)
+ * @see android.content.pm.ShortcutManager
+ */
+ public static final String SHORTCUT_SERVICE = "shortcut";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.hardware.location.ContextHubManager} for accessing context hubs.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.location.ContextHubManager
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CONTEXTHUB_SERVICE = "contexthub";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.health.SystemHealthManager} for accessing system health (battery, power,
+ * memory, etc) metrics.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String SYSTEM_HEALTH_SERVICE = "systemhealth";
+
+ /**
+ * Gatekeeper Service.
+ * @hide
+ */
+ public static final String GATEKEEPER_SERVICE = "android.service.gatekeeper.IGateKeeperService";
+
+ /**
+ * Service defining the policy for access to device identifiers.
+ * @hide
+ */
+ public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers";
+
+ /**
+ * Service to report a system health "incident"
+ * @hide
+ */
+ public static final String INCIDENT_SERVICE = "incident";
+
+ /**
+ * Service to assist incidentd and dumpstated in reporting status to the user
+ * and in confirming authorization to take an incident report or bugreport
+ * @hide
+ */
+ public static final String INCIDENT_COMPANION_SERVICE = "incidentcompanion";
+
+ /**
+ * Service to assist {@link android.app.StatsManager} that lives in system server.
+ * @hide
+ */
+ public static final String STATS_MANAGER_SERVICE = "statsmanager";
+
+ /**
+ * Service to assist statsd in obtaining general stats.
+ * @hide
+ */
+ public static final String STATS_COMPANION_SERVICE = "statscompanion";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an {@link android.app.StatsManager}.
+ * @hide
+ */
+ @SystemApi
+ public static final String STATS_MANAGER = "stats";
+
+ /**
+ * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+ * {@link IPlatformCompat} IBinder for communicating with the platform compat service.
+ * @hide
+ */
+ public static final String PLATFORM_COMPAT_SERVICE = "platform_compat";
+
+ /**
+ * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+ * {@link IPlatformCompatNative} IBinder for native code communicating with the platform compat
+ * service.
+ * @hide
+ */
+ public static final String PLATFORM_COMPAT_NATIVE_SERVICE = "platform_compat_native";
+
+ /**
+ * Service to capture a bugreport.
+ * @see #getSystemService(String)
+ * @see android.os.BugreportManager
+ */
+ public static final String BUGREPORT_SERVICE = "bugreport";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.content.om.OverlayManager} for managing overlay packages.
+ *
+ * @see #getSystemService(String)
+ * @see android.content.om.OverlayManager
+ * @hide
+ */
+ public static final String OVERLAY_SERVICE = "overlay";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {android.os.IIdmap2} for managing idmap files (used by overlay
+ * packages).
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String IDMAP_SERVICE = "idmap";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link VrManager} for accessing the VR service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String VR_SERVICE = "vrmanager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.timezone.ITimeZoneRulesManager}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.content.pm.CrossProfileApps} for cross profile operations.
+ *
+ * @see #getSystemService(String)
+ */
+ 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";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.timedetector.TimeDetector}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_DETECTOR_SERVICE = "time_detector";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.timezonedetector.TimeZoneDetector}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an {@link TimeManager}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_MANAGER = "time_manager";
+
+ /**
+ * Binder service name for {@link AppBindingService}.
+ * @hide
+ */
+ public static final String APP_BINDING_SERVICE = "app_binding";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.telephony.ims.ImsManager}.
+ */
+ public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.os.SystemConfigManager}.
+ * @hide
+ */
+ @SystemApi
+ public static final String SYSTEM_CONFIG_SERVICE = "system_config";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.telephony.ims.RcsMessageManager}.
+ * @hide
+ */
+ public static final String TELEPHONY_RCS_MESSAGE_SERVICE = "ircsmessage";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.os.image.DynamicSystemManager}.
+ * @hide
+ */
+ public static final String DYNAMIC_SYSTEM_SERVICE = "dynamic_system";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.app.blob.BlobStoreManager} for contributing and accessing data blobs
+ * from the blob store maintained by the system.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.blob.BlobStoreManager
+ */
+ public static final String BLOB_STORE_SERVICE = "blob_store";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link TelephonyRegistryManager}.
+ * @hide
+ */
+ public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.os.BatteryStatsManager}.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String BATTERY_STATS_SERVICE = "batterystats";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.appsearch.AppSearchManager} for
+ * indexing and querying app data managed by the system.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String APP_SEARCH_SERVICE = "app_search";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.content.integrity.AppIntegrityManager}.
+ * @hide
+ */
+ @SystemApi
+ public static final String APP_INTEGRITY_SERVICE = "app_integrity";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.content.pm.DataLoaderManager}.
+ * @hide
+ */
+ public static final String DATA_LOADER_MANAGER_SERVICE = "dataloader_manager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.os.incremental.IncrementalManager}.
+ * @hide
+ */
+ public static final String INCREMENTAL_SERVICE = "incremental";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.security.FileIntegrityManager}.
+ * @see #getSystemService(String)
+ * @see android.security.FileIntegrityManager
+ */
+ public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.lights.LightsManager} for controlling device lights.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String LIGHTS_SERVICE = "lights";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.uwb.UwbManager}.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String UWB_SERVICE = "uwb";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.DreamManager} for controlling Dream states.
+ *
+ * @see #getSystemService(String)
+
+ * @hide
+ */
+ @TestApi
+ public static final String DREAM_SERVICE = "dream";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.SmsManager} for accessing Sms functionality.
+ *
+ * @see #getSystemService(String)
+
+ * @hide
+ */
+ public static final String SMS_SERVICE = "sms";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access a {@link PeopleManager} to interact
+ * with your published conversations.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String PEOPLE_SERVICE = "people";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access device state service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String DEVICE_STATE_SERVICE = "device_state";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.metrics.MediaMetricsManager} for interacting with media metrics
+ * on the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.metrics.MediaMetricsManager
+ */
+ public static final String MEDIA_METRICS_SERVICE = "media_metrics";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access system speech recognition service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String SPEECH_RECOGNITION_SERVICE = "speech_recognition";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link GameManager}.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String GAME_SERVICE = "game";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access
+ * {@link android.content.pm.verify.domain.DomainVerificationManager} to retrieve approval and
+ * user state for declared web domains.
+ *
+ * @see #getSystemService(String)
+ * @see android.content.pm.verify.domain.DomainVerificationManager
+ */
+ public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
+
+ /**
+ * Use with {@link #getSystemService(String)} to access
+ * {@link android.view.displayhash.DisplayHashManager} to handle display hashes.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String DISPLAY_HASH_SERVICE = "display_hash";
+
+ /**
+ * Determine whether the given permission is allowed for a particular
+ * process and user ID running in the system.
+ *
+ * @param permission The name of the permission being checked.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the given
+ * pid/uid is allowed that permission, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkCallingPermission
+ */
+ @CheckResult(suggest="#enforcePermission(String,int,int,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkPermission(@NonNull String permission, int pid, int uid);
+
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @PackageManager.PermissionResult
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public abstract int checkPermission(@NonNull String permission, int pid, int uid,
+ IBinder callerToken);
+
+ /**
+ * Determine whether the calling process of an IPC you are handling has been
+ * granted a particular permission. This is basically the same as calling
+ * {@link #checkPermission(String, int, int)} with the pid and uid returned
+ * by {@link android.os.Binder#getCallingPid} and
+ * {@link android.os.Binder#getCallingUid}. One important difference
+ * is that if you are not currently processing an IPC, this function
+ * will always fail. This is done to protect against accidentally
+ * leaking permissions; you can use {@link #checkCallingOrSelfPermission}
+ * to avoid this protection.
+ *
+ * @param permission The name of the permission being checked.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
+ * pid/uid is allowed that permission, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkPermission
+ * @see #checkCallingOrSelfPermission
+ */
+ @CheckResult(suggest="#enforceCallingPermission(String,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkCallingPermission(@NonNull String permission);
+
+ /**
+ * Determine whether the calling process of an IPC <em>or you</em> have been
+ * granted a particular permission. This is the same as
+ * {@link #checkCallingPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param permission The name of the permission being checked.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
+ * pid/uid is allowed that permission, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkPermission
+ * @see #checkCallingPermission
+ */
+ @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkCallingOrSelfPermission(@NonNull String permission);
+
+ /**
+ * Determine whether <em>you</em> have been granted a particular permission.
+ *
+ * @param permission The name of the permission being checked.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if you have the
+ * permission, or {@link PackageManager#PERMISSION_DENIED} if not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkCallingPermission(String)
+ */
+ @PackageManager.PermissionResult
+ public abstract int checkSelfPermission(@NonNull String permission);
+
+ /**
+ * If the given permission is not allowed for a particular process
+ * and user ID running in the system, throw a {@link SecurityException}.
+ *
+ * @param permission The name of the permission being checked.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkPermission(String, int, int)
+ */
+ public abstract void enforcePermission(
+ @NonNull String permission, int pid, int uid, @Nullable String message);
+
+ /**
+ * If the calling process of an IPC you are handling has not been
+ * granted a particular permission, throw a {@link
+ * SecurityException}. This is basically the same as calling
+ * {@link #enforcePermission(String, int, int, String)} with the
+ * pid and uid returned by {@link android.os.Binder#getCallingPid}
+ * and {@link android.os.Binder#getCallingUid}. One important
+ * difference is that if you are not currently processing an IPC,
+ * this function will always throw the SecurityException. This is
+ * done to protect against accidentally leaking permissions; you
+ * can use {@link #enforceCallingOrSelfPermission} to avoid this
+ * protection.
+ *
+ * @param permission The name of the permission being checked.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkCallingPermission(String)
+ */
+ public abstract void enforceCallingPermission(
+ @NonNull String permission, @Nullable String message);
+
+ /**
+ * If neither you nor the calling process of an IPC you are
+ * handling has been granted a particular permission, throw a
+ * {@link SecurityException}. This is the same as {@link
+ * #enforceCallingPermission}, except it grants your own
+ * permissions if you are not currently processing an IPC. Use
+ * with care!
+ *
+ * @param permission The name of the permission being checked.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkCallingOrSelfPermission(String)
+ */
+ public abstract void enforceCallingOrSelfPermission(
+ @NonNull String permission, @Nullable String message);
+
+ /**
+ * Grant permission to access a specific Uri to another package, regardless
+ * of whether that package has general permission to access the Uri's
+ * content provider. This can be used to grant specific, temporary
+ * permissions, typically in response to user interaction (such as the
+ * user opening an attachment that you would like someone else to
+ * display).
+ *
+ * <p>Normally you should use {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} with the Intent being used to
+ * start an activity instead of this function directly. If you use this
+ * function directly, you should be sure to call
+ * {@link #revokeUriPermission} when the target should no longer be allowed
+ * to access it.
+ *
+ * <p>To succeed, the content provider owning the Uri must have set the
+ * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+ * grantUriPermissions} attribute in its manifest or included the
+ * {@link android.R.styleable#AndroidManifestGrantUriPermission
+ * <grant-uri-permissions>} tag.
+ *
+ * @param toPackage The package you would like to allow to access the Uri.
+ * @param uri The Uri you would like to grant access to.
+ * @param modeFlags The desired access modes.
+ *
+ * @see #revokeUriPermission
+ */
+ public abstract void grantUriPermission(String toPackage, Uri uri,
+ @Intent.GrantUriMode int modeFlags);
+
+ /**
+ * Remove all permissions to access a particular content provider Uri
+ * that were previously added with {@link #grantUriPermission} or <em>any other</em> mechanism.
+ * The given Uri will match all previously granted Uris that are the same or a
+ * sub-path of the given Uri. That is, revoking "content://foo/target" will
+ * revoke both "content://foo/target" and "content://foo/target/sub", but not
+ * "content://foo". It will not remove any prefix grants that exist at a
+ * higher level.
+ *
+ * <p>Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, if you did not have
+ * regular permission access to a Uri, but had received access to it through
+ * a specific Uri permission grant, you could not revoke that grant with this
+ * function and a {@link SecurityException} would be thrown. As of
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this function will not throw a security
+ * exception, but will remove whatever permission grants to the Uri had been given to the app
+ * (or none).</p>
+ *
+ * <p>Unlike {@link #revokeUriPermission(String, Uri, int)}, this method impacts all permission
+ * grants matching the given Uri, for any package they had been granted to, through any
+ * mechanism this had happened (such as indirectly through the clipboard, activity launch,
+ * service start, etc). That means this can be potentially dangerous to use, as it can
+ * revoke grants that another app could be strongly expecting to stick around.</p>
+ *
+ * @param uri The Uri you would like to revoke access to.
+ * @param modeFlags The access modes to revoke.
+ *
+ * @see #grantUriPermission
+ */
+ public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
+
+ /**
+ * Remove permissions to access a particular content provider Uri
+ * that were previously added with {@link #grantUriPermission} for a specific target
+ * package. The given Uri will match all previously granted Uris that are the same or a
+ * sub-path of the given Uri. That is, revoking "content://foo/target" will
+ * revoke both "content://foo/target" and "content://foo/target/sub", but not
+ * "content://foo". It will not remove any prefix grants that exist at a
+ * higher level.
+ *
+ * <p>Unlike {@link #revokeUriPermission(Uri, int)}, this method will <em>only</em>
+ * revoke permissions that had been explicitly granted through {@link #grantUriPermission}
+ * and only for the package specified. Any matching grants that have happened through
+ * other mechanisms (clipboard, activity launching, service starting, etc) will not be
+ * removed.</p>
+ *
+ * @param toPackage The package you had previously granted access to.
+ * @param uri The Uri you would like to revoke access to.
+ * @param modeFlags The access modes to revoke.
+ *
+ * @see #grantUriPermission
+ */
+ public abstract void revokeUriPermission(String toPackage, Uri uri,
+ @Intent.AccessUriMode int modeFlags);
+
+ /**
+ * Determine whether a particular process and user ID has been granted
+ * permission to access a specific URI. This only checks for permissions
+ * that have been explicitly granted -- if the given process/uid has
+ * more general access to the URI's content provider then this check will
+ * always fail.
+ *
+ * @param uri The uri that is being checked.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the given
+ * pid/uid is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkUriPermission(Uri uri, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags);
+
+ /**
+ * Determine whether a particular process and user ID has been granted
+ * permission to access a list of URIs. This only checks for permissions
+ * that have been explicitly granted -- if the given process/uid has
+ * more general access to the URI's content provider then this check will
+ * always fail.
+ *
+ * @param uris The list of URIs that is being checked.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to check for the list of uris
+ *
+ * @return Array of permission grants corresponding to each entry in the list of uris.
+ * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+ * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ @NonNull
+ @PackageManager.PermissionResult
+ public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @PackageManager.PermissionResult
+ public abstract int checkUriPermission(Uri uri, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags, IBinder callerToken);
+
+ /**
+ * Determine whether the calling process and user ID has been
+ * granted permission to access a specific URI. This is basically
+ * the same as calling {@link #checkUriPermission(Uri, int, int,
+ * int)} with the pid and uid returned by {@link
+ * android.os.Binder#getCallingPid} and {@link
+ * android.os.Binder#getCallingUid}. One important difference is
+ * that if you are not currently processing an IPC, this function
+ * will always fail.
+ *
+ * @param uri The uri that is being checked.
+ * @param modeFlags The access modes to check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the caller
+ * is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkUriPermission(Uri, int, int, int)
+ */
+ @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
+
+ /**
+ * Determine whether the calling process and user ID has been
+ * granted permission to access a list of URIs. This is basically
+ * the same as calling {@link #checkUriPermissions(List, int, int, int)}
+ * with the pid and uid returned by {@link
+ * android.os.Binder#getCallingPid} and {@link
+ * android.os.Binder#getCallingUid}. One important difference is
+ * that if you are not currently processing an IPC, this function
+ * will always fail.
+ *
+ * @param uris The list of URIs that is being checked.
+ * @param modeFlags The access modes to check.
+ *
+ * @return Array of permission grants corresponding to each entry in the list of uris.
+ * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+ * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkUriPermission(Uri, int, int, int)
+ */
+ @NonNull
+ @PackageManager.PermissionResult
+ public int[] checkCallingUriPermissions(@NonNull List<Uri> uris,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Determine whether the calling process of an IPC <em>or you</em> has been granted
+ * permission to access a specific URI. This is the same as
+ * {@link #checkCallingUriPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param uri The uri that is being checked.
+ * @param modeFlags The access modes to check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the caller
+ * is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkCallingOrSelfUriPermission(Uri uri,
+ @Intent.AccessUriMode int modeFlags);
+
+ /**
+ * Determine whether the calling process of an IPC <em>or you</em> has been granted
+ * permission to access a list of URIs. This is the same as
+ * {@link #checkCallingUriPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param uris The list of URIs that is being checked.
+ * @param modeFlags The access modes to check.
+ *
+ * @return Array of permission grants corresponding to each entry in the list of uris.
+ * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+ * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ @NonNull
+ @PackageManager.PermissionResult
+ public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Check both a Uri and normal permission. This allows you to perform
+ * both {@link #checkPermission} and {@link #checkUriPermission} in one
+ * call.
+ *
+ * @param uri The Uri whose permission is to be checked, or null to not
+ * do this check.
+ * @param readPermission The permission that provides overall read access,
+ * or null to not do this check.
+ * @param writePermission The permission that provides overall write
+ * access, or null to not do this check.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the caller
+ * is allowed to access that uri or holds one of the given permissions, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ */
+ @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)")
+ @PackageManager.PermissionResult
+ public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
+ @Nullable String writePermission, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags);
+
+ /**
+ * If a particular process and user ID has not been granted
+ * permission to access a specific URI, throw {@link
+ * SecurityException}. This only checks for permissions that have
+ * been explicitly granted -- if the given process/uid has more
+ * general access to the URI's content provider then this check
+ * will always fail.
+ *
+ * @param uri The uri that is being checked.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to enforce.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkUriPermission(Uri, int, int, int)
+ */
+ public abstract void enforceUriPermission(
+ Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message);
+
+ /**
+ * If the calling process and user ID has not been granted
+ * permission to access a specific URI, throw {@link
+ * SecurityException}. This is basically the same as calling
+ * {@link #enforceUriPermission(Uri, int, int, int, String)} with
+ * the pid and uid returned by {@link
+ * android.os.Binder#getCallingPid} and {@link
+ * android.os.Binder#getCallingUid}. One important difference is
+ * that if you are not currently processing an IPC, this function
+ * will always throw a SecurityException.
+ *
+ * @param uri The uri that is being checked.
+ * @param modeFlags The access modes to enforce.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkCallingUriPermission(Uri, int)
+ */
+ public abstract void enforceCallingUriPermission(
+ Uri uri, @Intent.AccessUriMode int modeFlags, String message);
+
+ /**
+ * If the calling process of an IPC <em>or you</em> has not been
+ * granted permission to access a specific URI, throw {@link
+ * SecurityException}. This is the same as {@link
+ * #enforceCallingUriPermission}, except it grants your own
+ * permissions if you are not currently processing an IPC. Use
+ * with care!
+ *
+ * @param uri The uri that is being checked.
+ * @param modeFlags The access modes to enforce.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkCallingOrSelfUriPermission(Uri, int)
+ */
+ public abstract void enforceCallingOrSelfUriPermission(
+ Uri uri, @Intent.AccessUriMode int modeFlags, String message);
+
+ /**
+ * Enforce both a Uri and normal permission. This allows you to perform
+ * both {@link #enforcePermission} and {@link #enforceUriPermission} in one
+ * call.
+ *
+ * @param uri The Uri whose permission is to be checked, or null to not
+ * do this check.
+ * @param readPermission The permission that provides overall read access,
+ * or null to not do this check.
+ * @param writePermission The permission that provides overall write
+ * access, or null to not do this check.
+ * @param pid The process ID being checked against. Must be > 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to enforce.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkUriPermission(Uri, String, String, int, int, int)
+ */
+ public abstract void enforceUriPermission(
+ @Nullable Uri uri, @Nullable String readPermission,
+ @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags,
+ @Nullable String message);
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "CONTEXT_" }, value = {
+ CONTEXT_INCLUDE_CODE,
+ CONTEXT_IGNORE_SECURITY,
+ CONTEXT_RESTRICTED,
+ CONTEXT_DEVICE_PROTECTED_STORAGE,
+ CONTEXT_CREDENTIAL_PROTECTED_STORAGE,
+ CONTEXT_REGISTER_PACKAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CreatePackageOptions {}
+
+ /**
+ * Flag for use with {@link #createPackageContext}: include the application
+ * code with the context. This means loading code into the caller's
+ * process, so that {@link #getClassLoader()} can be used to instantiate
+ * the application's classes. Setting this flags imposes security
+ * restrictions on what application context you can access; if the
+ * requested application can not be safely loaded into your process,
+ * java.lang.SecurityException will be thrown. If this flag is not set,
+ * there will be no restrictions on the packages that can be loaded,
+ * but {@link #getClassLoader} will always return the default system
+ * class loader.
+ */
+ public static final int CONTEXT_INCLUDE_CODE = 0x00000001;
+
+ /**
+ * Flag for use with {@link #createPackageContext}: ignore any security
+ * restrictions on the Context being requested, allowing it to always
+ * be loaded. For use with {@link #CONTEXT_INCLUDE_CODE} to allow code
+ * to be loaded into a process even when it isn't safe to do so. Use
+ * with extreme care!
+ */
+ public static final int CONTEXT_IGNORE_SECURITY = 0x00000002;
+
+ /**
+ * Flag for use with {@link #createPackageContext}: a restricted context may
+ * disable specific features. For instance, a View associated with a restricted
+ * context would ignore particular XML attributes.
+ */
+ public static final int CONTEXT_RESTRICTED = 0x00000004;
+
+ /**
+ * Flag for use with {@link #createPackageContext}: point all file APIs at
+ * device-protected storage.
+ *
+ * @hide
+ */
+ public static final int CONTEXT_DEVICE_PROTECTED_STORAGE = 0x00000008;
+
+ /**
+ * Flag for use with {@link #createPackageContext}: point all file APIs at
+ * credential-protected storage.
+ *
+ * @hide
+ */
+ public static final int CONTEXT_CREDENTIAL_PROTECTED_STORAGE = 0x00000010;
+
+ /**
+ * @hide Used to indicate we should tell the activity manager about the process
+ * loading this code.
+ */
+ public static final int CONTEXT_REGISTER_PACKAGE = 0x40000000;
+
+ /**
+ * Return a new Context object for the given application name. This
+ * Context is the same as what the named application gets when it is
+ * launched, containing the same resources and class loader. Each call to
+ * this method returns a new instance of a Context object; Context objects
+ * are not shared, however they share common state (Resources, ClassLoader,
+ * etc) so the Context instance itself is fairly lightweight.
+ *
+ * <p>Throws {@link android.content.pm.PackageManager.NameNotFoundException} if there is no
+ * application with the given package name.
+ *
+ * <p>Throws {@link java.lang.SecurityException} if the Context requested
+ * can not be loaded into the caller's process for security reasons (see
+ * {@link #CONTEXT_INCLUDE_CODE} for more information}.
+ *
+ * @param packageName Name of the application's package.
+ * @param flags Option flags.
+ *
+ * @return A {@link Context} for the application.
+ *
+ * @throws SecurityException
+ * @throws PackageManager.NameNotFoundException if there is no application with
+ * the given package name.
+ */
+ public abstract Context createPackageContext(String packageName,
+ @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Similar to {@link #createPackageContext(String, int)}, but with a
+ * different {@link UserHandle}. For example, {@link #getContentResolver()}
+ * will open any {@link Uri} as the given user.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Context createPackageContextAsUser(
+ @NonNull String packageName, @CreatePackageOptions int flags, @NonNull UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ if (Build.IS_ENG) {
+ throw new IllegalStateException("createPackageContextAsUser not overridden!");
+ }
+ return this;
+ }
+
+ /**
+ * Similar to {@link #createPackageContext(String, int)}, but for the own package with a
+ * different {@link UserHandle}. For example, {@link #getContentResolver()}
+ * will open any {@link Uri} as the given user.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Context createContextAsUser(@NonNull UserHandle user, @CreatePackageOptions int flags) {
+ if (Build.IS_ENG) {
+ throw new IllegalStateException("createContextAsUser not overridden!");
+ }
+ return this;
+ }
+
+ /**
+ * Creates a context given an {@link android.content.pm.ApplicationInfo}.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract Context createApplicationContext(ApplicationInfo application,
+ @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Return a new Context object for the given split name. The new Context has a ClassLoader and
+ * Resources object that can access the split's and all of its dependencies' code/resources.
+ * Each call to this method returns a new instance of a Context object;
+ * Context objects are not shared, however common state (ClassLoader, other Resources for
+ * the same split) may be so the Context itself can be fairly lightweight.
+ *
+ * @param splitName The name of the split to include, as declared in the split's
+ * <code>AndroidManifest.xml</code>.
+ * @return A {@link Context} with the given split's code and/or resources loaded.
+ */
+ public abstract Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Get the user associated with this context.
+ *
+ * @return the user associated with this context
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public UserHandle getUser() {
+ return android.os.Process.myUserHandle();
+ }
+
+ /**
+ * Get the user associated with this context
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public @UserIdInt int getUserId() {
+ return android.os.UserHandle.myUserId();
+ }
+
+ /**
+ * Return a new Context object for the current Context but whose resources
+ * are adjusted to match the given Configuration. Each call to this method
+ * returns a new instance of a Context object; Context objects are not
+ * shared, however common state (ClassLoader, other Resources for the
+ * same configuration) may be so the Context itself can be fairly lightweight.
+ *
+ * @param overrideConfiguration A {@link Configuration} specifying what
+ * values to modify in the base Configuration of the original Context's
+ * resources. If the base configuration changes (such as due to an
+ * orientation change), the resources of this context will also change except
+ * for those that have been explicitly overridden with a value here.
+ *
+ * @return A {@link Context} with the given configuration override.
+ */
+ public abstract Context createConfigurationContext(
+ @NonNull Configuration overrideConfiguration);
+
+ /**
+ * Return a new Context object for the current Context but whose resources
+ * are adjusted to match the metrics of the given Display. Each call to this method
+ * returns a new instance of a Context object; Context objects are not
+ * shared, however common state (ClassLoader, other Resources for the
+ * same configuration) may be so the Context itself can be fairly lightweight.
+ *
+ * To obtain an instance of a {@link WindowManager} (see {@link #getSystemService(String)}) that
+ * is configured to show windows on the given display call
+ * {@link #createWindowContext(int, Bundle)} on the returned display Context or use an
+ * {@link android.app.Activity}.
+ *
+ * <p>
+ * Note that invoking #createDisplayContext(Display) from an UI context is not regarded
+ * as an UI context. In other words, it is not suggested to access UI components (such as
+ * obtain a {@link WindowManager} by {@link #getSystemService(String)})
+ * from the context created from #createDisplayContext(Display).
+ * </p>
+ *
+ * @param display A {@link Display} object specifying the display for whose metrics the
+ * Context's resources should be tailored.
+ *
+ * @return A {@link Context} for the display.
+ *
+ * @see #getSystemService(String)
+ */
+ @DisplayContext
+ public abstract Context createDisplayContext(@NonNull Display display);
+
+ /**
+ * Creates a Context for a non-activity window.
+ *
+ * <p>
+ * A window context is a context that can be used to add non-activity windows, such as
+ * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}. A window context
+ * must be created from a context that has an associated {@link Display}, such as
+ * {@link android.app.Activity Activity} or a context created with
+ * {@link #createDisplayContext(Display)}.
+ *
+ * <p>
+ * The window context is created with the appropriate {@link Configuration} for the area of the
+ * display that the windows created with it can occupy; it must be used when
+ * {@link android.view.LayoutInflater inflating} views, such that they can be inflated with
+ * proper {@link Resources}.
+ *
+ * Below is a sample code to <b>add an application overlay window on the primary display:</b>
+ * <pre class="prettyprint">
+ * ...
+ * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class);
+ * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ * final Context windowContext = anyContext.createDisplayContext(primaryDisplay)
+ * .createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+ * final View overlayView = Inflater.from(windowContext).inflate(someLayoutXml, null);
+ *
+ * // WindowManager.LayoutParams initialization
+ * ...
+ * // The types used in addView and createWindowContext must match.
+ * mParams.type = TYPE_APPLICATION_OVERLAY;
+ * ...
+ *
+ * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+ * </pre>
+ *
+ * <p>
+ * This context's configuration and resources are adjusted to an area of the display where
+ * the windows with provided type will be added. <b>Note that all windows associated with the
+ * same context will have an affinity and can only be moved together between different displays
+ * or areas on a display.</b> If there is a need to add different window types, or
+ * non-associated windows, separate Contexts should be used.
+ * </p>
+ * <p>
+ * Creating a window context is an expensive operation. Misuse of this API may lead to a huge
+ * performance drop. The best practice is to use the same window context when possible.
+ * An approach is to create one window context with specific window type and display and
+ * use it everywhere it's needed.
+ * </p>
+ * <p>
+ * After {@link Build.VERSION_CODES#S}, window context provides the capability to receive
+ * configuration changes for existing token by overriding the
+ * {@link android.view.WindowManager.LayoutParams#token token} of the
+ * {@link android.view.WindowManager.LayoutParams} passed in
+ * {@link WindowManager#addView(View, LayoutParams)}. This is useful when an application needs
+ * to attach its window to an existing activity for window token sharing use-case.
+ * </p>
+ * <p>
+ * Note that the window context in {@link Build.VERSION_CODES#R} didn't have this
+ * capability. This is a no-op for the window context in {@link Build.VERSION_CODES#R}.
+ * </p>
+ * Below is sample code to <b>attach an existing token to a window context:</b>
+ * <pre class="prettyprint">
+ * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class);
+ * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ * final Context windowContext = anyContext.createWindowContext(primaryDisplay,
+ * TYPE_APPLICATION, null);
+ *
+ * // Get an existing token.
+ * final IBinder existingToken = activity.getWindow().getAttributes().token;
+ *
+ * // The types used in addView() and createWindowContext() must match.
+ * final WindowManager.LayoutParams params = new WindowManager.LayoutParams(TYPE_APPLICATION);
+ * params.token = existingToken;
+ *
+ * // After WindowManager#addView(), the server side will extract the provided token from
+ * // LayoutParams#token (existingToken in the sample code), and switch to propagate
+ * // configuration changes from the node associated with the provided token.
+ * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+ * </pre>
+ * <p>
+ * After {@link Build.VERSION_CODES#S}, window context provides the capability to listen to its
+ * {@link Configuration} changes by calling
+ * {@link #registerComponentCallbacks(ComponentCallbacks)}, while other kinds of {@link Context}
+ * will register the {@link ComponentCallbacks} to {@link #getApplicationContext() its
+ * Application context}. Note that window context only propagate
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback.
+ * {@link ComponentCallbacks#onLowMemory()} or other callbacks in {@link ComponentCallbacks2}
+ * won't be invoked.
+ * </p>
+ * <p>
+ * Note that using {@link android.app.Application} or {@link android.app.Service} context for
+ * UI-related queries may result in layout or continuity issues on devices with variable screen
+ * sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
+ * the {@link Configuration} changes for the visual container.
+ * </p>
+ * @param type Window type in {@link WindowManager.LayoutParams}
+ * @param options A bundle used to pass window-related options
+ * @return A {@link Context} that can be used to create
+ * non-{@link android.app.Activity activity} windows.
+ *
+ * @see #getSystemService(String)
+ * @see #getSystemService(Class)
+ * @see #WINDOW_SERVICE
+ * @see #LAYOUT_INFLATER_SERVICE
+ * @see #WALLPAPER_SERVICE
+ * @throws UnsupportedOperationException if this {@link Context} does not attach to a display,
+ * such as {@link android.app.Application Application} or {@link android.app.Service Service}.
+ */
+ @UiContext
+ @NonNull
+ public Context createWindowContext(@WindowType int type, @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Creates a {@code Context} for a non-{@link android.app.Activity activity} window on the given
+ * {@link Display}.
+ *
+ * <p>
+ * Similar to {@link #createWindowContext(int, Bundle)}, but the {@code display} is passed in,
+ * instead of implicitly using the {@link #getDisplay() original Context's Display}.
+ * </p>
+ *
+ * @param display The {@link Display} to associate with
+ * @param type Window type in {@link WindowManager.LayoutParams}
+ * @param options A bundle used to pass window-related options.
+ * @return A {@link Context} that can be used to create
+ * non-{@link android.app.Activity activity} windows.
+ * @throws IllegalArgumentException if the {@link Display} is {@code null}.
+ *
+ * @see #getSystemService(String)
+ * @see #getSystemService(Class)
+ * @see #WINDOW_SERVICE
+ * @see #LAYOUT_INFLATER_SERVICE
+ * @see #WALLPAPER_SERVICE
+ */
+ @UiContext
+ @NonNull
+ public Context createWindowContext(@NonNull Display display, @WindowType int type,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Creates a context with specific properties and behaviors.
+ *
+ * @param contextParams Parameters for how the new context should behave.
+ * @return A context with the specified behaviors.
+ *
+ * @see ContextParams
+ */
+ @NonNull
+ public Context createContext(@NonNull ContextParams contextParams) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Return a new Context object for the current Context but attribute to a different tag.
+ * In complex apps attribution tagging can be used to distinguish between separate logical
+ * parts.
+ *
+ * @param attributionTag The tag or {@code null} to create a context for the default.
+ *
+ * @return A {@link Context} that is tagged for the new attribution
+ *
+ * @see #getAttributionTag()
+ */
+ public @NonNull Context createAttributionContext(@Nullable String attributionTag) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ // TODO moltmann: remove
+ /**
+ * @removed
+ */
+ @Deprecated
+ public @NonNull Context createFeatureContext(@Nullable String attributionTag) {
+ return createContext(new ContextParams.Builder(getParams())
+ .setAttributionTag(attributionTag)
+ .build());
+ }
+
+ /**
+ * Return a new Context object for the current Context but whose storage
+ * APIs are backed by device-protected storage.
+ * <p>
+ * On devices with direct boot, data stored in this location is encrypted
+ * with a key tied to the physical device, and it can be accessed
+ * immediately after the device has booted successfully, both
+ * <em>before and after</em> the user has authenticated with their
+ * credentials (such as a lock pattern or PIN).
+ * <p>
+ * Because device-protected data is available without user authentication,
+ * you should carefully limit the data you store using this Context. For
+ * example, storing sensitive authentication tokens or passwords in the
+ * device-protected area is strongly discouraged.
+ * <p>
+ * If the underlying device does not have the ability to store
+ * device-protected and credential-protected data using different keys, then
+ * both storage areas will become available at the same time. They remain as
+ * two distinct storage locations on disk, and only the window of
+ * availability changes.
+ * <p>
+ * Each call to this method returns a new instance of a Context object;
+ * Context objects are not shared, however common state (ClassLoader, other
+ * Resources for the same configuration) may be so the Context itself can be
+ * fairly lightweight.
+ *
+ * @see #isDeviceProtectedStorage()
+ */
+ public abstract Context createDeviceProtectedStorageContext();
+
+ /**
+ * Return a new Context object for the current Context but whose storage
+ * APIs are backed by credential-protected storage. This is the default
+ * storage area for apps unless
+ * {@link android.R.attr#defaultToDeviceProtectedStorage} was requested.
+ * <p>
+ * On devices with direct boot, data stored in this location is encrypted
+ * with a key tied to user credentials, which can be accessed
+ * <em>only after</em> the user has entered their credentials (such as a
+ * lock pattern or PIN).
+ * <p>
+ * If the underlying device does not have the ability to store
+ * device-protected and credential-protected data using different keys, then
+ * both storage areas will become available at the same time. They remain as
+ * two distinct storage locations on disk, and only the window of
+ * availability changes.
+ * <p>
+ * Each call to this method returns a new instance of a Context object;
+ * Context objects are not shared, however common state (ClassLoader, other
+ * Resources for the same configuration) may be so the Context itself can be
+ * fairly lightweight.
+ *
+ * @see #isCredentialProtectedStorage()
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ public abstract Context createCredentialProtectedStorageContext();
+
+ /**
+ * Creates a UI context with a {@code token}. The users of this API should handle this context's
+ * configuration changes.
+ *
+ * @param token The token to associate with the {@link Resources}
+ * @param display The display to associate with the token context
+ *
+ * @hide
+ */
+ @UiContext
+ @NonNull
+ public Context createTokenContext(@NonNull IBinder token, @NonNull Display display) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Gets the display adjustments holder for this context. This information
+ * is provided on a per-application or activity basis and is used to simulate lower density
+ * display metrics for legacy applications and restricted screen sizes.
+ *
+ * @param displayId The display id for which to get compatibility info.
+ * @return The compatibility info holder, or null if not required by the application.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract DisplayAdjustments getDisplayAdjustments(int displayId);
+
+ /**
+ * Get the display this context is associated with. Applications should use this method with
+ * {@link android.app.Activity} or a context associated with a {@link Display} via
+ * {@link #createDisplayContext(Display)} to get a display object associated with a Context, or
+ * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id.
+ * @return Returns the {@link Display} object this context is associated with.
+ * @throws UnsupportedOperationException if the method is called on an instance that is not
+ * associated with any display.
+ */
+ @Nullable
+ public Display getDisplay() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * A version of {@link #getDisplay()} that does not perform a Context misuse check to be used by
+ * legacy APIs.
+ * TODO(b/149790106): Fix usages and remove.
+ * @hide
+ */
+ @Nullable
+ public Display getDisplayNoVerify() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Gets the ID of the display this context is associated with.
+ *
+ * @return display ID associated with this {@link Context}.
+ * @see #getDisplay()
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @TestApi
+ public abstract int getDisplayId();
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void updateDisplay(int displayId);
+
+ /**
+ * Indicates whether this Context is restricted.
+ *
+ * @return {@code true} if this Context is restricted, {@code false} otherwise.
+ *
+ * @see #CONTEXT_RESTRICTED
+ */
+ public boolean isRestricted() {
+ return false;
+ }
+
+ /**
+ * Indicates if the storage APIs of this Context are backed by
+ * device-protected storage.
+ *
+ * @see #createDeviceProtectedStorageContext()
+ */
+ public abstract boolean isDeviceProtectedStorage();
+
+ /**
+ * Indicates if the storage APIs of this Context are backed by
+ * credential-protected storage.
+ *
+ * @see #createCredentialProtectedStorageContext()
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ public abstract boolean isCredentialProtectedStorage();
+
+ /**
+ * Returns true if the context can load unsafe resources, e.g. fonts.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract boolean canLoadUnsafeResources();
+
+ /**
+ * Returns token if the {@link Context} is a {@link android.app.Activity}. Returns
+ * {@code null} otherwise.
+ *
+ * @hide
+ */
+ @Nullable
+ public IBinder getActivityToken() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Returns token if the {@link Context} is a {@link android.app.WindowContext}. Returns
+ * {@code null} otherwise.
+ *
+ * @hide
+ */
+ @Nullable
+ public IBinder getWindowContextToken() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Returns the proper token of a {@link Context}.
+ *
+ * If the {@link Context} is an {@link android.app.Activity}, returns
+ * {@link #getActivityToken()}. If the {@lijnk Context} is a {@link android.app.WindowContext},
+ * returns {@link #getWindowContextToken()}. Returns {@code null}, otherwise.
+ *
+ * @hide
+ */
+ @Nullable
+ public static IBinder getToken(@NonNull Context context) {
+ return context.getActivityToken() != null ? context.getActivityToken()
+ : context.getWindowContextToken();
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler,
+ int flags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * @hide
+ */
+ public IApplicationThread getIApplicationThread() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * @hide
+ */
+ public Handler getMainThreadHandler() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * @hide
+ */
+ public AutofillClient getAutofillClient() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAutofillClient(@SuppressWarnings("unused") AutofillClient client) {
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public ContentCaptureClient getContentCaptureClient() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public final boolean isAutofillCompatibilityEnabled() {
+ final AutofillOptions options = getAutofillOptions();
+ return options != null && options.compatModeEnabled;
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public AutofillOptions getAutofillOptions() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public void setAutofillOptions(@SuppressWarnings("unused") @Nullable AutofillOptions options) {
+ }
+
+ /**
+ * Gets the Content Capture options for this context, or {@code null} if it's not allowlisted.
+ *
+ * @hide
+ */
+ @Nullable
+ public ContentCaptureOptions getContentCaptureOptions() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public void setContentCaptureOptions(
+ @SuppressWarnings("unused") @Nullable ContentCaptureOptions options) {
+ }
+
+ /**
+ * Throws an exception if the Context is using system resources,
+ * which are non-runtime-overlay-themable and may show inconsistent UI.
+ * @hide
+ */
+ public void assertRuntimeOverlayThemable() {
+ // Resources.getSystem() is a singleton and the only Resources not managed by
+ // ResourcesManager; therefore Resources.getSystem() is not themable.
+ if (getResources() == Resources.getSystem()) {
+ throw new IllegalArgumentException("Non-UI context used to display UI; "
+ + "get a UI context from ActivityThread#getSystemUiContext()");
+ }
+ }
+
+ /**
+ * Returns {@code true} if the context is a UI context which can access UI components such as
+ * {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or
+ * {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI
+ * contexts throws {@link android.os.strictmode.Violation} if
+ * {@link android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
+ * <p>
+ * Examples of UI contexts are
+ * an {@link android.app.Activity Activity}, a context created from
+ * {@link #createWindowContext(int, Bundle)} or
+ * {@link android.inputmethodservice.InputMethodService InputMethodService}
+ * </p>
+ * <p>
+ * Note that even if it is allowed programmatically, it is not suggested to override this
+ * method to bypass {@link android.os.strictmode.IncorrectContextUseViolation} verification.
+ * </p>
+ *
+ * @see #getDisplay()
+ * @see #getSystemService(String)
+ * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
+ */
+ public boolean isUiContext() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Called when a {@link Context} is going to be released.
+ * This method can be overridden to perform the final cleanups, such as release
+ * {@link BroadcastReceiver} registrations.
+ *
+ * @see WindowContext#destroy()
+ *
+ * @hide
+ */
+ public void destroy() { }
+
+ /**
+ * Indicates this {@link Context} has the proper {@link Configuration} to obtain
+ * {@link android.view.LayoutInflater}, {@link android.view.ViewConfiguration} and
+ * {@link android.view.GestureDetector}. Generally, all UI contexts, such as
+ * {@link android.app.Activity} or {@link android.app.WindowContext}, are initialized with base
+ * configuration.
+ * <p>
+ * Note that the context created via {@link Context#createConfigurationContext(Configuration)}
+ * is also regarded as a context that is based on a configuration because the
+ * configuration is explicitly provided via the API.
+ * </p>
+ *
+ * @see #isUiContext()
+ * @see #createConfigurationContext(Configuration)
+ *
+ * @hide
+ */
+ public boolean isConfigurationContext() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+}
diff --git a/android/content/ContextParams.java b/android/content/ContextParams.java
new file mode 100644
index 0000000..5cc3a24
--- /dev/null
+++ b/android/content/ContextParams.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.content.pm.PackageManager;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents rules around how a context being created via
+ * {@link Context#createContext} should behave.
+ *
+ * <p>One of the dimensions to customize is how permissions should behave.
+ * For example, you can specify how permission accesses from a context should
+ * be attributed in the platform's permission tracking system.
+ *
+ * <p>The two main types of attribution are: against an attribution tag which
+ * is an arbitrary string your app specifies for the purposes of tracking permission
+ * accesses from a given portion of your app; against another package and optionally
+ * its attribution tag if you are accessing the data on behalf of another app and
+ * you will be passing that data to this app, recursively. Both attributions are
+ * not mutually exclusive.
+ *
+ * @see Context#createContext(ContextParams)
+ * @see AttributionSource
+ */
+public final class ContextParams {
+ private final @Nullable String mAttributionTag;
+ private final @Nullable AttributionSource mNext;
+ private final @NonNull Set<String> mRenouncedPermissions;
+
+ /** {@hide} */
+ public static final ContextParams EMPTY = new ContextParams.Builder().build();
+
+ private ContextParams(@Nullable String attributionTag,
+ @Nullable AttributionSource next,
+ @Nullable Set<String> renouncedPermissions) {
+ mAttributionTag = attributionTag;
+ mNext = next;
+ mRenouncedPermissions = (renouncedPermissions != null)
+ ? renouncedPermissions : Collections.emptySet();
+ }
+
+ /**
+ * @return The attribution tag.
+ */
+ @Nullable
+ public String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ /**
+ * @return The set of permissions to treat as renounced.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ public @NonNull Set<String> getRenouncedPermissions() {
+ return mRenouncedPermissions;
+ }
+
+ /** @hide */
+ public boolean isRenouncedPermission(@NonNull String permission) {
+ return mRenouncedPermissions.contains(permission);
+ }
+
+ /**
+ * @return The receiving attribution source.
+ */
+ @Nullable
+ public AttributionSource getNextAttributionSource() {
+ return mNext;
+ }
+
+ /**
+ * Builder for creating a {@link ContextParams}.
+ */
+ public static final class Builder {
+ private @Nullable String mAttributionTag;
+ private @NonNull Set<String> mRenouncedPermissions = Collections.emptySet();
+ private @Nullable AttributionSource mNext;
+
+ /**
+ * Create a new builder.
+ * <p>
+ * This is valuable when you are interested in having explicit control
+ * over every sub-parameter, and don't want to inherit any values from
+ * an existing Context.
+ * <p>
+ * Developers should strongly consider using
+ * {@link #Builder(ContextParams)} instead of this constructor, since
+ * that will will automatically inherit any new sub-parameters added in
+ * future platform releases.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Create a new builder that inherits all sub-parameters by default.
+ * <p>
+ * This is valuable when you are only interested in overriding specific
+ * sub-parameters, and want to preserve all other parameters. Setting a
+ * specific sub-parameter on the returned builder will override any
+ * inherited value.
+ */
+ public Builder(@NonNull ContextParams params) {
+ Objects.requireNonNull(params);
+ mAttributionTag = params.mAttributionTag;
+ mRenouncedPermissions = params.mRenouncedPermissions;
+ mNext = params.mNext;
+ }
+
+ /**
+ * Sets an attribution tag against which to track permission accesses.
+ *
+ * @param attributionTag The attribution tag.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setAttributionTag(@Nullable String attributionTag) {
+ mAttributionTag = attributionTag;
+ return this;
+ }
+
+ /**
+ * Sets the attribution source for the app on whose behalf you are doing the work.
+ *
+ * @param next The permission identity of the receiving app.
+ * @return This builder.
+ *
+ * @see AttributionSource
+ */
+ @NonNull
+ public Builder setNextAttributionSource(@Nullable AttributionSource next) {
+ mNext = next;
+ return this;
+ }
+
+ /**
+ * Sets permissions which have been voluntarily "renounced" by the
+ * calling app.
+ * <p>
+ * Interactions performed through services obtained from the created
+ * Context will ideally be treated as if these "renounced" permissions
+ * have not actually been granted to the app, regardless of their actual
+ * grant status.
+ * <p>
+ * This is designed for use by separate logical components within an app
+ * which have no intention of interacting with data or services that are
+ * protected by the renounced permissions.
+ * <p>
+ * Note that only {@link PermissionInfo#PROTECTION_DANGEROUS}
+ * permissions are supported by this mechanism. Additionally, this
+ * mechanism only applies to calls made through services obtained via
+ * {@link Context#getSystemService}; it has no effect on static or raw
+ * Binder calls.
+ *
+ * @param renouncedPermissions The set of permissions to treat as
+ * renounced, which is as if not granted.
+ * @return This builder.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
+ public @NonNull Builder setRenouncedPermissions(
+ @Nullable Set<String> renouncedPermissions) {
+ // This is not a security check but a fail fast - the OS enforces the permission too
+ if (renouncedPermissions != null && !renouncedPermissions.isEmpty()
+ && ActivityThread.currentApplication().checkSelfPermission(Manifest.permission
+ .RENOUNCE_PERMISSIONS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Renouncing permissions requires: "
+ + Manifest.permission.RENOUNCE_PERMISSIONS);
+ }
+ mRenouncedPermissions = renouncedPermissions;
+ return this;
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @return The new instance.
+ */
+ @NonNull
+ public ContextParams build() {
+ return new ContextParams(mAttributionTag, mNext,
+ mRenouncedPermissions);
+ }
+ }
+}
diff --git a/android/content/ContextWrapper.java b/android/content/ContextWrapper.java
new file mode 100644
index 0000000..6324d0e
--- /dev/null
+++ b/android/content/ContextWrapper.java
@@ -0,0 +1,1278 @@
+/*
+ * Copyright (C) 2006 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+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.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.autofill.AutofillManager.AutofillClient;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Proxying implementation of Context that simply delegates all of its calls to
+ * another Context. Can be subclassed to modify behavior without changing
+ * the original Context.
+ */
+public class ContextWrapper extends Context {
+ @UnsupportedAppUsage
+ Context mBase;
+
+ public ContextWrapper(Context base) {
+ mBase = base;
+ }
+
+ /**
+ * Set the base context for this ContextWrapper. All calls will then be
+ * delegated to the base context. Throws
+ * IllegalStateException if a base context has already been set.
+ *
+ * @param base The new base context for this wrapper.
+ */
+ protected void attachBaseContext(Context base) {
+ if (mBase != null) {
+ throw new IllegalStateException("Base context already set");
+ }
+ mBase = base;
+ }
+
+ /**
+ * @return the base context as set by the constructor or setBaseContext
+ */
+ public Context getBaseContext() {
+ return mBase;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ return mBase.getAssets();
+ }
+
+ @Override
+ public Resources getResources() {
+ return mBase.getResources();
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mBase.getPackageManager();
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mBase.getContentResolver();
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ return mBase.getMainLooper();
+ }
+
+ @Override
+ public Executor getMainExecutor() {
+ return mBase.getMainExecutor();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mBase.getApplicationContext();
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ mBase.setTheme(resid);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage
+ public int getThemeResId() {
+ return mBase.getThemeResId();
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return mBase.getTheme();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return mBase.getClassLoader();
+ }
+
+ @Override
+ public String getPackageName() {
+ return mBase.getPackageName();
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage
+ public String getBasePackageName() {
+ return mBase.getBasePackageName();
+ }
+
+ /** @hide */
+ @Override
+ public String getOpPackageName() {
+ return mBase.getOpPackageName();
+ }
+
+ /** @hide */
+ @Override
+ public @Nullable String getAttributionTag() {
+ return mBase.getAttributionTag();
+ }
+
+ @Override
+ public @Nullable ContextParams getParams() {
+ return mBase.getParams();
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mBase.getApplicationInfo();
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ return mBase.getPackageResourcePath();
+ }
+
+ @Override
+ public String getPackageCodePath() {
+ return mBase.getPackageCodePath();
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ return mBase.getSharedPreferences(name, mode);
+ }
+
+ /** @removed */
+ @Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
+ return mBase.getSharedPreferences(file, mode);
+ }
+
+ /** @hide */
+ @Override
+ public void reloadSharedPreferences() {
+ mBase.reloadSharedPreferences();
+ }
+
+ @Override
+ public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
+ return mBase.moveSharedPreferencesFrom(sourceContext, name);
+ }
+
+ @Override
+ public boolean deleteSharedPreferences(String name) {
+ return mBase.deleteSharedPreferences(name);
+ }
+
+ @Override
+ public FileInputStream openFileInput(String name)
+ throws FileNotFoundException {
+ return mBase.openFileInput(name);
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String name, int mode)
+ throws FileNotFoundException {
+ return mBase.openFileOutput(name, mode);
+ }
+
+ @Override
+ public boolean deleteFile(String name) {
+ return mBase.deleteFile(name);
+ }
+
+ @Override
+ public File getFileStreamPath(String name) {
+ return mBase.getFileStreamPath(name);
+ }
+
+ /** @removed */
+ @Override
+ public File getSharedPreferencesPath(String name) {
+ return mBase.getSharedPreferencesPath(name);
+ }
+
+ @Override
+ public String[] fileList() {
+ return mBase.fileList();
+ }
+
+ @Override
+ public File getDataDir() {
+ return mBase.getDataDir();
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mBase.getFilesDir();
+ }
+
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @NonNull
+ @Override
+ public File getCrateDir(@NonNull String cratedId) {
+ return mBase.getCrateDir(cratedId);
+ }
+
+ @Override
+ public File getNoBackupFilesDir() {
+ return mBase.getNoBackupFilesDir();
+ }
+
+ @Override
+ public @Nullable File getExternalFilesDir(@Nullable String type) {
+ return mBase.getExternalFilesDir(type);
+ }
+
+ @Override
+ public File[] getExternalFilesDirs(String type) {
+ return mBase.getExternalFilesDirs(type);
+ }
+
+ @Override
+ public File getObbDir() {
+ return mBase.getObbDir();
+ }
+
+ @Override
+ public File[] getObbDirs() {
+ return mBase.getObbDirs();
+ }
+
+ @Override
+ public File getCacheDir() {
+ return mBase.getCacheDir();
+ }
+
+ @Override
+ public File getCodeCacheDir() {
+ return mBase.getCodeCacheDir();
+ }
+
+ @Override
+ public @Nullable File getExternalCacheDir() {
+ return mBase.getExternalCacheDir();
+ }
+
+ @Override
+ public File[] getExternalCacheDirs() {
+ return mBase.getExternalCacheDirs();
+ }
+
+ @Override
+ public File[] getExternalMediaDirs() {
+ return mBase.getExternalMediaDirs();
+ }
+
+ @Override
+ public File getDir(String name, int mode) {
+ return mBase.getDir(name, mode);
+ }
+
+
+ /** @hide **/
+ @Override
+ public @Nullable File getPreloadsFileCache() {
+ return mBase.getPreloadsFileCache();
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
+ return mBase.openOrCreateDatabase(name, mode, factory);
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
+ @Nullable DatabaseErrorHandler errorHandler) {
+ return mBase.openOrCreateDatabase(name, mode, factory, errorHandler);
+ }
+
+ @Override
+ public boolean moveDatabaseFrom(Context sourceContext, String name) {
+ return mBase.moveDatabaseFrom(sourceContext, name);
+ }
+
+ @Override
+ public boolean deleteDatabase(String name) {
+ return mBase.deleteDatabase(name);
+ }
+
+ @Override
+ public File getDatabasePath(String name) {
+ return mBase.getDatabasePath(name);
+ }
+
+ @Override
+ public String[] databaseList() {
+ return mBase.databaseList();
+ }
+
+ @Override
+ @Deprecated
+ public Drawable getWallpaper() {
+ return mBase.getWallpaper();
+ }
+
+ @Override
+ @Deprecated
+ public Drawable peekWallpaper() {
+ return mBase.peekWallpaper();
+ }
+
+ @Override
+ @Deprecated
+ public int getWallpaperDesiredMinimumWidth() {
+ return mBase.getWallpaperDesiredMinimumWidth();
+ }
+
+ @Override
+ @Deprecated
+ public int getWallpaperDesiredMinimumHeight() {
+ return mBase.getWallpaperDesiredMinimumHeight();
+ }
+
+ @Override
+ @Deprecated
+ public void setWallpaper(Bitmap bitmap) throws IOException {
+ mBase.setWallpaper(bitmap);
+ }
+
+ @Override
+ @Deprecated
+ public void setWallpaper(InputStream data) throws IOException {
+ mBase.setWallpaper(data);
+ }
+
+ @Override
+ @Deprecated
+ public void clearWallpaper() throws IOException {
+ mBase.clearWallpaper();
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ mBase.startActivity(intent);
+ }
+
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, UserHandle user) {
+ mBase.startActivityAsUser(intent, user);
+ }
+
+ /** @hide **/
+ public void startActivityForResult(
+ String who, Intent intent, int requestCode, @Nullable Bundle options) {
+ mBase.startActivityForResult(who, intent, requestCode, options);
+ }
+
+ /** @hide **/
+ public boolean canStartActivityForResult() {
+ return mBase.canStartActivityForResult();
+ }
+
+ @Override
+ public void startActivity(Intent intent, @Nullable Bundle options) {
+ mBase.startActivity(intent, options);
+ }
+
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, @Nullable Bundle options, UserHandle user) {
+ mBase.startActivityAsUser(intent, options, user);
+ }
+
+ @Override
+ public void startActivities(Intent[] intents) {
+ mBase.startActivities(intents);
+ }
+
+ @Override
+ public void startActivities(Intent[] intents, @Nullable Bundle options) {
+ mBase.startActivities(intents, options);
+ }
+
+ /** @hide */
+ @Override
+ public int startActivitiesAsUser(Intent[] intents, @Nullable Bundle options,
+ UserHandle userHandle) {
+ return mBase.startActivitiesAsUser(intents, options, userHandle);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags)
+ throws IntentSender.SendIntentException {
+ mBase.startIntentSender(intent, fillInIntent, flagsMask,
+ flagsValues, extraFlags);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, @Nullable Bundle options)
+ throws IntentSender.SendIntentException {
+ mBase.startIntentSender(intent, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ mBase.sendBroadcast(intent);
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, @Nullable String receiverPermission) {
+ mBase.sendBroadcast(intent, receiverPermission);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions) {
+ mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+ mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable Bundle options) {
+ mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, options);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+ String[] receiverPermissions) {
+ mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions);
+ }
+
+ /** @hide */
+ @SystemApi
+ @Override
+ public void sendBroadcast(Intent intent, @Nullable String receiverPermission,
+ @Nullable Bundle options) {
+ mBase.sendBroadcast(intent, receiverPermission, options);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, @Nullable String receiverPermission, int appOp) {
+ mBase.sendBroadcast(intent, receiverPermission, appOp);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent,
+ @Nullable String receiverPermission) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, @Nullable String receiverPermission,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission,
+ resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
+ /** @hide */
+ @SystemApi
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, @Nullable String receiverPermission, @Nullable Bundle options,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission,
+ options, resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, @Nullable String receiverPermission, int appOp,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, appOp,
+ resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ mBase.sendBroadcastAsUser(intent, user);
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission) {
+ mBase.sendBroadcastAsUser(intent, user, receiverPermission);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, @Nullable Bundle options) {
+ mBase.sendBroadcastAsUser(intent, user, receiverPermission, options);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, int appOp) {
+ mBase.sendBroadcastAsUser(intent, user, receiverPermission, appOp);
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, int appOp,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, appOp, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, int appOp, @Nullable Bundle options,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, appOp, options,
+ resultReceiver, scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent,
+ @Nullable String receiverPermission, @Nullable String receiverAppOp,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, receiverAppOp, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent, int initialCode,
+ @Nullable String receiverPermission, @Nullable String receiverAppOp,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ @Nullable String initialData, @Nullable Bundle initialExtras,
+ @Nullable Bundle options) {
+ mBase.sendOrderedBroadcast(intent, initialCode, receiverPermission, receiverAppOp,
+ resultReceiver, scheduler, initialData, initialExtras, options);
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyBroadcast(Intent intent) {
+ mBase.sendStickyBroadcast(intent);
+ }
+
+ /**
+ * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
+ * Intent you are sending stays around after the broadcast is complete,
+ * so that others can quickly retrieve that data through the return
+ * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In
+ * all other ways, this behaves the same as
+ * {@link #sendBroadcast(Intent)}.
+ *
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast, and the Intent will be held to
+ * be re-broadcast to future receivers.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ *
+ * @see #sendBroadcast(Intent)
+ * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ @Override
+ @Deprecated
+ public void sendStickyBroadcast(@NonNull Intent intent, @Nullable Bundle options) {
+ mBase.sendStickyBroadcast(intent, options);
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyOrderedBroadcast(Intent intent,
+ @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ mBase.sendStickyOrderedBroadcast(intent,
+ resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
+ @Override
+ @Deprecated
+ public void removeStickyBroadcast(Intent intent) {
+ mBase.removeStickyBroadcast(intent);
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ mBase.sendStickyBroadcastAsUser(intent, user);
+ }
+
+ /** @hide */
+ @Override
+ @Deprecated
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable Bundle options) {
+ mBase.sendStickyBroadcastAsUser(intent, user, options);
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, @Nullable BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras) {
+ mBase.sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ @Deprecated
+ public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ mBase.removeStickyBroadcastAsUser(intent, user);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+ return mBase.registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ int flags) {
+ return mBase.registerReceiver(receiver, filter, flags);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ @Nullable String broadcastPermission, @Nullable Handler scheduler) {
+ return mBase.registerReceiver(receiver, filter, broadcastPermission,
+ scheduler);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags) {
+ return mBase.registerReceiver(receiver, filter, broadcastPermission,
+ scheduler, flags);
+ }
+
+ /** @hide */
+ @Override
+ @Nullable
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ return mBase.registerReceiverForAllUsers(receiver, filter, broadcastPermission,
+ scheduler);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage
+ public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ return mBase.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBase.unregisterReceiver(receiver);
+ }
+
+ @Override
+ public @Nullable ComponentName startService(Intent service) {
+ return mBase.startService(service);
+ }
+
+ @Override
+ public @Nullable ComponentName startForegroundService(Intent service) {
+ return mBase.startForegroundService(service);
+ }
+
+ @Override
+ public boolean stopService(Intent name) {
+ return mBase.stopService(name);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage
+ public @Nullable ComponentName startServiceAsUser(Intent service, UserHandle user) {
+ return mBase.startServiceAsUser(service, user);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public @Nullable ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
+ return mBase.startForegroundServiceAsUser(service, user);
+ }
+
+ /** @hide */
+ @Override
+ public boolean stopServiceAsUser(Intent name, UserHandle user) {
+ return mBase.stopServiceAsUser(name, user);
+ }
+
+ @Override
+ public boolean bindService(Intent service, ServiceConnection conn,
+ int flags) {
+ return mBase.bindService(service, conn, flags);
+ }
+
+ @Override
+ public boolean bindService(Intent service, int flags, Executor executor,
+ ServiceConnection conn) {
+ return mBase.bindService(service, flags, executor, conn);
+ }
+
+ @Override
+ public boolean bindIsolatedService(Intent service, int flags, String instanceName,
+ Executor executor, ServiceConnection conn) {
+ return mBase.bindIsolatedService(service, flags, instanceName, executor, conn);
+ }
+
+ /** @hide */
+ @Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ UserHandle user) {
+ return mBase.bindServiceAsUser(service, conn, flags, user);
+ }
+
+ /** @hide */
+ @Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ Handler handler, UserHandle user) {
+ return mBase.bindServiceAsUser(service, conn, flags, handler, user);
+ }
+
+ @Override
+ public void updateServiceGroup(ServiceConnection conn, int group, int importance) {
+ mBase.updateServiceGroup(conn, group, importance);
+ }
+
+ @Override
+ public void unbindService(ServiceConnection conn) {
+ mBase.unbindService(conn);
+ }
+
+ @Override
+ public boolean startInstrumentation(ComponentName className,
+ @Nullable String profileFile, @Nullable Bundle arguments) {
+ return mBase.startInstrumentation(className, profileFile, arguments);
+ }
+
+ @Override
+ public @Nullable Object getSystemService(String name) {
+ return mBase.getSystemService(name);
+ }
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ return mBase.getSystemServiceName(serviceClass);
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ return mBase.checkPermission(permission, pid, uid);
+ }
+
+ /** @hide */
+ @Override
+ public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+ return mBase.checkPermission(permission, pid, uid, callerToken);
+ }
+
+ @Override
+ public int checkCallingPermission(String permission) {
+ return mBase.checkCallingPermission(permission);
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return mBase.checkCallingOrSelfPermission(permission);
+ }
+
+ @Override
+ public int checkSelfPermission(String permission) {
+ return mBase.checkSelfPermission(permission);
+ }
+
+ @Override
+ public void enforcePermission(
+ String permission, int pid, int uid, @Nullable String message) {
+ mBase.enforcePermission(permission, pid, uid, message);
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, @Nullable String message) {
+ mBase.enforceCallingPermission(permission, message);
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(
+ String permission, @Nullable String message) {
+ mBase.enforceCallingOrSelfPermission(permission, message);
+ }
+
+ @Override
+ public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
+ mBase.grantUriPermission(toPackage, uri, modeFlags);
+ }
+
+ @Override
+ public void revokeUriPermission(Uri uri, int modeFlags) {
+ mBase.revokeUriPermission(uri, modeFlags);
+ }
+
+ @Override
+ public void revokeUriPermission(String targetPackage, Uri uri, int modeFlags) {
+ mBase.revokeUriPermission(targetPackage, uri, modeFlags);
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ return mBase.checkUriPermission(uri, pid, uid, modeFlags);
+ }
+
+ @NonNull
+ @Override
+ public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+ int modeFlags) {
+ return mBase.checkUriPermissions(uris, pid, uid, modeFlags);
+ }
+
+ /** @hide */
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
+ return mBase.checkUriPermission(uri, pid, uid, modeFlags, callerToken);
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri uri, int modeFlags) {
+ return mBase.checkCallingUriPermission(uri, modeFlags);
+ }
+
+ @NonNull
+ @Override
+ public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+ return mBase.checkCallingUriPermissions(uris, modeFlags);
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+ return mBase.checkCallingOrSelfUriPermission(uri, modeFlags);
+ }
+
+ @NonNull
+ @Override
+ public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+ return mBase.checkCallingOrSelfUriPermissions(uris, modeFlags);
+ }
+
+ @Override
+ public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
+ @Nullable String writePermission, int pid, int uid, int modeFlags) {
+ return mBase.checkUriPermission(uri, readPermission, writePermission,
+ pid, uid, modeFlags);
+ }
+
+ @Override
+ public void enforceUriPermission(
+ Uri uri, int pid, int uid, int modeFlags, String message) {
+ mBase.enforceUriPermission(uri, pid, uid, modeFlags, message);
+ }
+
+ @Override
+ public void enforceCallingUriPermission(
+ Uri uri, int modeFlags, String message) {
+ mBase.enforceCallingUriPermission(uri, modeFlags, message);
+ }
+
+ @Override
+ public void enforceCallingOrSelfUriPermission(
+ Uri uri, int modeFlags, String message) {
+ mBase.enforceCallingOrSelfUriPermission(uri, modeFlags, message);
+ }
+
+ @Override
+ public void enforceUriPermission(
+ @Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission,
+ int pid, int uid, int modeFlags, @Nullable String message) {
+ mBase.enforceUriPermission(
+ uri, readPermission, writePermission, pid, uid, modeFlags,
+ message);
+ }
+
+ @Override
+ public Context createPackageContext(String packageName, int flags)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createPackageContext(packageName, flags);
+ }
+
+ /** @hide */
+ @Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createPackageContextAsUser(packageName, flags, user);
+ }
+
+ /** @hide */
+ @Override
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+ return mBase.createContextAsUser(user, flags);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage(trackingBug = 175981568)
+ public Context createApplicationContext(ApplicationInfo application,
+ int flags) throws PackageManager.NameNotFoundException {
+ return mBase.createApplicationContext(application, flags);
+ }
+
+ /** @hide */
+ @Override
+ public Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createContextForSplit(splitName);
+ }
+
+ /** @hide */
+ @Override
+ public int getUserId() {
+ return mBase.getUserId();
+ }
+
+ /** @hide */
+ @Override
+ public UserHandle getUser() {
+ return mBase.getUser();
+ }
+
+ @Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ return mBase.createConfigurationContext(overrideConfiguration);
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ return mBase.createDisplayContext(display);
+ }
+
+ @Override
+ @NonNull
+ public Context createWindowContext(@WindowType int type, @Nullable Bundle options) {
+ return mBase.createWindowContext(type, options);
+ }
+
+ @Override
+ @NonNull
+ public Context createWindowContext(@NonNull Display display, @WindowType int type,
+ @Nullable Bundle options) {
+ return mBase.createWindowContext(display, type, options);
+ }
+
+ @Override
+ @NonNull
+ public Context createContext(@NonNull ContextParams contextParams) {
+ return mBase.createContext(contextParams);
+ }
+
+ @Override
+ public @NonNull Context createAttributionContext(@Nullable String attributionTag) {
+ return mBase.createAttributionContext(attributionTag);
+ }
+
+ @NonNull
+ @Override
+ public AttributionSource getAttributionSource() {
+ return mBase.getAttributionSource();
+ }
+
+ @Override
+ public boolean isRestricted() {
+ return mBase.isRestricted();
+ }
+
+ /** @hide */
+ @Override
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ return mBase.getDisplayAdjustments(displayId);
+ }
+
+ @Override
+ public @Nullable Display getDisplay() {
+ return mBase.getDisplay();
+ }
+
+ /** @hide */
+ @Override
+ public @Nullable Display getDisplayNoVerify() {
+ return mBase.getDisplayNoVerify();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getDisplayId() {
+ return mBase.getDisplayId();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateDisplay(int displayId) {
+ mBase.updateDisplay(displayId);
+ }
+
+ @Override
+ public Context createDeviceProtectedStorageContext() {
+ return mBase.createDeviceProtectedStorageContext();
+ }
+
+ /** {@hide} */
+ @SystemApi
+ @Override
+ public Context createCredentialProtectedStorageContext() {
+ return mBase.createCredentialProtectedStorageContext();
+ }
+
+ /** @hide */
+ @UiContext
+ @NonNull
+ @Override
+ public Context createTokenContext(@NonNull IBinder token, @NonNull Display display) {
+ return mBase.createTokenContext(token, display);
+ }
+
+ @Override
+ public boolean isDeviceProtectedStorage() {
+ return mBase.isDeviceProtectedStorage();
+ }
+
+ /** {@hide} */
+ @SystemApi
+ @Override
+ public boolean isCredentialProtectedStorage() {
+ return mBase.isCredentialProtectedStorage();
+ }
+
+ /** {@hide} */
+ @Override
+ public boolean canLoadUnsafeResources() {
+ return mBase.canLoadUnsafeResources();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public @Nullable IBinder getActivityToken() {
+ return mBase.getActivityToken();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public @Nullable IBinder getWindowContextToken() {
+ return mBase != null ? mBase.getWindowContextToken() : null;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public @Nullable IServiceConnection getServiceDispatcher(ServiceConnection conn,
+ Handler handler, int flags) {
+ return mBase.getServiceDispatcher(conn, handler, flags);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public IApplicationThread getIApplicationThread() {
+ return mBase.getIApplicationThread();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public Handler getMainThreadHandler() {
+ return mBase.getMainThreadHandler();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getNextAutofillId() {
+ return mBase.getNextAutofillId();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public AutofillClient getAutofillClient() {
+ return mBase.getAutofillClient();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setAutofillClient(AutofillClient client) {
+ mBase.setAutofillClient(client);
+ }
+
+ /** @hide */
+ @Override
+ public AutofillOptions getAutofillOptions() {
+ return mBase == null ? null : mBase.getAutofillOptions();
+ }
+
+ /** @hide */
+ @Override
+ public void setAutofillOptions(AutofillOptions options) {
+ if (mBase != null) {
+ mBase.setAutofillOptions(options);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public @Nullable ContentCaptureOptions getContentCaptureOptions() {
+ return mBase == null ? null : mBase.getContentCaptureOptions();
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @Override
+ public void setContentCaptureOptions(@Nullable ContentCaptureOptions options) {
+ if (mBase != null) {
+ mBase.setContentCaptureOptions(options);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isUiContext() {
+ if (mBase == null) {
+ return false;
+ }
+ return mBase.isUiContext();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isConfigurationContext() {
+ if (mBase == null) {
+ return false;
+ }
+ return mBase.isConfigurationContext();
+ }
+}
diff --git a/android/content/CursorEntityIterator.java b/android/content/CursorEntityIterator.java
new file mode 100644
index 0000000..952366d
--- /dev/null
+++ b/android/content/CursorEntityIterator.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2009 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.compat.annotation.UnsupportedAppUsage;
+import android.database.Cursor;
+import android.os.RemoteException;
+
+/**
+ * Abstract implementation of EntityIterator that makes it easy to wrap a cursor
+ * that can contain several consecutive rows for an entity.
+ * @hide
+ */
+public abstract class CursorEntityIterator implements EntityIterator {
+ private final Cursor mCursor;
+ private boolean mIsClosed;
+
+ /**
+ * Constructor that makes initializes the cursor such that the iterator points to the
+ * first Entity, if there are any.
+ * @param cursor the cursor that contains the rows that make up the entities
+ */
+ @UnsupportedAppUsage
+ public CursorEntityIterator(Cursor cursor) {
+ mIsClosed = false;
+ mCursor = cursor;
+ mCursor.moveToFirst();
+ }
+
+ /**
+ * Returns the entity that the cursor is currently pointing to. This must take care to advance
+ * the cursor past this entity. This will never be called if the cursor is at the end.
+ * @param cursor the cursor that contains the entity rows
+ * @return the entity that the cursor is currently pointing to
+ * @throws RemoteException if a RemoteException is caught while attempting to build the Entity
+ */
+ public abstract Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException;
+
+ /**
+ * Returns whether there are more elements to iterate, i.e. whether the
+ * iterator is positioned in front of an element.
+ *
+ * @return {@code true} if there are more elements, {@code false} otherwise.
+ * @see EntityIterator#next()
+ */
+ public final boolean hasNext() {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling hasNext() when the iterator is closed");
+ }
+
+ return !mCursor.isAfterLast();
+ }
+
+ /**
+ * Returns the next object in the iteration, i.e. returns the element in
+ * front of the iterator and advances the iterator by one position.
+ *
+ * @return the next object.
+ * @throws java.util.NoSuchElementException
+ * if there are no more elements.
+ * @see EntityIterator#hasNext()
+ */
+ public Entity next() {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling next() when the iterator is closed");
+ }
+ if (!hasNext()) {
+ throw new IllegalStateException("you may only call next() if hasNext() is true");
+ }
+
+ try {
+ return getEntityAndIncrementCursor(mCursor);
+ } catch (RemoteException e) {
+ throw new RuntimeException("caught a remote exception, this process will die soon", e);
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove not supported by EntityIterators");
+ }
+
+ public final void reset() {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling reset() when the iterator is closed");
+ }
+ mCursor.moveToFirst();
+ }
+
+ /**
+ * Indicates that this iterator is no longer needed and that any associated resources
+ * may be released (such as a SQLite cursor).
+ */
+ public final void close() {
+ if (mIsClosed) {
+ throw new IllegalStateException("closing when already closed");
+ }
+ mIsClosed = true;
+ mCursor.close();
+ }
+}
diff --git a/android/content/CursorLoader.java b/android/content/CursorLoader.java
new file mode 100644
index 0000000..fda646c
--- /dev/null
+++ b/android/content/CursorLoader.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2010 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.compat.annotation.UnsupportedAppUsage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * A loader that queries the {@link ContentResolver} and returns a {@link Cursor}.
+ * This class implements the {@link Loader} protocol in a standard way for
+ * querying cursors, building on {@link AsyncTaskLoader} to perform the cursor
+ * query on a background thread so that it does not block the application's UI.
+ *
+ * <p>A CursorLoader must be built with the full information for the query to
+ * perform, either through the
+ * {@link #CursorLoader(Context, Uri, String[], String, String[], String)} or
+ * creating an empty instance with {@link #CursorLoader(Context)} and filling
+ * in the desired parameters with {@link #setUri(Uri)}, {@link #setSelection(String)},
+ * {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
+ * and {@link #setProjection(String[])}.
+ *
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.CursorLoader}
+ */
+@Deprecated
+public class CursorLoader extends AsyncTaskLoader<Cursor> {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final ForceLoadContentObserver mObserver;
+
+ Uri mUri;
+ String[] mProjection;
+ String mSelection;
+ String[] mSelectionArgs;
+ String mSortOrder;
+
+ Cursor mCursor;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ CancellationSignal mCancellationSignal;
+
+ /* Runs on a worker thread */
+ @Override
+ public Cursor loadInBackground() {
+ synchronized (this) {
+ if (isLoadInBackgroundCanceled()) {
+ throw new OperationCanceledException();
+ }
+ mCancellationSignal = new CancellationSignal();
+ }
+ try {
+ Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
+ mSelectionArgs, mSortOrder, mCancellationSignal);
+ if (cursor != null) {
+ try {
+ // Ensure the cursor window is filled.
+ cursor.getCount();
+ cursor.registerContentObserver(mObserver);
+ } catch (RuntimeException ex) {
+ cursor.close();
+ throw ex;
+ }
+ }
+ return cursor;
+ } finally {
+ synchronized (this) {
+ mCancellationSignal = null;
+ }
+ }
+ }
+
+ @Override
+ public void cancelLoadInBackground() {
+ super.cancelLoadInBackground();
+
+ synchronized (this) {
+ if (mCancellationSignal != null) {
+ mCancellationSignal.cancel();
+ }
+ }
+ }
+
+ /* Runs on the UI thread */
+ @Override
+ public void deliverResult(Cursor cursor) {
+ if (isReset()) {
+ // An async query came in while the loader is stopped
+ if (cursor != null) {
+ cursor.close();
+ }
+ return;
+ }
+ Cursor oldCursor = mCursor;
+ mCursor = cursor;
+
+ if (isStarted()) {
+ super.deliverResult(cursor);
+ }
+
+ if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
+ oldCursor.close();
+ }
+ }
+
+ /**
+ * Creates an empty unspecified CursorLoader. You must follow this with
+ * calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc
+ * to specify the query to perform.
+ */
+ public CursorLoader(Context context) {
+ super(context);
+ mObserver = new ForceLoadContentObserver();
+ }
+
+ /**
+ * Creates a fully-specified CursorLoader. See
+ * {@link ContentResolver#query(Uri, String[], String, String[], String)
+ * ContentResolver.query()} for documentation on the meaning of the
+ * parameters. These will be passed as-is to that call.
+ */
+ public CursorLoader(Context context, Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ super(context);
+ mObserver = new ForceLoadContentObserver();
+ mUri = uri;
+ mProjection = projection;
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ mSortOrder = sortOrder;
+ }
+
+ /**
+ * Starts an asynchronous load of the data. When the result is ready the callbacks
+ * will be called on the UI thread. If a previous load has been completed and is still valid
+ * the result may be passed to the callbacks immediately.
+ *
+ * Must be called from the UI thread
+ */
+ @Override
+ protected void onStartLoading() {
+ if (mCursor != null) {
+ deliverResult(mCursor);
+ }
+ if (takeContentChanged() || mCursor == null) {
+ forceLoad();
+ }
+ }
+
+ /**
+ * Must be called from the UI thread
+ */
+ @Override
+ protected void onStopLoading() {
+ // Attempt to cancel the current load task if possible.
+ cancelLoad();
+ }
+
+ @Override
+ public void onCanceled(Cursor cursor) {
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ if (mCursor != null && !mCursor.isClosed()) {
+ mCursor.close();
+ }
+ mCursor = null;
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ public void setUri(Uri uri) {
+ mUri = uri;
+ }
+
+ public String[] getProjection() {
+ return mProjection;
+ }
+
+ public void setProjection(String[] projection) {
+ mProjection = projection;
+ }
+
+ public String getSelection() {
+ return mSelection;
+ }
+
+ public void setSelection(String selection) {
+ mSelection = selection;
+ }
+
+ public String[] getSelectionArgs() {
+ return mSelectionArgs;
+ }
+
+ public void setSelectionArgs(String[] selectionArgs) {
+ mSelectionArgs = selectionArgs;
+ }
+
+ public String getSortOrder() {
+ return mSortOrder;
+ }
+
+ public void setSortOrder(String sortOrder) {
+ mSortOrder = sortOrder;
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ writer.print(prefix); writer.print("mUri="); writer.println(mUri);
+ writer.print(prefix); writer.print("mProjection=");
+ writer.println(Arrays.toString(mProjection));
+ writer.print(prefix); writer.print("mSelection="); writer.println(mSelection);
+ writer.print(prefix); writer.print("mSelectionArgs=");
+ writer.println(Arrays.toString(mSelectionArgs));
+ writer.print(prefix); writer.print("mSortOrder="); writer.println(mSortOrder);
+ writer.print(prefix); writer.print("mCursor="); writer.println(mCursor);
+ writer.print(prefix); writer.print("mContentChanged="); writer.println(mContentChanged);
+ }
+}
diff --git a/android/content/DefaultDataHandler.java b/android/content/DefaultDataHandler.java
new file mode 100644
index 0000000..863c9f6
--- /dev/null
+++ b/android/content/DefaultDataHandler.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2008 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.net.Uri;
+import android.util.Xml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Stack;
+
+/**
+ * Inserts default data from InputStream, should be in XML format.
+ * If the provider syncs data to the server, the imported data will be synced to the server.
+ * <p>Samples:</p>
+ * <br/>
+ * Insert one row:
+ * <pre>
+ * <row uri="content://contacts/people">
+ * <Col column = "name" value = "foo feebe "/>
+ * <Col column = "addr" value = "Tx"/>
+ * </row></pre>
+ * <br/>
+ * Delete, it must be in order of uri, select, and arg:
+ * <pre>
+ * <del uri="content://contacts/people" select="name=? and addr=?"
+ * arg1 = "foo feebe" arg2 ="Tx"/></pre>
+ * <br/>
+ * Use first row's uri to insert into another table,
+ * content://contacts/people/1/phones:
+ * <pre>
+ * <row uri="content://contacts/people">
+ * <col column = "name" value = "foo feebe"/>
+ * <col column = "addr" value = "Tx"/>
+ * <row postfix="phones">
+ * <col column="number" value="512-514-6535"/>
+ * </row>
+ * <row postfix="phones">
+ * <col column="cell" value="512-514-6535"/>
+ * </row>
+ * </row></pre>
+ * <br/>
+ * Insert multiple rows in to same table and same attributes:
+ * <pre>
+ * <row uri="content://contacts/people" >
+ * <row>
+ * <col column= "name" value = "foo feebe"/>
+ * <col column= "addr" value = "Tx"/>
+ * </row>
+ * <row>
+ * </row>
+ * </row></pre>
+ *
+ * @hide
+ */
+public class DefaultDataHandler implements ContentInsertHandler {
+ private final static String ROW = "row";
+ private final static String COL = "col";
+ private final static String URI_STR = "uri";
+ private final static String POSTFIX = "postfix";
+ private final static String DEL = "del";
+ private final static String SELECT = "select";
+ private final static String ARG = "arg";
+
+ private Stack<Uri> mUris = new Stack<Uri>();
+ private ContentValues mValues;
+ private ContentResolver mContentResolver;
+
+ public void insert(ContentResolver contentResolver, InputStream in)
+ throws IOException, SAXException {
+ mContentResolver = contentResolver;
+ Xml.parse(in, Xml.Encoding.UTF_8, this);
+ }
+
+ public void insert(ContentResolver contentResolver, String in)
+ throws SAXException {
+ mContentResolver = contentResolver;
+ Xml.parse(in, this);
+ }
+
+ private void parseRow(Attributes atts) throws SAXException {
+ String uriStr = atts.getValue(URI_STR);
+ Uri uri;
+ if (uriStr != null) {
+ // case 1
+ uri = Uri.parse(uriStr);
+ if (uri == null) {
+ throw new SAXException("attribute " +
+ atts.getValue(URI_STR) + " parsing failure");
+ }
+
+ } else if (mUris.size() > 0){
+ // case 2
+ String postfix = atts.getValue(POSTFIX);
+ if (postfix != null) {
+ uri = Uri.withAppendedPath(mUris.lastElement(),
+ postfix);
+ } else {
+ uri = mUris.lastElement();
+ }
+ } else {
+ throw new SAXException("attribute parsing failure");
+ }
+
+ mUris.push(uri);
+
+ }
+
+ private Uri insertRow() {
+ Uri u = mContentResolver.insert(mUris.lastElement(), mValues);
+ mValues = null;
+ return u;
+ }
+
+ public void startElement(String uri, String localName, String name,
+ Attributes atts) throws SAXException {
+ if (ROW.equals(localName)) {
+ if (mValues != null) {
+ // case 2, <Col> before <Row> insert last uri
+ if (mUris.empty()) {
+ throw new SAXException("uri is empty");
+ }
+ Uri nextUri = insertRow();
+ if (nextUri == null) {
+ throw new SAXException("insert to uri " +
+ mUris.lastElement().toString() + " failure");
+ } else {
+ // make sure the stack lastElement save uri for more than one row
+ mUris.pop();
+ mUris.push(nextUri);
+ parseRow(atts);
+ }
+ } else {
+ int attrLen = atts.getLength();
+ if (attrLen == 0) {
+ // case 3, share same uri as last level
+ mUris.push(mUris.lastElement());
+ } else {
+ parseRow(atts);
+ }
+ }
+ } else if (COL.equals(localName)) {
+ int attrLen = atts.getLength();
+ if (attrLen != 2) {
+ throw new SAXException("illegal attributes number " + attrLen);
+ }
+ String key = atts.getValue(0);
+ String value = atts.getValue(1);
+ if (key != null && key.length() > 0 && value != null && value.length() > 0) {
+ if (mValues == null) {
+ mValues = new ContentValues();
+ }
+ mValues.put(key, value);
+ } else {
+ throw new SAXException("illegal attributes value");
+ }
+ } else if (DEL.equals(localName)){
+ Uri u = Uri.parse(atts.getValue(URI_STR));
+ if (u == null) {
+ throw new SAXException("attribute " +
+ atts.getValue(URI_STR) + " parsing failure");
+ }
+ int attrLen = atts.getLength() - 2;
+ if (attrLen > 0) {
+ String[] selectionArgs = new String[attrLen];
+ for (int i = 0; i < attrLen; i++) {
+ selectionArgs[i] = atts.getValue(i+2);
+ }
+ mContentResolver.delete(u, atts.getValue(1), selectionArgs);
+ } else if (attrLen == 0){
+ mContentResolver.delete(u, atts.getValue(1), null);
+ } else {
+ mContentResolver.delete(u, null, null);
+ }
+
+ } else {
+ throw new SAXException("unknown element: " + localName);
+ }
+ }
+
+ public void endElement(String uri, String localName, String name)
+ throws SAXException {
+ if (ROW.equals(localName)) {
+ if (mUris.empty()) {
+ throw new SAXException("uri mismatch");
+ }
+ if (mValues != null) {
+ insertRow();
+ }
+ mUris.pop();
+ }
+ }
+
+
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void endDocument() throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void endPrefixMapping(String prefix) throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void processingInstruction(String target, String data)
+ throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void startDocument() throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/android/content/DialogInterface.java b/android/content/DialogInterface.java
new file mode 100644
index 0000000..511f356
--- /dev/null
+++ b/android/content/DialogInterface.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2006 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.view.KeyEvent;
+
+/**
+ * Interface that defines a dialog-type class that can be shown, dismissed, or
+ * canceled, and may have buttons that can be clicked.
+ */
+public interface DialogInterface {
+ /** The identifier for the positive button. */
+ int BUTTON_POSITIVE = -1;
+
+ /** The identifier for the negative button. */
+ int BUTTON_NEGATIVE = -2;
+
+ /** The identifier for the neutral button. */
+ int BUTTON_NEUTRAL = -3;
+
+ /** @deprecated Use {@link #BUTTON_POSITIVE} */
+ @Deprecated
+ int BUTTON1 = BUTTON_POSITIVE;
+
+ /** @deprecated Use {@link #BUTTON_NEGATIVE} */
+ @Deprecated
+ int BUTTON2 = BUTTON_NEGATIVE;
+
+ /** @deprecated Use {@link #BUTTON_NEUTRAL} */
+ @Deprecated
+ int BUTTON3 = BUTTON_NEUTRAL;
+
+ /**
+ * Cancels the dialog, invoking the {@link OnCancelListener}.
+ * <p>
+ * The {@link OnDismissListener} may also be called if cancellation
+ * dismisses the dialog.
+ */
+ void cancel();
+
+ /**
+ * Dismisses the dialog, invoking the {@link OnDismissListener}.
+ */
+ void dismiss();
+
+ /**
+ * Interface used to allow the creator of a dialog to run some code when the
+ * dialog is canceled.
+ * <p>
+ * This will only be called when the dialog is canceled, if the creator
+ * needs to know when it is dismissed in general, use
+ * {@link DialogInterface.OnDismissListener}.
+ */
+ interface OnCancelListener {
+ /**
+ * This method will be invoked when the dialog is canceled.
+ *
+ * @param dialog the dialog that was canceled will be passed into the
+ * method
+ */
+ void onCancel(DialogInterface dialog);
+ }
+
+ /**
+ * Interface used to allow the creator of a dialog to run some code when the
+ * dialog is dismissed.
+ */
+ interface OnDismissListener {
+ /**
+ * This method will be invoked when the dialog is dismissed.
+ *
+ * @param dialog the dialog that was dismissed will be passed into the
+ * method
+ */
+ void onDismiss(DialogInterface dialog);
+ }
+
+ /**
+ * Interface used to allow the creator of a dialog to run some code when the
+ * dialog is shown.
+ */
+ interface OnShowListener {
+ /**
+ * This method will be invoked when the dialog is shown.
+ *
+ * @param dialog the dialog that was shown will be passed into the
+ * method
+ */
+ void onShow(DialogInterface dialog);
+ }
+
+ /**
+ * Interface used to allow the creator of a dialog to run some code when an
+ * item on the dialog is clicked.
+ */
+ interface OnClickListener {
+ /**
+ * This method will be invoked when a button in the dialog is clicked.
+ *
+ * @param dialog the dialog that received the click
+ * @param which the button that was clicked (ex.
+ * {@link DialogInterface#BUTTON_POSITIVE}) or the position
+ * of the item clicked
+ */
+ void onClick(DialogInterface dialog, int which);
+ }
+
+ /**
+ * Interface used to allow the creator of a dialog to run some code when an
+ * item in a multi-choice dialog is clicked.
+ */
+ interface OnMultiChoiceClickListener {
+ /**
+ * This method will be invoked when an item in the dialog is clicked.
+ *
+ * @param dialog the dialog where the selection was made
+ * @param which the position of the item in the list that was clicked
+ * @param isChecked {@code true} if the click checked the item, else
+ * {@code false}
+ */
+ void onClick(DialogInterface dialog, int which, boolean isChecked);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a key event is
+ * dispatched to this dialog. The callback will be invoked before the key
+ * event is given to the dialog.
+ */
+ interface OnKeyListener {
+ /**
+ * Called when a key is dispatched to a dialog. This allows listeners to
+ * get a chance to respond before the dialog.
+ *
+ * @param dialog the dialog the key has been dispatched to
+ * @param keyCode the code for the physical key that was pressed
+ * @param event the KeyEvent object containing full information about
+ * the event
+ * @return {@code true} if the listener has consumed the event,
+ * {@code false} otherwise
+ */
+ boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event);
+ }
+}
diff --git a/android/content/Entity.java b/android/content/Entity.java
new file mode 100644
index 0000000..13137c4
--- /dev/null
+++ b/android/content/Entity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 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.compat.annotation.UnsupportedAppUsage;
+import android.net.Uri;
+import android.os.Build;
+
+import java.util.ArrayList;
+
+/**
+ * A representation of a item using ContentValues. It contains one top level ContentValue
+ * plus a collection of Uri, ContentValues tuples as subvalues. One example of its use
+ * is in Contacts, where the top level ContentValue contains the columns from the RawContacts
+ * table and the subvalues contain a ContentValues object for each row from the Data table that
+ * corresponds to that RawContact. The uri refers to the Data table uri for each row.
+ */
+public final class Entity {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final private ContentValues mValues;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final private ArrayList<NamedContentValues> mSubValues;
+
+ public Entity(ContentValues values) {
+ mValues = values;
+ mSubValues = new ArrayList<NamedContentValues>();
+ }
+
+ public ContentValues getEntityValues() {
+ return mValues;
+ }
+
+ public ArrayList<NamedContentValues> getSubValues() {
+ return mSubValues;
+ }
+
+ public void addSubValue(Uri uri, ContentValues values) {
+ mSubValues.add(new Entity.NamedContentValues(uri, values));
+ }
+
+ public static class NamedContentValues {
+ public final Uri uri;
+ public final ContentValues values;
+
+ public NamedContentValues(Uri uri, ContentValues values) {
+ this.uri = uri;
+ this.values = values;
+ }
+ }
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Entity: ").append(getEntityValues());
+ for (Entity.NamedContentValues namedValue : getSubValues()) {
+ sb.append("\n ").append(namedValue.uri);
+ sb.append("\n -> ").append(namedValue.values);
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/content/EntityIterator.java b/android/content/EntityIterator.java
new file mode 100644
index 0000000..55c47ba
--- /dev/null
+++ b/android/content/EntityIterator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2009 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 java.util.Iterator;
+
+/**
+ * A specialization of {@link Iterator} that allows iterating over a collection of
+ * {@link Entity} objects. In addition to the iteration functionality it also allows
+ * resetting the iterator back to the beginning and provides for an explicit {@link #close()}
+ * method to indicate that the iterator is no longer needed and that its resources
+ * can be released.
+ */
+public interface EntityIterator extends Iterator<Entity> {
+ /**
+ * Reset the iterator back to the beginning.
+ */
+ public void reset();
+
+ /**
+ * Indicates that this iterator is no longer needed and that any associated resources
+ * may be released (such as a SQLite cursor).
+ */
+ public void close();
+}
diff --git a/android/content/IContentProvider.java b/android/content/IContentProvider.java
new file mode 100644
index 0000000..e0315a3
--- /dev/null
+++ b/android/content/IContentProvider.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2006 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.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * The ipc interface to talk to a content provider.
+ * @hide
+ */
+public interface IContentProvider extends IInterface {
+ Cursor query(@NonNull AttributionSource attributionSource, Uri url,
+ @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
+ throws RemoteException;
+ String getType(Uri url) throws RemoteException;
+
+ /**
+ * A oneway version of getType. The functionality is exactly the same, except that the
+ * call returns immediately, and the resulting type is returned when available via
+ * a binder callback.
+ */
+ void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException;
+
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "ContentProviderClient#insert(android.net.Uri, android.content.ContentValues)} "
+ + "instead")
+ default Uri insert(String callingPkg, Uri url, ContentValues initialValues)
+ throws RemoteException {
+ return insert(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, initialValues, null);
+ }
+ Uri insert(@NonNull AttributionSource attributionSource, Uri url,
+ ContentValues initialValues, Bundle extras) throws RemoteException;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "ContentProviderClient#bulkInsert(android.net.Uri, android.content.ContentValues[])"
+ + "} instead")
+ default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
+ throws RemoteException {
+ return bulkInsert(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, initialValues);
+ }
+ int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
+ ContentValues[] initialValues) throws RemoteException;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "ContentProviderClient#delete(android.net.Uri, java.lang.String, java.lang"
+ + ".String[])} instead")
+ default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+ throws RemoteException {
+ return delete(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ }
+ int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras)
+ throws RemoteException;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "ContentProviderClient#update(android.net.Uri, android.content.ContentValues, java"
+ + ".lang.String, java.lang.String[])} instead")
+ default int update(String callingPkg, Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException {
+ return update(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ url, values, ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ }
+ int update(@NonNull AttributionSource attributionSource, Uri url, ContentValues values,
+ Bundle extras) throws RemoteException;
+
+ ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException;
+
+ AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException;
+
+ ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
+ String authority, ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException;
+
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "ContentProviderClient#call(java.lang.String, java.lang.String, android.os.Bundle)} "
+ + "instead")
+ public default Bundle call(String callingPkg, String method,
+ @Nullable String arg, @Nullable Bundle extras) throws RemoteException {
+ return call(new AttributionSource(Binder.getCallingUid(), callingPkg, null),
+ "unknown", method, arg, extras);
+ }
+
+ Bundle call(@NonNull AttributionSource attributionSource, String authority,
+ String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
+
+ int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
+ int uid, int modeFlags) throws RemoteException;
+
+ ICancellationSignal createCancellationSignal() throws RemoteException;
+
+ Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri)
+ throws RemoteException;
+
+ /**
+ * A oneway version of canonicalize. The functionality is exactly the same, except that the
+ * call returns immediately, and the resulting type is returned when available via
+ * a binder callback.
+ */
+ void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
+ RemoteCallback callback) throws RemoteException;
+
+ Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri)
+ throws RemoteException;
+
+ /**
+ * A oneway version of uncanonicalize. The functionality is exactly the same, except that the
+ * call returns immediately, and the resulting type is returned when available via
+ * a binder callback.
+ */
+ void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri,
+ RemoteCallback callback) throws RemoteException;
+
+ public boolean refresh(@NonNull AttributionSource attributionSource, Uri url,
+ @Nullable Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException;
+
+ // Data interchange.
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+
+ public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource,
+ Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException;
+
+ /* IPC constants */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ static final String descriptor = "android.content.IContentProvider";
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ static final int QUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+ static final int GET_TYPE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+ static final int INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+ static final int DELETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+ static final int UPDATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
+ static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
+ static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
+ static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
+ static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
+ static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
+ static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
+ static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
+ static final int CREATE_CANCELATION_SIGNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 23;
+ static final int CANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 24;
+ static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25;
+ static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26;
+ static final int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 27;
+ int GET_TYPE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 28;
+ int CANONICALIZE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 29;
+ int UNCANONICALIZE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 30;
+}
diff --git a/android/content/Intent.java b/android/content/Intent.java
new file mode 100644
index 0000000..9e35a32
--- /dev/null
+++ b/android/content/Intent.java
@@ -0,0 +1,11732 @@
+/*
+ * Copyright (C) 2006 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 static android.content.ContentProvider.maybeAddUserId;
+
+import android.accessibilityservice.AccessibilityService;
+import android.annotation.AnyRes;
+import android.annotation.BroadcastBehavior;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.AppGlobals;
+import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IncidentManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
+import android.os.StrictMode;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.provider.ContactsContract.QuickContact;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * An intent is an abstract description of an operation to be performed. It
+ * can be used with {@link Context#startActivity(Intent) startActivity} to
+ * launch an {@link android.app.Activity},
+ * {@link android.content.Context#sendBroadcast(Intent) broadcastIntent} to
+ * send it to any interested {@link BroadcastReceiver BroadcastReceiver} components,
+ * and {@link android.content.Context#startService} or
+ * {@link android.content.Context#bindService} to communicate with a
+ * background {@link android.app.Service}.
+ *
+ * <p>An Intent provides a facility for performing late runtime binding between the code in
+ * different applications. Its most significant use is in the launching of activities, where it
+ * can be thought of as the glue between activities. It is basically a passive data structure
+ * holding an abstract description of an action to be performed.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to create and resolve intents, read the
+ * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <a name="IntentStructure"></a>
+ * <h3>Intent Structure</h3>
+ * <p>The primary pieces of information in an intent are:</p>
+ *
+ * <ul>
+ * <li> <p><b>action</b> -- The general action to be performed, such as
+ * {@link #ACTION_VIEW}, {@link #ACTION_EDIT}, {@link #ACTION_MAIN},
+ * etc.</p>
+ * </li>
+ * <li> <p><b>data</b> -- The data to operate on, such as a person record
+ * in the contacts database, expressed as a {@link android.net.Uri}.</p>
+ * </li>
+ * </ul>
+ *
+ *
+ * <p>Some examples of action/data pairs are:</p>
+ *
+ * <ul>
+ * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/people/1</i></b> -- Display
+ * information about the person whose identifier is "1".</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_DIAL} <i>content://contacts/people/1</i></b> -- Display
+ * the phone dialer with the person filled in.</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_VIEW} <i>tel:123</i></b> -- Display
+ * the phone dialer with the given number filled in. Note how the
+ * VIEW action does what is considered the most reasonable thing for
+ * a particular URI.</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_DIAL} <i>tel:123</i></b> -- Display
+ * the phone dialer with the given number filled in.</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_EDIT} <i>content://contacts/people/1</i></b> -- Edit
+ * information about the person whose identifier is "1".</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/people/</i></b> -- Display
+ * a list of people, which the user can browse through. This example is a
+ * typical top-level entry into the Contacts application, showing you the
+ * list of people. Selecting a particular person to view would result in a
+ * new intent { <b>{@link #ACTION_VIEW} <i>content://contacts/people/N</i></b> }
+ * being used to start an activity to display that person.</p>
+ * </li>
+ * </ul>
+ *
+ * <p>In addition to these primary attributes, there are a number of secondary
+ * attributes that you can also include with an intent:</p>
+ *
+ * <ul>
+ * <li> <p><b>category</b> -- Gives additional information about the action
+ * to execute. For example, {@link #CATEGORY_LAUNCHER} means it should
+ * appear in the Launcher as a top-level application, while
+ * {@link #CATEGORY_ALTERNATIVE} means it should be included in a list
+ * of alternative actions the user can perform on a piece of data.</p>
+ * <li> <p><b>type</b> -- Specifies an explicit type (a MIME type) of the
+ * intent data. Normally the type is inferred from the data itself.
+ * By setting this attribute, you disable that evaluation and force
+ * an explicit type.</p>
+ * <li> <p><b>component</b> -- Specifies an explicit name of a component
+ * class to use for the intent. Normally this is determined by looking
+ * at the other information in the intent (the action, data/type, and
+ * categories) and matching that with a component that can handle it.
+ * If this attribute is set then none of the evaluation is performed,
+ * and this component is used exactly as is. By specifying this attribute,
+ * all of the other Intent attributes become optional.</p>
+ * <li> <p><b>extras</b> -- This is a {@link Bundle} of any additional information.
+ * This can be used to provide extended information to the component.
+ * For example, if we have a action to send an e-mail message, we could
+ * also include extra pieces of data here to supply a subject, body,
+ * etc.</p>
+ * </ul>
+ *
+ * <p>Here are some examples of other operations you can specify as intents
+ * using these additional parameters:</p>
+ *
+ * <ul>
+ * <li> <p><b>{@link #ACTION_MAIN} with category {@link #CATEGORY_HOME}</b> --
+ * Launch the home screen.</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_GET_CONTENT} with MIME type
+ * <i>{@link android.provider.Contacts.Phones#CONTENT_URI
+ * vnd.android.cursor.item/phone}</i></b>
+ * -- Display the list of people's phone numbers, allowing the user to
+ * browse through them and pick one and return it to the parent activity.</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_GET_CONTENT} with MIME type
+ * <i>*{@literal /}*</i> and category {@link #CATEGORY_OPENABLE}</b>
+ * -- Display all pickers for data that can be opened with
+ * {@link ContentResolver#openInputStream(Uri) ContentResolver.openInputStream()},
+ * allowing the user to pick one of them and then some data inside of it
+ * and returning the resulting URI to the caller. This can be used,
+ * for example, in an e-mail application to allow the user to pick some
+ * data to include as an attachment.</p>
+ * </li>
+ * </ul>
+ *
+ * <p>There are a variety of standard Intent action and category constants
+ * defined in the Intent class, but applications can also define their own.
+ * These strings use Java-style scoping, to ensure they are unique -- for
+ * example, the standard {@link #ACTION_VIEW} is called
+ * "android.intent.action.VIEW".</p>
+ *
+ * <p>Put together, the set of actions, data types, categories, and extra data
+ * defines a language for the system allowing for the expression of phrases
+ * such as "call john smith's cell". As applications are added to the system,
+ * they can extend this language by adding new actions, types, and categories, or
+ * they can modify the behavior of existing phrases by supplying their own
+ * activities that handle them.</p>
+ *
+ * <a name="IntentResolution"></a>
+ * <h3>Intent Resolution</h3>
+ *
+ * <p>There are two primary forms of intents you will use.
+ *
+ * <ul>
+ * <li> <p><b>Explicit Intents</b> have specified a component (via
+ * {@link #setComponent} or {@link #setClass}), which provides the exact
+ * class to be run. Often these will not include any other information,
+ * simply being a way for an application to launch various internal
+ * activities it has as the user interacts with the application.
+ *
+ * <li> <p><b>Implicit Intents</b> have not specified a component;
+ * instead, they must include enough information for the system to
+ * determine which of the available components is best to run for that
+ * intent.
+ * </ul>
+ *
+ * <p>When using implicit intents, given such an arbitrary intent we need to
+ * know what to do with it. This is handled by the process of <em>Intent
+ * resolution</em>, which maps an Intent to an {@link android.app.Activity},
+ * {@link BroadcastReceiver}, or {@link android.app.Service} (or sometimes two or
+ * more activities/receivers) that can handle it.</p>
+ *
+ * <p>The intent resolution mechanism basically revolves around matching an
+ * Intent against all of the <intent-filter> descriptions in the
+ * installed application packages. (Plus, in the case of broadcasts, any {@link BroadcastReceiver}
+ * objects explicitly registered with {@link Context#registerReceiver}.) More
+ * details on this can be found in the documentation on the {@link
+ * IntentFilter} class.</p>
+ *
+ * <p>There are three pieces of information in the Intent that are used for
+ * resolution: the action, type, and category. Using this information, a query
+ * is done on the {@link PackageManager} for a component that can handle the
+ * intent. The appropriate component is determined based on the intent
+ * information supplied in the <code>AndroidManifest.xml</code> file as
+ * follows:</p>
+ *
+ * <ul>
+ * <li> <p>The <b>action</b>, if given, must be listed by the component as
+ * one it handles.</p>
+ * <li> <p>The <b>type</b> is retrieved from the Intent's data, if not
+ * already supplied in the Intent. Like the action, if a type is
+ * included in the intent (either explicitly or implicitly in its
+ * data), then this must be listed by the component as one it handles.</p>
+ * <li> For data that is not a <code>content:</code> URI and where no explicit
+ * type is included in the Intent, instead the <b>scheme</b> of the
+ * intent data (such as <code>http:</code> or <code>mailto:</code>) is
+ * considered. Again like the action, if we are matching a scheme it
+ * must be listed by the component as one it can handle.
+ * <li> <p>The <b>categories</b>, if supplied, must <em>all</em> be listed
+ * by the activity as categories it handles. That is, if you include
+ * the categories {@link #CATEGORY_LAUNCHER} and
+ * {@link #CATEGORY_ALTERNATIVE}, then you will only resolve to components
+ * with an intent that lists <em>both</em> of those categories.
+ * Activities will very often need to support the
+ * {@link #CATEGORY_DEFAULT} so that they can be found by
+ * {@link Context#startActivity Context.startActivity()}.</p>
+ * </ul>
+ *
+ * <p>For example, consider the Note Pad sample application that
+ * 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"
+ * package="<i>com.android.notepad</i>">
+ * <application android:icon="@drawable/app_notes"
+ * android:label="@string/app_name">
+ *
+ * <provider class=".NotePadProvider"
+ * android:authorities="<i>com.google.provider.NotePad</i>" />
+ *
+ * <activity class=".NotesList" android:label="@string/title_notes_list">
+ * <intent-filter>
+ * <action android:name="android.intent.action.MAIN" />
+ * <category android:name="android.intent.category.LAUNCHER" />
+ * </intent-filter>
+ * <intent-filter>
+ * <action android:name="android.intent.action.VIEW" />
+ * <action android:name="android.intent.action.EDIT" />
+ * <action android:name="android.intent.action.PICK" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ * <intent-filter>
+ * <action android:name="android.intent.action.GET_CONTENT" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ * </activity>
+ *
+ * <activity class=".NoteEditor" android:label="@string/title_note">
+ * <intent-filter android:label="@string/resolve_edit">
+ * <action android:name="android.intent.action.VIEW" />
+ * <action android:name="android.intent.action.EDIT" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ *
+ * <intent-filter>
+ * <action android:name="android.intent.action.INSERT" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ *
+ * </activity>
+ *
+ * <activity class=".TitleEditor" android:label="@string/title_edit_title"
+ * android:theme="@android:style/Theme.Dialog">
+ * <intent-filter android:label="@string/resolve_title">
+ * <action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <category android:name="android.intent.category.ALTERNATIVE" />
+ * <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+ * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ * </activity>
+ *
+ * </application>
+ * </manifest></pre>
+ *
+ * <p>The first activity,
+ * <code>com.android.notepad.NotesList</code>, serves as our main
+ * entry into the app. It can do three things as described by its three intent
+ * templates:
+ * <ol>
+ * <li><pre>
+ * <intent-filter>
+ * <action android:name="{@link #ACTION_MAIN android.intent.action.MAIN}" />
+ * <category android:name="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" />
+ * </intent-filter></pre>
+ * <p>This provides a top-level entry into the NotePad application: the standard
+ * MAIN action is a main entry point (not requiring any other information in
+ * the Intent), and the LAUNCHER category says that this entry point should be
+ * listed in the application launcher.</p>
+ * <li><pre>
+ * <intent-filter>
+ * <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" />
+ * <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" />
+ * <action android:name="{@link #ACTION_PICK android.intent.action.PICK}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" />
+ * </intent-filter></pre>
+ * <p>This declares the things that the activity can do on a directory of
+ * notes. The type being supported is given with the <type> tag, where
+ * <code>vnd.android.cursor.dir/vnd.google.note</code> is a URI from which
+ * a Cursor of zero or more items (<code>vnd.android.cursor.dir</code>) can
+ * be retrieved which holds our note pad data (<code>vnd.google.note</code>).
+ * The activity allows the user to view or edit the directory of data (via
+ * the VIEW and EDIT actions), or to pick a particular note and return it
+ * to the caller (via the PICK action). Note also the DEFAULT category
+ * supplied here: this is <em>required</em> for the
+ * {@link Context#startActivity Context.startActivity} method to resolve your
+ * activity when its component name is not explicitly specified.</p>
+ * <li><pre>
+ * <intent-filter>
+ * <action android:name="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter></pre>
+ * <p>This filter describes the ability to return to the caller a note selected by
+ * the user without needing to know where it came from. The data type
+ * <code>vnd.android.cursor.item/vnd.google.note</code> is a URI from which
+ * a Cursor of exactly one (<code>vnd.android.cursor.item</code>) item can
+ * be retrieved which contains our note pad data (<code>vnd.google.note</code>).
+ * The GET_CONTENT action is similar to the PICK action, where the activity
+ * will return to its caller a piece of data selected by the user. Here,
+ * however, the caller specifies the type of data they desire instead of
+ * the type of data the user will be picking from.</p>
+ * </ol>
+ *
+ * <p>Given these capabilities, the following intents will resolve to the
+ * NotesList activity:</p>
+ *
+ * <ul>
+ * <li> <p><b>{ action=android.app.action.MAIN }</b> matches all of the
+ * activities that can be used as top-level entry points into an
+ * application.</p>
+ * <li> <p><b>{ action=android.app.action.MAIN,
+ * category=android.app.category.LAUNCHER }</b> is the actual intent
+ * used by the Launcher to populate its top-level list.</p>
+ * <li> <p><b>{ action=android.intent.action.VIEW
+ * data=content://com.google.provider.NotePad/notes }</b>
+ * displays a list of all the notes under
+ * "content://com.google.provider.NotePad/notes", which
+ * the user can browse through and see the details on.</p>
+ * <li> <p><b>{ action=android.app.action.PICK
+ * data=content://com.google.provider.NotePad/notes }</b>
+ * provides a list of the notes under
+ * "content://com.google.provider.NotePad/notes", from which
+ * the user can pick a note whose data URL is returned back to the caller.</p>
+ * <li> <p><b>{ action=android.app.action.GET_CONTENT
+ * type=vnd.android.cursor.item/vnd.google.note }</b>
+ * is similar to the pick action, but allows the caller to specify the
+ * kind of data they want back so that the system can find the appropriate
+ * activity to pick something of that data type.</p>
+ * </ul>
+ *
+ * <p>The second activity,
+ * <code>com.android.notepad.NoteEditor</code>, shows the user a single
+ * note entry and allows them to edit it. It can do two things as described
+ * by its two intent templates:
+ * <ol>
+ * <li><pre>
+ * <intent-filter android:label="@string/resolve_edit">
+ * <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" />
+ * <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter></pre>
+ * <p>The first, primary, purpose of this activity is to let the user interact
+ * with a single note, as decribed by the MIME type
+ * <code>vnd.android.cursor.item/vnd.google.note</code>. The activity can
+ * either VIEW a note or allow the user to EDIT it. Again we support the
+ * DEFAULT category to allow the activity to be launched without explicitly
+ * specifying its component.</p>
+ * <li><pre>
+ * <intent-filter>
+ * <action android:name="{@link #ACTION_INSERT android.intent.action.INSERT}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" />
+ * </intent-filter></pre>
+ * <p>The secondary use of this activity is to insert a new note entry into
+ * an existing directory of notes. This is used when the user creates a new
+ * note: the INSERT action is executed on the directory of notes, causing
+ * this activity to run and have the user create the new note data which
+ * it then adds to the content provider.</p>
+ * </ol>
+ *
+ * <p>Given these capabilities, the following intents will resolve to the
+ * NoteEditor activity:</p>
+ *
+ * <ul>
+ * <li> <p><b>{ action=android.intent.action.VIEW
+ * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ * shows the user the content of note <var>{ID}</var>.</p>
+ * <li> <p><b>{ action=android.app.action.EDIT
+ * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ * allows the user to edit the content of note <var>{ID}</var>.</p>
+ * <li> <p><b>{ action=android.app.action.INSERT
+ * data=content://com.google.provider.NotePad/notes }</b>
+ * creates a new, empty note in the notes list at
+ * "content://com.google.provider.NotePad/notes"
+ * and allows the user to edit it. If they keep their changes, the URI
+ * of the newly created note is returned to the caller.</p>
+ * </ul>
+ *
+ * <p>The last activity,
+ * <code>com.android.notepad.TitleEditor</code>, allows the user to
+ * edit the title of a note. This could be implemented as a class that the
+ * application directly invokes (by explicitly setting its component in
+ * the Intent), but here we show a way you can publish alternative
+ * operations on existing data:</p>
+ *
+ * <pre>
+ * <intent-filter android:label="@string/resolve_title">
+ * <action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <category android:name="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" />
+ * <category android:name="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" />
+ * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter></pre>
+ *
+ * <p>In the single intent template here, we
+ * have created our own private action called
+ * <code>com.android.notepad.action.EDIT_TITLE</code> which means to
+ * edit the title of a note. It must be invoked on a specific note
+ * (data type <code>vnd.android.cursor.item/vnd.google.note</code>) like the previous
+ * view and edit actions, but here displays and edits the title contained
+ * in the note data.
+ *
+ * <p>In addition to supporting the default category as usual, our title editor
+ * also supports two other standard categories: ALTERNATIVE and
+ * SELECTED_ALTERNATIVE. Implementing
+ * these categories allows others to find the special action it provides
+ * without directly knowing about it, through the
+ * {@link android.content.pm.PackageManager#queryIntentActivityOptions} method, or
+ * more often to build dynamic menu items with
+ * {@link android.view.Menu#addIntentOptions}. Note that in the intent
+ * template here was also supply an explicit name for the template
+ * (via <code>android:label="@string/resolve_title"</code>) to better control
+ * what the user sees when presented with this activity as an alternative
+ * action to the data they are viewing.
+ *
+ * <p>Given these capabilities, the following intent will resolve to the
+ * TitleEditor activity:</p>
+ *
+ * <ul>
+ * <li> <p><b>{ action=com.android.notepad.action.EDIT_TITLE
+ * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ * displays and allows the user to edit the title associated
+ * with note <var>{ID}</var>.</p>
+ * </ul>
+ *
+ * <h3>Standard Activity Actions</h3>
+ *
+ * <p>These are the current standard actions that Intent defines for launching
+ * activities (usually through {@link Context#startActivity}. The most
+ * important, and by far most frequently used, are {@link #ACTION_MAIN} and
+ * {@link #ACTION_EDIT}.
+ *
+ * <ul>
+ * <li> {@link #ACTION_MAIN}
+ * <li> {@link #ACTION_VIEW}
+ * <li> {@link #ACTION_ATTACH_DATA}
+ * <li> {@link #ACTION_EDIT}
+ * <li> {@link #ACTION_PICK}
+ * <li> {@link #ACTION_CHOOSER}
+ * <li> {@link #ACTION_GET_CONTENT}
+ * <li> {@link #ACTION_DIAL}
+ * <li> {@link #ACTION_CALL}
+ * <li> {@link #ACTION_SEND}
+ * <li> {@link #ACTION_SENDTO}
+ * <li> {@link #ACTION_ANSWER}
+ * <li> {@link #ACTION_INSERT}
+ * <li> {@link #ACTION_DELETE}
+ * <li> {@link #ACTION_RUN}
+ * <li> {@link #ACTION_SYNC}
+ * <li> {@link #ACTION_PICK_ACTIVITY}
+ * <li> {@link #ACTION_SEARCH}
+ * <li> {@link #ACTION_WEB_SEARCH}
+ * <li> {@link #ACTION_FACTORY_TEST}
+ * </ul>
+ *
+ * <h3>Standard Broadcast Actions</h3>
+ *
+ * <p>These are the current standard actions that Intent defines for receiving
+ * broadcasts (usually through {@link Context#registerReceiver} or a
+ * <receiver> tag in a manifest).
+ *
+ * <ul>
+ * <li> {@link #ACTION_TIME_TICK}
+ * <li> {@link #ACTION_TIME_CHANGED}
+ * <li> {@link #ACTION_TIMEZONE_CHANGED}
+ * <li> {@link #ACTION_BOOT_COMPLETED}
+ * <li> {@link #ACTION_PACKAGE_ADDED}
+ * <li> {@link #ACTION_PACKAGE_CHANGED}
+ * <li> {@link #ACTION_PACKAGE_REMOVED}
+ * <li> {@link #ACTION_PACKAGE_RESTARTED}
+ * <li> {@link #ACTION_PACKAGE_DATA_CLEARED}
+ * <li> {@link #ACTION_PACKAGES_SUSPENDED}
+ * <li> {@link #ACTION_PACKAGES_UNSUSPENDED}
+ * <li> {@link #ACTION_UID_REMOVED}
+ * <li> {@link #ACTION_BATTERY_CHANGED}
+ * <li> {@link #ACTION_POWER_CONNECTED}
+ * <li> {@link #ACTION_POWER_DISCONNECTED}
+ * <li> {@link #ACTION_SHUTDOWN}
+ * </ul>
+ *
+ * <h3>Standard Categories</h3>
+ *
+ * <p>These are the current standard categories that can be used to further
+ * clarify an Intent via {@link #addCategory}.
+ *
+ * <ul>
+ * <li> {@link #CATEGORY_DEFAULT}
+ * <li> {@link #CATEGORY_BROWSABLE}
+ * <li> {@link #CATEGORY_TAB}
+ * <li> {@link #CATEGORY_ALTERNATIVE}
+ * <li> {@link #CATEGORY_SELECTED_ALTERNATIVE}
+ * <li> {@link #CATEGORY_LAUNCHER}
+ * <li> {@link #CATEGORY_INFO}
+ * <li> {@link #CATEGORY_HOME}
+ * <li> {@link #CATEGORY_PREFERENCE}
+ * <li> {@link #CATEGORY_TEST}
+ * <li> {@link #CATEGORY_CAR_DOCK}
+ * <li> {@link #CATEGORY_DESK_DOCK}
+ * <li> {@link #CATEGORY_LE_DESK_DOCK}
+ * <li> {@link #CATEGORY_HE_DESK_DOCK}
+ * <li> {@link #CATEGORY_CAR_MODE}
+ * <li> {@link #CATEGORY_APP_MARKET}
+ * <li> {@link #CATEGORY_VR_HOME}
+ * </ul>
+ *
+ * <h3>Standard Extra Data</h3>
+ *
+ * <p>These are the current standard fields that can be used as extra data via
+ * {@link #putExtra}.
+ *
+ * <ul>
+ * <li> {@link #EXTRA_ALARM_COUNT}
+ * <li> {@link #EXTRA_BCC}
+ * <li> {@link #EXTRA_CC}
+ * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME}
+ * <li> {@link #EXTRA_DATA_REMOVED}
+ * <li> {@link #EXTRA_DOCK_STATE}
+ * <li> {@link #EXTRA_DOCK_STATE_HE_DESK}
+ * <li> {@link #EXTRA_DOCK_STATE_LE_DESK}
+ * <li> {@link #EXTRA_DOCK_STATE_CAR}
+ * <li> {@link #EXTRA_DOCK_STATE_DESK}
+ * <li> {@link #EXTRA_DOCK_STATE_UNDOCKED}
+ * <li> {@link #EXTRA_DONT_KILL_APP}
+ * <li> {@link #EXTRA_EMAIL}
+ * <li> {@link #EXTRA_INITIAL_INTENTS}
+ * <li> {@link #EXTRA_INTENT}
+ * <li> {@link #EXTRA_KEY_EVENT}
+ * <li> {@link #EXTRA_ORIGINATING_URI}
+ * <li> {@link #EXTRA_PHONE_NUMBER}
+ * <li> {@link #EXTRA_REFERRER}
+ * <li> {@link #EXTRA_REMOTE_INTENT_TOKEN}
+ * <li> {@link #EXTRA_REPLACING}
+ * <li> {@link #EXTRA_SHORTCUT_ICON}
+ * <li> {@link #EXTRA_SHORTCUT_ICON_RESOURCE}
+ * <li> {@link #EXTRA_SHORTCUT_INTENT}
+ * <li> {@link #EXTRA_STREAM}
+ * <li> {@link #EXTRA_SHORTCUT_NAME}
+ * <li> {@link #EXTRA_SUBJECT}
+ * <li> {@link #EXTRA_TEMPLATE}
+ * <li> {@link #EXTRA_TEXT}
+ * <li> {@link #EXTRA_TITLE}
+ * <li> {@link #EXTRA_UID}
+ * <li> {@link #EXTRA_USER_INITIATED}
+ * </ul>
+ *
+ * <h3>Flags</h3>
+ *
+ * <p>These are the possible flags that can be used in the Intent via
+ * {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list
+ * of all possible flags.
+ */
+public class Intent implements Parcelable, Cloneable {
+ private static final String TAG = "Intent";
+
+ private static final String ATTR_ACTION = "action";
+ private static final String TAG_CATEGORIES = "categories";
+ private static final String ATTR_CATEGORY = "category";
+ private static final String TAG_EXTRA = "extra";
+ private static final String ATTR_TYPE = "type";
+ private static final String ATTR_IDENTIFIER = "ident";
+ private static final String ATTR_COMPONENT = "component";
+ private static final String ATTR_DATA = "data";
+ private static final String ATTR_FLAGS = "flags";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Standard intent activity actions (see action variable).
+
+ /**
+ * Activity Action: Start as a main entry point, does not expect to
+ * receive data.
+ * <p>Input: nothing
+ * <p>Output: nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MAIN = "android.intent.action.MAIN";
+
+ /**
+ * Activity Action: Display the data to the user. This is the most common
+ * action performed on data -- it is the generic action you can use on
+ * a piece of data to get the most reasonable thing to occur. For example,
+ * when used on a contacts entry it will view the entry; when used on a
+ * mailto: URI it will bring up a compose window filled with the information
+ * supplied by the URI; when used with a tel: URI it will invoke the
+ * dialer.
+ * <p>Input: {@link #getData} is URI from which to retrieve data.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VIEW = "android.intent.action.VIEW";
+
+ /**
+ * Extra that can be included on activity intents coming from the storage UI
+ * when it launches sub-activities to manage various types of storage. For example,
+ * it may use {@link #ACTION_VIEW} with a "image/*" MIME type to have an app show
+ * the images on the device, and in that case also include this extra to tell the
+ * app it is coming from the storage UI so should help the user manage storage of
+ * this type.
+ */
+ public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE";
+
+ /**
+ * A synonym for {@link #ACTION_VIEW}, the "standard" action that is
+ * performed on a piece of data.
+ */
+ public static final String ACTION_DEFAULT = ACTION_VIEW;
+
+ /**
+ * Activity Action: Quick view the data. Launches a quick viewer for
+ * a URI or a list of URIs.
+ * <p>Activities handling this intent action should handle the vast majority of
+ * MIME types rather than only specific ones.
+ * <p>Quick viewers must render the quick view image locally, and must not send
+ * file content outside current device.
+ * <p>Input: {@link #getData} is a mandatory content URI of the item to
+ * preview. {@link #getClipData} contains an optional list of content URIs
+ * if there is more than one item to preview. {@link #EXTRA_INDEX} is an
+ * optional index of the URI in the clip data to show first.
+ * {@link #EXTRA_QUICK_VIEW_FEATURES} is an optional extra indicating the features
+ * that can be shown in the quick view UI.
+ * <p>Output: nothing.
+ * @see #EXTRA_INDEX
+ * @see #EXTRA_QUICK_VIEW_FEATURES
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
+
+ /**
+ * Used to indicate that some piece of data should be attached to some other
+ * place. For example, image data could be attached to a contact. It is up
+ * to the recipient to decide where the data should be attached; the intent
+ * does not specify the ultimate destination.
+ * <p>Input: {@link #getData} is URI of data to be attached.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA";
+
+ /**
+ * Activity Action: Provide explicit editable access to the given data.
+ * <p>Input: {@link #getData} is URI of data to be edited.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_EDIT = "android.intent.action.EDIT";
+
+ /**
+ * Activity Action: Pick an existing item, or insert a new item, and then edit it.
+ * <p>Input: {@link #getType} is the desired MIME type of the item to create or edit.
+ * The extras can contain type specific data to pass through to the editing/creating
+ * activity.
+ * <p>Output: The URI of the item that was picked. This must be a content:
+ * URI so that any receiver can access it.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+
+ /**
+ * Activity Action: Pick an item from the data, returning what was selected.
+ * <p>Input: {@link #getData} is URI containing a directory of data
+ * (vnd.android.cursor.dir/*) from which to pick an item.
+ * <p>Output: The URI of the item that was picked.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICK = "android.intent.action.PICK";
+
+ /**
+ * Activity Action: Creates a reminder.
+ * <p>Input: {@link #EXTRA_TITLE} The title of the reminder that will be shown to the user.
+ * {@link #EXTRA_TEXT} The reminder text that will be shown to the user. The intent should at
+ * least specify a title or a text. {@link #EXTRA_TIME} The time when the reminder will
+ * be shown to the user. The time is specified in milliseconds since the Epoch (optional).
+ * </p>
+ * <p>Output: Nothing.</p>
+ *
+ * @see #EXTRA_TITLE
+ * @see #EXTRA_TEXT
+ * @see #EXTRA_TIME
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
+
+ /**
+ * Activity Action: Creates a shortcut.
+ * <p>Input: Nothing.</p>
+ * <p>Output: An Intent representing the {@link android.content.pm.ShortcutInfo} result.</p>
+ * <p>For compatibility with older versions of android the intent may also contain three
+ * extras: SHORTCUT_INTENT (value: Intent), SHORTCUT_NAME (value: String),
+ * and SHORTCUT_ICON (value: Bitmap) or SHORTCUT_ICON_RESOURCE
+ * (value: ShortcutIconResource).</p>
+ *
+ * @see android.content.pm.ShortcutManager#createShortcutResultIntent
+ * @see #EXTRA_SHORTCUT_INTENT
+ * @see #EXTRA_SHORTCUT_NAME
+ * @see #EXTRA_SHORTCUT_ICON
+ * @see #EXTRA_SHORTCUT_ICON_RESOURCE
+ * @see android.content.Intent.ShortcutIconResource
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
+
+ /**
+ * The name of the extra used to define the Intent of a shortcut.
+ *
+ * @see #ACTION_CREATE_SHORTCUT
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
+ */
+ @Deprecated
+ public static final String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
+ /**
+ * The name of the extra used to define the name of a shortcut.
+ *
+ * @see #ACTION_CREATE_SHORTCUT
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
+ */
+ @Deprecated
+ public static final String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
+ /**
+ * The name of the extra used to define the icon, as a Bitmap, of a shortcut.
+ *
+ * @see #ACTION_CREATE_SHORTCUT
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
+ */
+ @Deprecated
+ public static final String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
+ /**
+ * The name of the extra used to define the icon, as a ShortcutIconResource, of a shortcut.
+ *
+ * @see #ACTION_CREATE_SHORTCUT
+ * @see android.content.Intent.ShortcutIconResource
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
+ */
+ @Deprecated
+ public static final String EXTRA_SHORTCUT_ICON_RESOURCE =
+ "android.intent.extra.shortcut.ICON_RESOURCE";
+
+ /**
+ * An activity that provides a user interface for adjusting application preferences.
+ * Optional but recommended settings for all applications which have settings.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APPLICATION_PREFERENCES
+ = "android.intent.action.APPLICATION_PREFERENCES";
+
+ /**
+ * Activity Action: Launch an activity showing the app information.
+ * For applications which install other applications (such as app stores), it is recommended
+ * to handle this action for providing the app information to the user.
+ *
+ * <p>Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose information needs
+ * to be displayed.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SHOW_APP_INFO
+ = "android.intent.action.SHOW_APP_INFO";
+
+ /**
+ * Activity Action: Placeholder that the component handling it can do activity
+ * recognition. Can be placed on a service. Only one service per package is
+ * supported.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_ACTIVITY_RECOGNIZER =
+ "android.intent.action.ACTIVITY_RECOGNIZER";
+
+ /**
+ * Represents a shortcut/live folder icon resource.
+ *
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ * @see Intent#EXTRA_SHORTCUT_ICON_RESOURCE
+ * @see android.provider.LiveFolders#ACTION_CREATE_LIVE_FOLDER
+ * @see android.provider.LiveFolders#EXTRA_LIVE_FOLDER_ICON
+ */
+ public static class ShortcutIconResource implements Parcelable {
+ /**
+ * The package name of the application containing the icon.
+ */
+ public String packageName;
+
+ /**
+ * The resource name of the icon, including package, name and type.
+ */
+ public String resourceName;
+
+ /**
+ * Creates a new ShortcutIconResource for the specified context and resource
+ * identifier.
+ *
+ * @param context The context of the application.
+ * @param resourceId The resource identifier for the icon.
+ * @return A new ShortcutIconResource with the specified's context package name
+ * and icon resource identifier.``
+ */
+ public static ShortcutIconResource fromContext(Context context, @AnyRes int resourceId) {
+ ShortcutIconResource icon = new ShortcutIconResource();
+ icon.packageName = context.getPackageName();
+ icon.resourceName = context.getResources().getResourceName(resourceId);
+ return icon;
+ }
+
+ /**
+ * Used to read a ShortcutIconResource from a Parcel.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<ShortcutIconResource> CREATOR =
+ new Parcelable.Creator<ShortcutIconResource>() {
+
+ public ShortcutIconResource createFromParcel(Parcel source) {
+ ShortcutIconResource icon = new ShortcutIconResource();
+ icon.packageName = source.readString8();
+ icon.resourceName = source.readString8();
+ return icon;
+ }
+
+ public ShortcutIconResource[] newArray(int size) {
+ return new ShortcutIconResource[size];
+ }
+ };
+
+ /**
+ * No special parcel contents.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString8(packageName);
+ dest.writeString8(resourceName);
+ }
+
+ @Override
+ public String toString() {
+ return resourceName;
+ }
+ }
+
+ /**
+ * Activity Action: Display an activity chooser, allowing the user to pick
+ * what they want to before proceeding. This can be used as an alternative
+ * to the standard activity picker that is displayed by the system when
+ * you try to start an activity with multiple possible matches, with these
+ * differences in behavior:
+ * <ul>
+ * <li>You can specify the title that will appear in the activity chooser.
+ * <li>The user does not have the option to make one of the matching
+ * activities a preferred activity, and all possible activities will
+ * always be shown even if one of them is currently marked as the
+ * preferred activity.
+ * </ul>
+ * <p>
+ * This action should be used when the user will naturally expect to
+ * select an activity in order to proceed. An example if when not to use
+ * it is when the user clicks on a "mailto:" link. They would naturally
+ * expect to go directly to their mail app, so startActivity() should be
+ * called directly: it will
+ * either launch the current preferred app, or put up a dialog allowing the
+ * user to pick an app to use and optionally marking that as preferred.
+ * <p>
+ * In contrast, if the user is selecting a menu item to send a picture
+ * they are viewing to someone else, there are many different things they
+ * may want to do at this point: send it through e-mail, upload it to a
+ * web service, etc. In this case the CHOOSER action should be used, to
+ * always present to the user a list of the things they can do, with a
+ * nice title given by the caller such as "Send this photo with:".
+ * <p>
+ * If you need to grant URI permissions through a chooser, you must specify
+ * the permissions to be granted on the ACTION_CHOOSER Intent
+ * <em>in addition</em> to the EXTRA_INTENT inside. This means using
+ * {@link #setClipData} to specify the URIs to be granted as well as
+ * {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION} as appropriate.
+ * <p>
+ * As a convenience, an Intent of this form can be created with the
+ * {@link #createChooser} function.
+ * <p>
+ * Input: No data should be specified. get*Extra must have
+ * a {@link #EXTRA_INTENT} field containing the Intent being executed,
+ * and can optionally have a {@link #EXTRA_TITLE} field containing the
+ * title text to display in the chooser.
+ * <p>
+ * Output: Depends on the protocol of {@link #EXTRA_INTENT}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+
+ /**
+ * Convenience function for creating a {@link #ACTION_CHOOSER} Intent.
+ *
+ * <p>Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given
+ * target intent, also optionally supplying a title. If the target
+ * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be
+ * set in the returned chooser intent, with its ClipData set appropriately:
+ * either a direct reflection of {@link #getClipData()} if that is non-null,
+ * or a new ClipData built from {@link #getData()}.
+ *
+ * @param target The Intent that the user will be selecting an activity
+ * to perform.
+ * @param title Optional title that will be displayed in the chooser,
+ * only when the target action is not ACTION_SEND or ACTION_SEND_MULTIPLE.
+ * @return Return a new Intent object that you can hand to
+ * {@link Context#startActivity(Intent) Context.startActivity()} and
+ * related methods.
+ */
+ public static Intent createChooser(Intent target, CharSequence title) {
+ return createChooser(target, title, null);
+ }
+
+ /**
+ * Convenience function for creating a {@link #ACTION_CHOOSER} Intent.
+ *
+ * <p>Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given
+ * target intent, also optionally supplying a title. If the target
+ * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be
+ * set in the returned chooser intent, with its ClipData set appropriately:
+ * either a direct reflection of {@link #getClipData()} if that is non-null,
+ * or a new ClipData built from {@link #getData()}.</p>
+ *
+ * <p>The caller may optionally supply an {@link IntentSender} to receive a callback
+ * when the user makes a choice. This can be useful if the calling application wants
+ * to remember the last chosen target and surface it as a more prominent or one-touch
+ * affordance elsewhere in the UI for next time.</p>
+ *
+ * @param target The Intent that the user will be selecting an activity
+ * to perform.
+ * @param title Optional title that will be displayed in the chooser,
+ * only when the target action is not ACTION_SEND or ACTION_SEND_MULTIPLE.
+ * @param sender Optional IntentSender to be called when a choice is made.
+ * @return Return a new Intent object that you can hand to
+ * {@link Context#startActivity(Intent) Context.startActivity()} and
+ * related methods.
+ */
+ public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) {
+ Intent intent = new Intent(ACTION_CHOOSER);
+ intent.putExtra(EXTRA_INTENT, target);
+ if (title != null) {
+ intent.putExtra(EXTRA_TITLE, title);
+ }
+
+ if (sender != null) {
+ intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+ }
+
+ // Migrate any clip data and flags from target.
+ int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION);
+ if (permFlags != 0) {
+ ClipData targetClipData = target.getClipData();
+ if (targetClipData == null && target.getData() != null) {
+ ClipData.Item item = new ClipData.Item(target.getData());
+ String[] mimeTypes;
+ if (target.getType() != null) {
+ mimeTypes = new String[] { target.getType() };
+ } else {
+ mimeTypes = new String[] { };
+ }
+ targetClipData = new ClipData(null, mimeTypes, item);
+ }
+ if (targetClipData != null) {
+ intent.setClipData(targetClipData);
+ intent.addFlags(permFlags);
+ }
+ }
+
+ return intent;
+ }
+
+ /**
+ * Activity Action: Allow the user to select a particular kind of data and
+ * return it. This is different than {@link #ACTION_PICK} in that here we
+ * just say what kind of data is desired, not a URI of existing data from
+ * which the user can pick. An ACTION_GET_CONTENT could allow the user to
+ * create the data as it runs (for example taking a picture or recording a
+ * sound), let them browse over the web and download the desired data,
+ * etc.
+ * <p>
+ * There are two main ways to use this action: if you want a specific kind
+ * of data, such as a person contact, you set the MIME type to the kind of
+ * data you want and launch it with {@link Context#startActivity(Intent)}.
+ * The system will then launch the best application to select that kind
+ * of data for you.
+ * <p>
+ * You may also be interested in any of a set of types of content the user
+ * can pick. For example, an e-mail application that wants to allow the
+ * user to add an attachment to an e-mail message can use this action to
+ * bring up a list of all of the types of content the user can attach.
+ * <p>
+ * In this case, you should wrap the GET_CONTENT intent with a chooser
+ * (through {@link #createChooser}), which will give the proper interface
+ * for the user to pick how to send your data and allow you to specify
+ * a prompt indicating what they are doing. You will usually specify a
+ * broad MIME type (such as image/* or {@literal *}/*), resulting in a
+ * broad range of content types the user can select from.
+ * <p>
+ * When using such a broad GET_CONTENT action, it is often desirable to
+ * only pick from data that can be represented as a stream. This is
+ * accomplished by requiring the {@link #CATEGORY_OPENABLE} in the Intent.
+ * <p>
+ * Callers can optionally specify {@link #EXTRA_LOCAL_ONLY} to request that
+ * the launched content chooser only returns results representing data that
+ * is locally available on the device. For example, if this extra is set
+ * to true then an image picker should not show any pictures that are available
+ * from a remote server but not already on the local device (thus requiring
+ * they be downloaded when opened).
+ * <p>
+ * If the caller can handle multiple returned items (the user performing
+ * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE}
+ * to indicate this.
+ * <p>
+ * Input: {@link #getType} is the desired MIME type to retrieve. Note
+ * that no URI is supplied in the intent, as there are no constraints on
+ * where the returned data originally comes from. You may also include the
+ * {@link #CATEGORY_OPENABLE} if you can only accept data that can be
+ * opened as a stream. You may use {@link #EXTRA_LOCAL_ONLY} to limit content
+ * selection to local data. You may use {@link #EXTRA_ALLOW_MULTIPLE} to
+ * allow the user to select multiple items.
+ * <p>
+ * Output: The URI of the item that was picked. This must be a content:
+ * URI so that any receiver can access it.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";
+ /**
+ * Activity Action: Dial a number as specified by the data. This shows a
+ * UI with the number being dialed, allowing the user to explicitly
+ * initiate the call.
+ * <p>Input: If nothing, an empty dialer is started; else {@link #getData}
+ * is URI of a phone number to be dialed or a tel: URI of an explicit phone
+ * number.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DIAL = "android.intent.action.DIAL";
+ /**
+ * Activity Action: Perform a call to someone specified by the data.
+ * <p>Input: If nothing, an empty dialer is started; else {@link #getData}
+ * is URI of a phone number to be dialed or a tel: URI of an explicit phone
+ * number.
+ * <p>Output: nothing.
+ *
+ * <p>Note: there will be restrictions on which applications can initiate a
+ * call; most applications should use the {@link #ACTION_DIAL}.
+ * <p>Note: this Intent <strong>cannot</strong> be used to call emergency
+ * numbers. Applications can <strong>dial</strong> emergency numbers using
+ * {@link #ACTION_DIAL}, however.
+ *
+ * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M}
+ * and above and declares as using the {@link android.Manifest.permission#CALL_PHONE}
+ * permission which is not granted, then attempting to use this action will
+ * result in a {@link java.lang.SecurityException}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CALL = "android.intent.action.CALL";
+ /**
+ * Activity Action: Perform a call to an emergency number specified by the
+ * data.
+ * <p>Input: {@link #getData} is URI of a phone number to be dialed or a
+ * tel: URI of an explicit phone number.
+ * <p>Output: nothing.
+ *
+ * <p class="note"><strong>Note:</strong> It is not guaranteed that the call will be placed on
+ * the {@link PhoneAccount} provided in the {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE}
+ * extra (if specified) and may be placed on another {@link PhoneAccount} with the
+ * {@link PhoneAccount#CAPABILITY_PLACE_EMERGENCY_CALLS} capability, depending on external
+ * factors, such as network conditions and Modem/SIM status.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY";
+ /**
+ * Activity Action: Dial a emergency number specified by the data. This shows a
+ * UI with the number being dialed, allowing the user to explicitly
+ * initiate the call.
+ * <p>Input: If nothing, an empty emergency dialer is started; else {@link #getData}
+ * is a tel: URI of an explicit emergency phone number.
+ * <p>Output: nothing.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DIAL_EMERGENCY = "android.intent.action.DIAL_EMERGENCY";
+ /**
+ * Activity action: Perform a call to any number (emergency or not)
+ * specified by the data.
+ * <p>Input: {@link #getData} is URI of a phone number to be dialed or a
+ * tel: URI of an explicit phone number.
+ * <p>Output: nothing.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
+
+ /**
+ * Activity Action: Main entry point for carrier setup apps.
+ * <p>Carrier apps that provide an implementation for this action may be invoked to configure
+ * carrier service and typically require
+ * {@link android.telephony.TelephonyManager#hasCarrierPrivileges() carrier privileges} to
+ * fulfill their duties.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CARRIER_SETUP = "android.intent.action.CARRIER_SETUP";
+ /**
+ * Activity Action: Send a message to someone specified by the data.
+ * <p>Input: {@link #getData} is URI describing the target.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SENDTO = "android.intent.action.SENDTO";
+ /**
+ * Activity Action: Deliver some data to someone else. Who the data is
+ * being delivered to is not specified; it is up to the receiver of this
+ * action to ask the user where the data should be sent.
+ * <p>
+ * When launching a SEND intent, you should usually wrap it in a chooser
+ * (through {@link #createChooser}), which will give the proper interface
+ * for the user to pick how to send your data and allow you to specify
+ * a prompt indicating what they are doing.
+ * <p>
+ * Input: {@link #getType} is the MIME type of the data being sent.
+ * get*Extra can have either a {@link #EXTRA_TEXT}
+ * or {@link #EXTRA_STREAM} field, containing the data to be sent. If
+ * using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it
+ * should be the MIME type of the data in EXTRA_STREAM. Use {@literal *}/*
+ * if the MIME type is unknown (this will only allow senders that can
+ * handle generic data streams). If using {@link #EXTRA_TEXT}, you can
+ * also optionally supply {@link #EXTRA_HTML_TEXT} for clients to retrieve
+ * your text with HTML formatting.
+ * <p>
+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data
+ * being sent can be supplied through {@link #setClipData(ClipData)}. This
+ * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing
+ * content: URIs and other advanced features of {@link ClipData}. If
+ * using this approach, you still must supply the same data through the
+ * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below
+ * for compatibility with old applications. If you don't set a ClipData,
+ * it will be copied there for you when calling {@link Context#startActivity(Intent)}.
+ * <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#O}, if
+ * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in
+ * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may
+ * be openable only as asset typed files using
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
+ * <p>
+ * Optional standard extras, which may be interpreted by some recipients as
+ * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
+ * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEND = "android.intent.action.SEND";
+ /**
+ * Activity Action: Deliver multiple data to someone else.
+ * <p>
+ * Like {@link #ACTION_SEND}, except the data is multiple.
+ * <p>
+ * Input: {@link #getType} is the MIME type of the data being sent.
+ * get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link
+ * #EXTRA_STREAM} field, containing the data to be sent. If using
+ * {@link #EXTRA_TEXT}, you can also optionally supply {@link #EXTRA_HTML_TEXT}
+ * for clients to retrieve your text with HTML formatting.
+ * <p>
+ * Multiple types are supported, and receivers should handle mixed types
+ * whenever possible. The right way for the receiver to check them is to
+ * use the content resolver on each URI. The intent sender should try to
+ * put the most concrete mime type in the intent type, but it can fall
+ * back to {@literal <type>/*} or {@literal *}/* as needed.
+ * <p>
+ * e.g. if you are sending image/jpg and image/jpg, the intent's type can
+ * be image/jpg, but if you are sending image/jpg and image/png, then the
+ * intent's type should be image/*.
+ * <p>
+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data
+ * being sent can be supplied through {@link #setClipData(ClipData)}. This
+ * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing
+ * content: URIs and other advanced features of {@link ClipData}. If
+ * using this approach, you still must supply the same data through the
+ * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below
+ * for compatibility with old applications. If you don't set a ClipData,
+ * it will be copied there for you when calling {@link Context#startActivity(Intent)}.
+ * <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#O}, if
+ * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in
+ * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may
+ * be openable only as asset typed files using
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
+ * <p>
+ * Optional standard extras, which may be interpreted by some recipients as
+ * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
+ * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
+ /**
+ * Activity Action: Handle an incoming phone call.
+ * <p>Input: nothing.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ANSWER = "android.intent.action.ANSWER";
+ /**
+ * Activity Action: Insert an empty item into the given container.
+ * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+ * in which to place the data.
+ * <p>Output: URI of the new data that was created.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSERT = "android.intent.action.INSERT";
+ /**
+ * Activity Action: Create a new item in the given container, initializing it
+ * from the current contents of the clipboard.
+ * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+ * in which to place the data.
+ * <p>Output: URI of the new data that was created.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PASTE = "android.intent.action.PASTE";
+ /**
+ * Activity Action: Delete the given data from its container.
+ * <p>Input: {@link #getData} is URI of data to be deleted.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DELETE = "android.intent.action.DELETE";
+ /**
+ * Activity Action: Run the data, whatever that means.
+ * <p>Input: ? (Note: this is currently specific to the test harness.)
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_RUN = "android.intent.action.RUN";
+ /**
+ * Activity Action: Perform a data synchronization.
+ * <p>Input: ?
+ * <p>Output: ?
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SYNC = "android.intent.action.SYNC";
+ /**
+ * Activity Action: Pick an activity given an intent, returning the class
+ * selected.
+ * <p>Input: get*Extra field {@link #EXTRA_INTENT} is an Intent
+ * used with {@link PackageManager#queryIntentActivities} to determine the
+ * set of activities from which to pick.
+ * <p>Output: Class name of the activity that was selected.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY";
+ /**
+ * Activity Action: Perform a search.
+ * <p>Input: {@link android.app.SearchManager#QUERY getStringExtra(SearchManager.QUERY)}
+ * is the text to search for. If empty, simply
+ * enter your search results Activity with the search UI activated.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEARCH = "android.intent.action.SEARCH";
+ /**
+ * Activity Action: Start the platform-defined tutorial
+ * <p>Input: {@link android.app.SearchManager#QUERY getStringExtra(SearchManager.QUERY)}
+ * is the text to search for. If empty, simply
+ * enter your search results Activity with the search UI activated.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SYSTEM_TUTORIAL = "android.intent.action.SYSTEM_TUTORIAL";
+ /**
+ * Activity Action: Perform a web search.
+ * <p>
+ * Input: {@link android.app.SearchManager#QUERY
+ * getStringExtra(SearchManager.QUERY)} is the text to search for. If it is
+ * a url starts with http or https, the site will be opened. If it is plain
+ * text, Google search will be applied.
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
+
+ /**
+ * Activity Action: Perform assist action.
+ * <p>
+ * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide
+ * additional optional contextual information about where the user was when they
+ * requested the assist; {@link #EXTRA_REFERRER} may be set with additional referrer
+ * information.
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ASSIST = "android.intent.action.ASSIST";
+
+ /**
+ * Activity Action: Perform voice assist action.
+ * <p>
+ * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide
+ * additional optional contextual information about where the user was when they
+ * requested the voice assist.
+ * Output: nothing.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} containing the name of the current foreground
+ * application package at the time the assist was invoked.
+ */
+ public static final String EXTRA_ASSIST_PACKAGE
+ = "android.intent.extra.ASSIST_PACKAGE";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} containing the uid of the current foreground
+ * application package at the time the assist was invoked.
+ */
+ public static final String EXTRA_ASSIST_UID
+ = "android.intent.extra.ASSIST_UID";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} and containing additional contextual
+ * information supplied by the current foreground app at the time of the assist request.
+ * This is a {@link Bundle} of additional data.
+ */
+ public static final String EXTRA_ASSIST_CONTEXT
+ = "android.intent.extra.ASSIST_CONTEXT";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} suggesting that the user will likely use a
+ * keyboard as the primary input device for assistance.
+ */
+ public static final String EXTRA_ASSIST_INPUT_HINT_KEYBOARD =
+ "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} containing the InputDevice id
+ * that was used to invoke the assist.
+ */
+ public static final String EXTRA_ASSIST_INPUT_DEVICE_ID =
+ "android.intent.extra.ASSIST_INPUT_DEVICE_ID";
+
+ /**
+ * Activity Action: List all available applications.
+ * <p>Input: Nothing.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ALL_APPS = "android.intent.action.ALL_APPS";
+ /**
+ * Activity Action: Show settings for choosing wallpaper.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER";
+
+ /**
+ * Activity Action: Show activity for reporting a bug.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BUG_REPORT = "android.intent.action.BUG_REPORT";
+
+ /**
+ * Activity Action: Main entry point for factory tests. Only used when
+ * the device is booting in factory test node. The implementing package
+ * must be installed in the system image.
+ * <p>Input: nothing
+ * <p>Output: nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST";
+
+ /**
+ * Activity Action: The user pressed the "call" button to go to the dialer
+ * or other appropriate UI for placing a call.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
+
+ /**
+ * Activity Action: Start Voice Command.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * <p class="note">
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
+
+ /**
+ * Activity Action: Start action associated with long pressing on the
+ * search key.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS";
+
+ /**
+ * Activity Action: The user pressed the "Report" button in the crash/ANR dialog.
+ * This intent is delivered to the package which installed the application, usually
+ * Google Play.
+ * <p>Input: No data is specified. The bug report is passed in using
+ * an {@link #EXTRA_BUG_REPORT} field.
+ * <p>Output: Nothing.
+ *
+ * @see #EXTRA_BUG_REPORT
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
+
+ /**
+ * An incident or bug report has been taken, and a system app has requested it to be shared,
+ * so trigger the confirmation screen.
+ *
+ * This will be sent directly to the registered receiver with the
+ * android.permission.APPROVE_INCIDENT_REPORTS permission.
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_PENDING_INCIDENT_REPORTS_CHANGED =
+ "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED";
+
+ /**
+ * An incident report has been taken, and the user has approved it for sharing.
+ * <p>
+ * This will be sent directly to the registered receiver, which must have
+ * both the DUMP and USAGE_STATS permissions.
+ * <p>
+ * After receiving this, the application should wait until a suitable time
+ * (e.g. network available), get the list of available reports with
+ * {@link IncidentManager#getIncidentReportList IncidentManager.getIncidentReportList(String)}
+ * and then when the reports have been successfully uploaded, call
+ * {@link IncidentManager#deleteIncidentReport IncidentManager.deleteIncidentReport(Uri)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_INCIDENT_REPORT_READY =
+ "android.intent.action.INCIDENT_REPORT_READY";
+
+ /**
+ * Activity Action: Show power usage information to the user.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
+
+ /**
+ * Activity Action: Setup wizard action provided for OTA provisioning to determine if it needs
+ * to run.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, setup wizard can be identified
+ * using {@link #ACTION_MAIN} and {@link #CATEGORY_SETUP_WIZARD}
+ * @hide
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ public static final String ACTION_DEVICE_INITIALIZATION_WIZARD =
+ "android.intent.action.DEVICE_INITIALIZATION_WIZARD";
+
+ /**
+ * Activity Action: Setup wizard to launch after a platform update. This
+ * activity should have a string meta-data field associated with it,
+ * {@link #METADATA_SETUP_VERSION}, which defines the current version of
+ * the platform for setup. The activity will be launched only if
+ * {@link android.provider.Settings.Secure#LAST_SETUP_SHOWN} is not the
+ * same value.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
+
+ /**
+ * Activity Action: Start the Keyboard Shortcuts Helper screen.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SHOW_KEYBOARD_SHORTCUTS =
+ "com.android.intent.action.SHOW_KEYBOARD_SHORTCUTS";
+
+ /**
+ * Activity Action: Dismiss the Keyboard Shortcuts Helper screen.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DISMISS_KEYBOARD_SHORTCUTS =
+ "com.android.intent.action.DISMISS_KEYBOARD_SHORTCUTS";
+
+ /**
+ * Activity Action: Show settings for managing network data usage of a
+ * specific application. Applications should define an activity that offers
+ * options to control data usage.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_NETWORK_USAGE =
+ "android.intent.action.MANAGE_NETWORK_USAGE";
+
+ /**
+ * Activity Action: Launch application installer.
+ * <p>
+ * Input: The data must be a content: URI at which the application
+ * can be retrieved. As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1},
+ * you can also use "package:<package-name>" to install an application for the
+ * current user that is already installed for another user. You can optionally supply
+ * {@link #EXTRA_INSTALLER_PACKAGE_NAME}, {@link #EXTRA_NOT_UNKNOWN_SOURCE},
+ * {@link #EXTRA_ALLOW_REPLACE}, and {@link #EXTRA_RETURN_RESULT}.
+ * <p>
+ * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
+ * succeeded.
+ * <p>
+ * <strong>Note:</strong>If your app is targeting API level higher than 25 you
+ * need to hold {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES}
+ * in order to launch the application installer.
+ * </p>
+ *
+ * @see #EXTRA_INSTALLER_PACKAGE_NAME
+ * @see #EXTRA_NOT_UNKNOWN_SOURCE
+ * @see #EXTRA_RETURN_RESULT
+ *
+ * @deprecated use {@link android.content.pm.PackageInstaller} instead
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
+
+ /**
+ * Activity Action: Activity to handle split installation failures.
+ * <p>Splits may be installed dynamically. This happens when an Activity is launched,
+ * but the split that contains the application isn't installed. When a split is
+ * installed in this manner, the containing package usually doesn't know this is
+ * happening. However, if an error occurs during installation, the containing
+ * package can define a single activity handling this action to deal with such
+ * failures.
+ * <p>The activity handling this action must be in the base package.
+ * <p>
+ * Input: {@link #EXTRA_INTENT} the original intent that started split installation.
+ * {@link #EXTRA_SPLIT_NAME} the name of the split that failed to be installed.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE";
+
+ /**
+ * Activity Action: Launch instant application installer.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE
+ = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
+
+ /**
+ * Service Action: Resolve instant application.
+ * <p>
+ * The system will have a persistent connection to this service.
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE
+ = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
+
+ /**
+ * Activity Action: Launch instant app settings.
+ *
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS
+ = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
+
+ /**
+ * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
+ * package. Specifies the installer package name; this package will receive the
+ * {@link #ACTION_APP_ERROR} intent.
+ */
+ public static final String EXTRA_INSTALLER_PACKAGE_NAME
+ = "android.intent.extra.INSTALLER_PACKAGE_NAME";
+
+ /**
+ * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
+ * package. Specifies that the application being installed should not be
+ * treated as coming from an unknown source, but as coming from the app
+ * invoking the Intent. For this to work you must start the installer with
+ * startActivityForResult().
+ */
+ public static final String EXTRA_NOT_UNKNOWN_SOURCE
+ = "android.intent.extra.NOT_UNKNOWN_SOURCE";
+
+ /**
+ * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link #ACTION_VIEW} to indicate the URI from which the local APK in the Intent
+ * data field originated from.
+ */
+ public static final String EXTRA_ORIGINATING_URI
+ = "android.intent.extra.ORIGINATING_URI";
+
+ /**
+ * This extra can be used with any Intent used to launch an activity, supplying information
+ * about who is launching that activity. This field contains a {@link android.net.Uri}
+ * object, typically an http: or https: URI of the web site that the referral came from;
+ * it can also use the {@link #URI_ANDROID_APP_SCHEME android-app:} scheme to identify
+ * a native application that it came from.
+ *
+ * <p>To retrieve this value in a client, use {@link android.app.Activity#getReferrer}
+ * instead of directly retrieving the extra. It is also valid for applications to
+ * instead supply {@link #EXTRA_REFERRER_NAME} for cases where they can only create
+ * a string, not a Uri; the field here, if supplied, will always take precedence,
+ * however.</p>
+ *
+ * @see #EXTRA_REFERRER_NAME
+ */
+ public static final String EXTRA_REFERRER
+ = "android.intent.extra.REFERRER";
+
+ /**
+ * Alternate version of {@link #EXTRA_REFERRER} that supplies the URI as a String rather
+ * than a {@link android.net.Uri} object. Only for use in cases where Uri objects can
+ * not be created, in particular when Intent extras are supplied through the
+ * {@link #URI_INTENT_SCHEME intent:} or {@link #URI_ANDROID_APP_SCHEME android-app:}
+ * schemes.
+ *
+ * @see #EXTRA_REFERRER
+ */
+ public static final String EXTRA_REFERRER_NAME
+ = "android.intent.extra.REFERRER_NAME";
+
+ /**
+ * Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link #ACTION_VIEW} to indicate the uid of the package that initiated the install
+ * Currently only a system app that hosts the provider authority "downloads" or holds the
+ * permission {@link android.Manifest.permission.MANAGE_DOCUMENTS} can use this.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_ORIGINATING_UID
+ = "android.intent.extra.ORIGINATING_UID";
+
+ /**
+ * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
+ * package. Tells the installer UI to skip the confirmation with the user
+ * if the .apk is replacing an existing one.
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, Android
+ * will no longer show an interstitial message about updating existing
+ * applications so this is no longer needed.
+ */
+ @Deprecated
+ public static final String EXTRA_ALLOW_REPLACE
+ = "android.intent.extra.ALLOW_REPLACE";
+
+ /**
+ * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} or
+ * {@link #ACTION_UNINSTALL_PACKAGE}. Specifies that the installer UI should
+ * return to the application the result code of the install/uninstall. The returned result
+ * code will be {@link android.app.Activity#RESULT_OK} on success or
+ * {@link android.app.Activity#RESULT_FIRST_USER} on failure.
+ */
+ public static final String EXTRA_RETURN_RESULT
+ = "android.intent.extra.RETURN_RESULT";
+
+ /**
+ * Package manager install result code. @hide because result codes are not
+ * yet ready to be exposed.
+ */
+ public static final String EXTRA_INSTALL_RESULT
+ = "android.intent.extra.INSTALL_RESULT";
+
+ /**
+ * Activity Action: Launch application uninstaller.
+ * <p>
+ * Input: The data must be a package: URI whose scheme specific part is
+ * the package name of the current installed package to be uninstalled.
+ * You can optionally supply {@link #EXTRA_RETURN_RESULT}.
+ * <p>
+ * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
+ * succeeded.
+ * <p>
+ * Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}
+ * since {@link Build.VERSION_CODES#P}.
+ *
+ * @deprecated Use {@link android.content.pm.PackageInstaller#uninstall(String, IntentSender)}
+ * instead
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
+
+ /**
+ * Specify whether the package should be uninstalled for all users.
+ * @hide because these should not be part of normal application flow.
+ */
+ public static final String EXTRA_UNINSTALL_ALL_USERS
+ = "android.intent.extra.UNINSTALL_ALL_USERS";
+
+ /**
+ * A string that associates with a metadata entry, indicating the last run version of the
+ * platform that was setup.
+ *
+ * @see #ACTION_UPGRADE_SETUP
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
+
+ /**
+ * Activity action: Launch UI to manage the permissions of an app.
+ * <p>
+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose permissions
+ * will be managed by the launched UI.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @see #EXTRA_PACKAGE_NAME
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_APP_PERMISSIONS =
+ "android.intent.action.MANAGE_APP_PERMISSIONS";
+
+ /**
+ * Activity action: Launch UI to manage a specific permissions of an app.
+ * <p>
+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose permission
+ * will be managed by the launched UI.
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_NAME} specifies the (individual) permission
+ * that should be managed by the launched UI.
+ * </p>
+ * <p>
+ * <li> {@link #EXTRA_USER} specifies the UserHandle of the user that owns the app.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @see #EXTRA_PACKAGE_NAME
+ * @see #EXTRA_PERMISSION_NAME
+ * @see #EXTRA_USER
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_APP_PERMISSION =
+ "android.intent.action.MANAGE_APP_PERMISSION";
+
+ /**
+ * Activity action: Launch UI to manage permissions.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_PERMISSIONS =
+ "android.intent.action.MANAGE_PERMISSIONS";
+
+ /**
+ * Activity action: Launch UI to manage auto-revoke state.
+ *
+ * This is equivalent to Intent#ACTION_APPLICATION_DETAILS_SETTINGS
+ *
+ * <p>
+ * Input: {@link Intent#setData data} should be a {@code package}-scheme {@link Uri} with
+ * a package name, whose auto-revoke state will be reviewed (mandatory).
+ * E.g. {@code Uri.fromParts("package", packageName, null) }
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_AUTO_REVOKE_PERMISSIONS =
+ "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+
+ /**
+ * Activity action: Launch UI to manage unused apps (hibernated apps).
+ *
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_UNUSED_APPS =
+ "android.intent.action.MANAGE_UNUSED_APPS";
+
+ /**
+ * Activity action: Launch UI to review permissions for an app.
+ * The system uses this intent if permission review for apps not
+ * supporting the new runtime permissions model is enabled. In
+ * this mode a permission review is required before any of the
+ * app components can run.
+ * <p>
+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose
+ * permissions will be reviewed (mandatory).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_INTENT} specifies a pending intent to
+ * be fired after the permission review (optional).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_REMOTE_CALLBACK} specifies a callback to
+ * be invoked after the permission review (optional).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_RESULT_NEEDED} specifies whether the intent
+ * passed via {@link #EXTRA_INTENT} needs a result (optional).
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @see #EXTRA_PACKAGE_NAME
+ * @see #EXTRA_INTENT
+ * @see #EXTRA_REMOTE_CALLBACK
+ * @see #EXTRA_RESULT_NEEDED
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_PERMISSIONS =
+ "android.intent.action.REVIEW_PERMISSIONS";
+
+ /**
+ * Activity action: Launch UI to show information about the usage
+ * of a given permission group. This action would be handled by apps that
+ * want to show details about how and why given permission group is being
+ * used.
+ * <p>
+ * <strong>Important:</strong>You must protect the activity that handles
+ * this action with the {@link android.Manifest.permission#START_VIEW_PERMISSION_USAGE
+ * START_VIEW_PERMISSION_USAGE} permission to ensure that only the
+ * system can launch this activity. The system will not launch
+ * activities that are not properly protected.
+ *
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group
+ * for which the launched UI would be targeted.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE)
+ public static final String ACTION_VIEW_PERMISSION_USAGE =
+ "android.intent.action.VIEW_PERMISSION_USAGE";
+
+ /**
+ * Activity action: Launch UI to show information about the usage of a given permission group in
+ * a given period. This action would be handled by apps that want to show details about how and
+ * why given permission group is being used.
+ * <p>
+ * <strong>Important:</strong>You must protect the activity that handles this action with the
+ * {@link android.Manifest.permission#START_VIEW_PERMISSION_USAGE} permission to ensure that
+ * only the system can launch this activity. The system will not launch activities that are not
+ * properly protected.
+ *
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group for which the
+ * launched UI would be targeted.
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_ATTRIBUTION_TAGS} specifies the attribution tags for the usage entry.
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_START_TIME} specifies the start time of the period (epoch time in
+ * millis). Both start time and end time are needed and start time must be <= end time.
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_END_TIME} specifies the end time of the period (epoch time in
+ * millis). Both start time and end time are needed and start time must be <= end time.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE)
+ public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD =
+ "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
+
+ /**
+ * Activity action: Launch UI to manage a default app.
+ * <p>
+ * Input: {@link #EXTRA_ROLE_NAME} specifies the role of the default app which will be managed
+ * by the launched UI.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_MANAGE_DEFAULT_APP =
+ "android.intent.action.MANAGE_DEFAULT_APP";
+
+ /**
+ * Intent extra: A role name.
+ * <p>
+ * Type: String
+ * </p>
+ *
+ * @see android.app.role.RoleManager
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
+
+ /**
+ * Activity action: Launch UI to manage special app accesses.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES =
+ "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
+
+ /**
+ * Intent extra: A callback for reporting remote result as a bundle.
+ * <p>
+ * Type: IRemoteCallback
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
+
+ /**
+ * Intent extra: An app package name.
+ * <p>
+ * Type: String
+ * </p>
+ *
+ */
+ 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 as an
+ * extra with {@link #ACTION_MY_PACKAGE_SUSPENDED}.
+ *
+ * <p>The contents of this {@link Bundle} are a contract between the suspended app and the
+ * suspending app, i.e. any app with the permission {@code android.permission.SUSPEND_APPS}.
+ * This is meant to enable the suspended app to better handle the state of being 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
+ * </p>
+ */
+ public static final String EXTRA_SPLIT_NAME = "android.intent.extra.SPLIT_NAME";
+
+ /**
+ * Intent extra: A {@link ComponentName} value.
+ * <p>
+ * Type: String
+ * </p>
+ */
+ public static final String EXTRA_COMPONENT_NAME = "android.intent.extra.COMPONENT_NAME";
+
+ /**
+ * Intent extra: An extra for specifying whether a result is needed.
+ * <p>
+ * Type: boolean
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
+
+ /**
+ * Intent extra: ID of the shortcut used to send the share intent. Will be sent with
+ * {@link #ACTION_SEND}.
+ *
+ * @see ShortcutInfo#getId()
+ *
+ * <p>
+ * Type: String
+ * </p>
+ */
+ public static final String EXTRA_SHORTCUT_ID = "android.intent.extra.shortcut.ID";
+
+ /**
+ * Activity action: Launch UI to manage which apps have a given permission.
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission group
+ * which will be managed by the launched UI.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @see #EXTRA_PERMISSION_NAME
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_PERMISSION_APPS =
+ "android.intent.action.MANAGE_PERMISSION_APPS";
+
+ /**
+ * Intent extra: The name of a permission.
+ * <p>
+ * Type: String
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
+
+ /**
+ * Intent extra: The name of a permission group.
+ * <p>
+ * Type: String
+ * </p>
+ */
+ public static final String EXTRA_PERMISSION_GROUP_NAME =
+ "android.intent.extra.PERMISSION_GROUP_NAME";
+
+ /**
+ * Intent extra: The number of milliseconds.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_DURATION_MILLIS =
+ "android.intent.extra.DURATION_MILLIS";
+
+ /**
+ * Activity action: Launch UI to review app uses of permissions.
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name
+ * that will be displayed by the launched UI. Do not pass both this and
+ * {@link #EXTRA_PERMISSION_GROUP_NAME} .
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group name
+ * that will be displayed by the launched UI. Do not pass both this and
+ * {@link #EXTRA_PERMISSION_NAME}.
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
+ * activity to show (optional). Must be non-negative.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ * <p class="note">
+ * This requires {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS} permission.
+ * </p>
+ *
+ * @see #EXTRA_PERMISSION_NAME
+ * @see #EXTRA_PERMISSION_GROUP_NAME
+ * @see #EXTRA_DURATION_MILLIS
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_PERMISSION_USAGE =
+ "android.intent.action.REVIEW_PERMISSION_USAGE";
+
+ /**
+ * Activity action: Launch UI to review the timeline history of permissions.
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group name
+ * that will be displayed by the launched UI.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ * <p class="note">
+ * This requires {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS} permission.
+ * </p>
+ *
+ * @see #EXTRA_PERMISSION_GROUP_NAME
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_PERMISSION_HISTORY =
+ "android.intent.action.REVIEW_PERMISSION_HISTORY";
+
+ /**
+ * Activity action: Launch UI to review ongoing app uses of permissions.
+ * <p>
+ * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
+ * activity to show (optional). Must be non-negative.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ * <p class="note">
+ * This requires {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS} permission.
+ * </p>
+ *
+ * @see #EXTRA_DURATION_MILLIS
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_ONGOING_PERMISSION_USAGE =
+ "android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE";
+
+ /**
+ * Activity action: Launch UI to review running accessibility services.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES =
+ "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Standard intent broadcast actions (see action variable).
+
+ /**
+ * Broadcast Action: Sent when the device goes to sleep and becomes non-interactive.
+ * <p>
+ * For historical reasons, the name of this broadcast action refers to the power
+ * state of the screen but it is actually sent in response to changes in the
+ * overall interactive state of the device.
+ * </p><p>
+ * This broadcast is sent when the device becomes non-interactive which may have
+ * nothing to do with the screen turning off. To determine the
+ * actual state of the screen, use {@link android.view.Display#getState}.
+ * </p><p>
+ * See {@link android.os.PowerManager#isInteractive} for details.
+ * </p>
+ * You <em>cannot</em> receive this through components declared in
+ * manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
+
+ /**
+ * Broadcast Action: Sent when the device wakes up and becomes interactive.
+ * <p>
+ * For historical reasons, the name of this broadcast action refers to the power
+ * state of the screen but it is actually sent in response to changes in the
+ * overall interactive state of the device.
+ * </p><p>
+ * This broadcast is sent when the device becomes interactive which may have
+ * nothing to do with the screen turning on. To determine the
+ * actual state of the screen, use {@link android.view.Display#getState}.
+ * </p><p>
+ * See {@link android.os.PowerManager#isInteractive} for details.
+ * </p>
+ * You <em>cannot</em> receive this through components declared in
+ * manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
+
+ /**
+ * Broadcast Action: Sent after the system stops dreaming.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * It is only sent to registered receivers.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";
+
+ /**
+ * Broadcast Action: Sent after the system starts dreaming.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * It is only sent to registered receivers.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";
+
+ /**
+ * Broadcast Action: Sent when the user is present after device wakes up (e.g when the
+ * keyguard is gone).
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+
+ /**
+ * Broadcast Action: The current time has changed. Sent every
+ * minute. You <em>cannot</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TIME_TICK = "android.intent.action.TIME_TICK";
+ /**
+ * Broadcast Action: The time was set.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TIME_CHANGED = "android.intent.action.TIME_SET";
+ /**
+ * Broadcast Action: The date has changed.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
+ /**
+ * Broadcast Action: The timezone has changed. The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_TIMEZONE} - The java.util.TimeZone.getID() value identifying the new
+ * time zone.</li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TIMEZONE_CHANGED = "android.intent.action.TIMEZONE_CHANGED";
+ /**
+ * Alarm Changed Action: This is broadcast when the AlarmClock
+ * application's alarm is set or unset. It is used by the
+ * AlarmClock application and the StatusBar service.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final String ACTION_ALARM_CHANGED = "android.intent.action.ALARM_CHANGED";
+
+ /**
+ * Broadcast Action: This is broadcast once, after the user has finished
+ * booting, but while still in the "locked" state. It can be used to perform
+ * application-specific initialization, such as installing alarms. You must
+ * hold the {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}
+ * permission in order to receive this broadcast.
+ * <p>
+ * This broadcast is sent immediately at boot by all devices (regardless of
+ * direct boot support) running {@link android.os.Build.VERSION_CODES#N} or
+ * higher. Upon receipt of this broadcast, the user is still locked and only
+ * device-protected storage can be accessed safely. If you want to access
+ * credential-protected storage, you need to wait for the user to be
+ * unlocked (typically by entering their lock pattern or PIN for the first
+ * time), after which the {@link #ACTION_USER_UNLOCKED} and
+ * {@link #ACTION_BOOT_COMPLETED} broadcasts are sent.
+ * <p>
+ * To receive this broadcast, your receiver component must be marked as
+ * being {@link ComponentInfo#directBootAware}.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ *
+ * @see Context#createDeviceProtectedStorageContext()
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
+
+ /**
+ * Broadcast Action: This is broadcast once, after the user has finished
+ * booting. It can be used to perform application-specific initialization,
+ * such as installing alarms. You must hold the
+ * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission in
+ * order to receive this broadcast.
+ * <p>
+ * This broadcast is sent at boot by all devices (both with and without
+ * direct boot support). Upon receipt of this broadcast, the user is
+ * unlocked and both device-protected and credential-protected storage can
+ * accessed safely.
+ * <p>
+ * If you need to run while the user is still locked (before they've entered
+ * their lock pattern or PIN for the first time), you can listen for the
+ * {@link #ACTION_LOCKED_BOOT_COMPLETED} broadcast.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(includeBackground = true)
+ public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
+
+ /**
+ * Broadcast Action: This is broadcast when a user action should request a
+ * temporary system dialog to dismiss. Some examples of temporary system
+ * dialogs are the notification window-shade and the recent tasks dialog.
+ *
+ * @deprecated This intent is deprecated for third-party applications starting from Android
+ * {@link Build.VERSION_CODES#S} for security reasons. Unauthorized usage by applications
+ * will result in the broadcast intent being dropped for apps targeting API level less than
+ * {@link Build.VERSION_CODES#S} and in a {@link SecurityException} for apps targeting SDK
+ * level {@link Build.VERSION_CODES#S} or higher. Instrumentation initiated from the shell
+ * (eg. tests) is still able to use the intent. The platform will automatically collapse
+ * the proper system dialogs in the proper use-cases. For all others, the user is the one in
+ * control of closing dialogs.
+ *
+ * @see AccessibilityService#GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @RequiresPermission(android.Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS)
+ @Deprecated
+ public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
+ /**
+ * Broadcast Action: Trigger the download and eventual installation
+ * of a package.
+ * <p>Input: {@link #getData} is the URI of the package file to download.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @deprecated This constant has never been used.
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL";
+ /**
+ * Broadcast Action: A new application package has been installed on the
+ * device. The data contains the name of the package. Note that the
+ * newly installed package does <em>not</em> receive this broadcast.
+ * <p>May include the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
+ * <li> {@link #EXTRA_REPLACING} is set to true if this is following
+ * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
+ /**
+ * Broadcast Action: A new version of an application package has been
+ * installed, replacing an existing version that was previously installed.
+ * The data contains the name of the package.
+ * <p>May include the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
+ /**
+ * Broadcast Action: A new version of your application has been installed
+ * over an existing one. This is only sent to the application that was
+ * replaced. It does not contain any additional data; to receive it, just
+ * use an intent filter for this action.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
+ /**
+ * Broadcast Action: An existing application package has been removed from
+ * the device. The data contains the name of the package. The package
+ * that is being removed does <em>not</em> receive this Intent.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid previously assigned
+ * to the package.
+ * <li> {@link #EXTRA_DATA_REMOVED} is set to true if the entire
+ * application -- data and code -- is being removed.
+ * <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
+ * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
+ * <li> {@link #EXTRA_USER_INITIATED} containing boolean field to signal that the application
+ * was removed with the user-initiated action.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
+ /**
+ * Broadcast Action: An existing application package has been removed from
+ * the device. The data contains the name of the package and the visibility
+ * allow list. The package that is being removed does <em>not</em> receive
+ * this Intent.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid previously assigned
+ * to the package.
+ * <li> {@link #EXTRA_DATA_REMOVED} is set to true if the entire
+ * application -- data and code -- is being removed.
+ * <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
+ * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
+ * <li> {@link #EXTRA_USER_INITIATED} containing boolean field to signal
+ * that the application was removed with the user-initiated action.
+ * <li> {@link #EXTRA_VISIBILITY_ALLOW_LIST} containing an int array to
+ * indicate the visibility allow list.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide This broadcast is used internally by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_REMOVED_INTERNAL =
+ "android.intent.action.PACKAGE_REMOVED_INTERNAL";
+ /**
+ * Broadcast Action: An existing application package has been completely
+ * removed from the device. The data contains the name of the package.
+ * This is like {@link #ACTION_PACKAGE_REMOVED}, but only set when
+ * {@link #EXTRA_DATA_REMOVED} is true and
+ * {@link #EXTRA_REPLACING} is false of that broadcast.
+ *
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid previously assigned
+ * to the package.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_FULLY_REMOVED
+ = "android.intent.action.PACKAGE_FULLY_REMOVED";
+ /**
+ * Broadcast Action: An existing application package has been changed (for
+ * example, a component has been enabled or disabled). The data contains
+ * the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name
+ * of the changed components (or the package name itself).
+ * <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
+ * default action of restarting the application.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
+ /**
+ * Broadcast Action: Sent to the system rollback manager when a package
+ * needs to have rollback enabled.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide This broadcast is used internally by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_ENABLE_ROLLBACK =
+ "android.intent.action.PACKAGE_ENABLE_ROLLBACK";
+ /**
+ * Broadcast Action: Sent to the system rollback manager when the rollback for a certain
+ * package needs to be cancelled.
+ *
+ * <p class="note">This intent is sent by PackageManagerService to notify RollbackManager
+ * that enabling a specific rollback has timed out.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CANCEL_ENABLE_ROLLBACK =
+ "android.intent.action.CANCEL_ENABLE_ROLLBACK";
+ /**
+ * Broadcast Action: A rollback has been committed.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. The receiver must hold MANAGE_ROLLBACK permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ROLLBACK_COMMITTED =
+ "android.intent.action.ROLLBACK_COMMITTED";
+ /**
+ * @hide
+ * Broadcast Action: Ask system services if there is any reason to
+ * restart the given package. The data contains the name of the
+ * package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_PACKAGES} String array of all packages to check.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
+ /**
+ * Broadcast Action: The user has restarted a package, and all of its
+ * processes have been killed. All runtime state
+ * associated with it (processes, alarms, notifications, etc) should
+ * be removed. Note that the restarted package does <em>not</em>
+ * receive this broadcast.
+ * The data contains the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+ /**
+ * Broadcast Action: The user has cleared the data of a package. This should
+ * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
+ * its persistent data is erased and this broadcast sent.
+ * Note that the cleared package does <em>not</em>
+ * receive this broadcast. The data contains the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. If the
+ * package whose data was cleared is an uninstalled instant app, then the UID
+ * will be -1. The platform keeps some meta-data associated with instant apps
+ * after they are uninstalled.
+ * <li> {@link #EXTRA_PACKAGE_NAME} containing the package name only if the cleared
+ * data was for an instant app.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
+ /**
+ * Broadcast Action: Packages have been suspended.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been suspended
+ * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been suspended
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It is only sent to registered receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
+ /**
+ * Broadcast Action: Packages have been unsuspended.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been unsuspended
+ * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been unsuspended
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It is only sent to registered receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
+
+ /**
+ * Broadcast Action: Distracting packages have been changed.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been changed.
+ * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been changed.
+ * <li> {@link #EXTRA_DISTRACTION_RESTRICTIONS} the new restrictions set on these packages.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It is only sent to registered receivers.
+ *
+ * @see PackageManager#setDistractingPackageRestrictions(String[], int)
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DISTRACTING_PACKAGES_CHANGED =
+ "android.intent.action.DISTRACTING_PACKAGES_CHANGED";
+
+ /**
+ * 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";
+
+ /**
+ * Activity Action: Started to show more details about why an application was suspended.
+ *
+ * <p>Whenever the system detects an activity launch for a suspended app, this action can
+ * be used to show more details about the reason for suspension.
+ *
+ * <p>Apps holding {@link android.Manifest.permission#SUSPEND_APPS} must declare an activity
+ * handling this intent and protect it with
+ * {@link android.Manifest.permission#SEND_SHOW_SUSPENDED_APP_DETAILS}.
+ *
+ * <p>Includes an extra {@link #EXTRA_PACKAGE_NAME} which is the name of the suspended package.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+ * PersistableBundle, String)
+ * @see PackageManager#isPackageSuspended()
+ * @see #ACTION_PACKAGES_SUSPENDED
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS =
+ "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
+
+ /**
+ * Broadcast Action: Sent to indicate that the user unsuspended a package.
+ *
+ * <p>This can happen when the user taps on the neutral button of the
+ * {@linkplain SuspendDialogInfo suspend-dialog} which was created by using
+ * {@link SuspendDialogInfo#BUTTON_ACTION_UNSUSPEND}. This broadcast is only sent to the
+ * suspending app that originally specified this dialog while calling
+ * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+ * PersistableBundle, SuspendDialogInfo)}.
+ *
+ * <p>Includes an extra {@link #EXTRA_PACKAGE_NAME} which is the name of the package that just
+ * got 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 PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+ * PersistableBundle, SuspendDialogInfo)
+ * @see PackageManager#isPackageSuspended()
+ * @see SuspendDialogInfo#BUTTON_ACTION_MORE_DETAILS
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_UNSUSPENDED_MANUALLY =
+ "android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY";
+
+ /**
+ * 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}.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED";
+
+ /**
+ * Broadcast Action: Sent to the installer package of an application when
+ * that application is first launched (that is the first time it is moved
+ * out of the stopped state). The data contains the name of the package.
+ *
+ * <p>When the application is first launched, the application itself doesn't receive this
+ * broadcast.</p>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH";
+
+ /**
+ * Broadcast Action: Sent to the system package verifier when a package
+ * needs to be verified. The data contains the package URI.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
+
+ /**
+ * Broadcast Action: Sent to the system package verifier when a package is
+ * verified. The data contains the package URI.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
+
+ /**
+ * Broadcast Action: Sent to the system intent filter verifier when an
+ * intent filter needs to be verified. The data contains the filter data
+ * hosts to be verified against.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ * @deprecated Superseded by domain verification APIs. See {@link DomainVerificationManager}.
+ */
+ @Deprecated
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION =
+ "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+
+
+ /**
+ * Broadcast Action: Sent to the system domain verification agent when an app's domains need
+ * to be verified. The data contains the domains hosts to be verified against.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DOMAINS_NEED_VERIFICATION =
+ "android.intent.action.DOMAINS_NEED_VERIFICATION";
+
+ /**
+ * Broadcast Action: Resources for a set of packages (which were
+ * previously unavailable) are currently
+ * available since the media on which they exist is available.
+ * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
+ * list of packages whose availability changed.
+ * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
+ * list of uids of packages whose availability changed.
+ * Note that the
+ * packages in this list do <em>not</em> receive this broadcast.
+ * The specified set of packages are now available on the system.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
+ * whose resources(were previously unavailable) are currently available.
+ * {@link #EXTRA_CHANGED_UID_LIST} is the set of uids of the
+ * packages whose resources(were previously unavailable)
+ * are currently available.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
+
+ /**
+ * Broadcast Action: Resources for a set of packages are currently
+ * unavailable since the media on which they exist is unavailable.
+ * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
+ * list of packages whose availability changed.
+ * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
+ * list of uids of packages whose availability changed.
+ * The specified set of packages can no longer be
+ * launched and are practically unavailable on the system.
+ * <p>Inclues the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
+ * whose resources are no longer available.
+ * {@link #EXTRA_CHANGED_UID_LIST} is the set of packages
+ * whose resources are no longer available.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
+
+ /**
+ * Broadcast Action: preferred activities have changed *explicitly*.
+ *
+ * <p>Note there are cases where a preferred activity is invalidated *implicitly*, e.g.
+ * when an app is installed or uninstalled, but in such cases this broadcast will *not*
+ * be sent.
+ *
+ * {@link #EXTRA_USER_HANDLE} contains the user ID in question.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PREFERRED_ACTIVITY_CHANGED =
+ "android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED";
+
+
+ /**
+ * Broadcast Action: The current system wallpaper has changed. See
+ * {@link android.app.WallpaperManager} for retrieving the new wallpaper.
+ * This should <em>only</em> be used to determine when the wallpaper
+ * has changed to show the new wallpaper to the user. You should certainly
+ * never, in response to this, change the wallpaper or other attributes of
+ * it such as the suggested size. That would be unexpected, right? You'd cause
+ * all kinds of loops, especially if other apps are doing similar things,
+ * right? Of course. So please don't do this.
+ *
+ * @deprecated Modern applications should use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER
+ * WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER} to have the wallpaper
+ * shown behind their UI, rather than watching for this broadcast and
+ * rendering the wallpaper on their own.
+ */
+ @Deprecated @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
+ /**
+ * Broadcast Action: The current device {@link android.content.res.Configuration}
+ * (orientation, locale, etc) has changed. When such a change happens, the
+ * UIs (view hierarchy) will need to be rebuilt based on this new
+ * information; for the most part, applications don't need to worry about
+ * this, because the system will take care of stopping and restarting the
+ * application to make sure it sees the new changes. Some system code that
+ * can not be restarted will need to watch for this action and handle it
+ * appropriately.
+ *
+ * <p class="note">
+ * You <em>cannot</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see android.content.res.Configuration
+ */
+ @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.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
+ /**
+ * Broadcast Action: This is a <em>sticky broadcast</em> containing the
+ * charging state, level, and other information about the battery.
+ * See {@link android.os.BatteryManager} for documentation on the
+ * contents of the Intent.
+ *
+ * <p class="note">
+ * You <em>cannot</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}. See {@link #ACTION_BATTERY_LOW},
+ * {@link #ACTION_BATTERY_OKAY}, {@link #ACTION_POWER_CONNECTED},
+ * and {@link #ACTION_POWER_DISCONNECTED} for distinct battery-related
+ * broadcasts that are sent and can be received through manifest
+ * receivers.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @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.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
+ /**
+ * Broadcast Action: Indicates the battery is now okay after being low.
+ * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has
+ * gone back up to an okay state.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";
+ /**
+ * Broadcast Action: External power has been connected to the device.
+ * This is intended for applications that wish to register specifically to this notification.
+ * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
+ * stay active to receive this notification. This action can be used to implement actions
+ * that wait until power is available to trigger.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED";
+ /**
+ * Broadcast Action: External power has been removed from the device.
+ * This is intended for applications that wish to register specifically to this notification.
+ * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
+ * stay active to receive this notification. This action can be used to implement actions
+ * that wait until power is available to trigger.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_POWER_DISCONNECTED =
+ "android.intent.action.ACTION_POWER_DISCONNECTED";
+ /**
+ * Broadcast Action: Device is shutting down.
+ * This is broadcast when the device is being shut down (completely turned
+ * 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.
+ * <p>May include the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_SHUTDOWN_USERSPACE_ONLY} a boolean that is set to true if this
+ * shutdown is only for userspace processes. If not set, assumed to be false.
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
+ /**
+ * Activity Action: Start this activity to request system shutdown.
+ * The optional boolean extra field {@link #EXTRA_KEY_CONFIRM} can be set to true
+ * to request confirmation from the user before shutting down. The optional boolean
+ * extra field {@link #EXTRA_USER_REQUESTED_SHUTDOWN} can be set to true to
+ * indicate that the shutdown is requested by the user.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * {@hide}
+ */
+ public static final String ACTION_REQUEST_SHUTDOWN
+ = "com.android.internal.intent.action.REQUEST_SHUTDOWN";
+ /**
+ * Broadcast Action: A sticky broadcast that indicates low storage space
+ * condition on the device
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ *
+ * @deprecated if your app targets {@link android.os.Build.VERSION_CODES#O}
+ * or above, this broadcast will no longer be delivered to any
+ * {@link BroadcastReceiver} defined in your manifest. Instead,
+ * apps are strongly encouraged to use the improved
+ * {@link Context#getCacheDir()} behavior so the system can
+ * automatically free up storage when needed.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW";
+ /**
+ * Broadcast Action: Indicates low storage space condition on the device no
+ * longer exists
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ *
+ * @deprecated if your app targets {@link android.os.Build.VERSION_CODES#O}
+ * or above, this broadcast will no longer be delivered to any
+ * {@link BroadcastReceiver} defined in your manifest. Instead,
+ * apps are strongly encouraged to use the improved
+ * {@link Context#getCacheDir()} behavior so the system can
+ * automatically free up storage when needed.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK";
+ /**
+ * Broadcast Action: A sticky broadcast that indicates a storage space full
+ * condition on the device. This is intended for activities that want to be
+ * able to fill the data partition completely, leaving only enough free
+ * space to prevent system-wide SQLite failures.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ *
+ * @deprecated if your app targets {@link android.os.Build.VERSION_CODES#O}
+ * or above, this broadcast will no longer be delivered to any
+ * {@link BroadcastReceiver} defined in your manifest. Instead,
+ * apps are strongly encouraged to use the improved
+ * {@link Context#getCacheDir()} behavior so the system can
+ * automatically free up storage when needed.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_DEVICE_STORAGE_FULL = "android.intent.action.DEVICE_STORAGE_FULL";
+ /**
+ * Broadcast Action: Indicates storage space full condition on the device no
+ * longer exists.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ *
+ * @deprecated if your app targets {@link android.os.Build.VERSION_CODES#O}
+ * or above, this broadcast will no longer be delivered to any
+ * {@link BroadcastReceiver} defined in your manifest. Instead,
+ * apps are strongly encouraged to use the improved
+ * {@link Context#getCacheDir()} behavior so the system can
+ * automatically free up storage when needed.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_DEVICE_STORAGE_NOT_FULL = "android.intent.action.DEVICE_STORAGE_NOT_FULL";
+ /**
+ * Broadcast Action: Indicates low memory condition notification acknowledged by user
+ * and package management should be started.
+ * This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW
+ * notification.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+ /**
+ * Broadcast Action: The device has entered USB Mass Storage mode.
+ * This is used mainly for the USB Settings panel.
+ * Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified
+ * when the SD card file system is mounted or unmounted
+ * @deprecated replaced by android.os.storage.StorageEventListener
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED";
+
+ /**
+ * Broadcast Action: The device has exited USB Mass Storage mode.
+ * This is used mainly for the USB Settings panel.
+ * Apps should listen for ACTION_MEDIA_MOUNTED and ACTION_MEDIA_UNMOUNTED broadcasts to be notified
+ * when the SD card file system is mounted or unmounted
+ * @deprecated replaced by android.os.storage.StorageEventListener
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED";
+
+ /**
+ * Broadcast Action: External media has been removed.
+ * The path to the mount point for the removed media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED";
+
+ /**
+ * Broadcast Action: External media is present, but not mounted at its mount point.
+ * The path to the mount point for the unmounted media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED";
+
+ /**
+ * Broadcast Action: External media is present, and being disk-checked
+ * The path to the mount point for the checking media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
+
+ /**
+ * Broadcast Action: External media is present, but is using an incompatible fs (or is blank)
+ * The path to the mount point for the checking media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_NOFS = "android.intent.action.MEDIA_NOFS";
+
+ /**
+ * Broadcast Action: External media is present and mounted at its mount point.
+ * The path to the mount point for the mounted media is contained in the Intent.mData field.
+ * The Intent contains an extra with name "read-only" and Boolean value to indicate if the
+ * media was mounted read only.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_MOUNTED = "android.intent.action.MEDIA_MOUNTED";
+
+ /**
+ * Broadcast Action: External media is unmounted because it is being shared via USB mass storage.
+ * The path to the mount point for the shared media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED";
+
+ /**
+ * Broadcast Action: External media is no longer being shared via USB mass storage.
+ * The path to the mount point for the previously shared media is contained in the Intent.mData field.
+ *
+ * @hide
+ */
+ public static final String ACTION_MEDIA_UNSHARED = "android.intent.action.MEDIA_UNSHARED";
+
+ /**
+ * Broadcast Action: External media was removed from SD card slot, but mount point was not unmounted.
+ * The path to the mount point for the removed media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
+
+ /**
+ * Broadcast Action: External media is present but cannot be mounted.
+ * The path to the mount point for the unmountable media is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE";
+
+ /**
+ * Broadcast Action: User has expressed the desire to remove the external storage media.
+ * Applications should close all files they have open within the mount point when they receive this intent.
+ * The path to the mount point for the media to be ejected is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_EJECT = "android.intent.action.MEDIA_EJECT";
+
+ /**
+ * Broadcast Action: The media scanner has started scanning a directory.
+ * The path to the directory being scanned is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED";
+
+ /**
+ * Broadcast Action: The media scanner has finished scanning a directory.
+ * The path to the scanned directory is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED";
+
+ /**
+ * Broadcast Action: Request the media scanner to scan a file and add it to
+ * the media database.
+ * <p>
+ * The path to the file is contained in {@link Intent#getData()}.
+ *
+ * @deprecated Callers should migrate to inserting items directly into
+ * {@link MediaStore}, where they will be automatically scanned
+ * after each mutation.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+
+ /**
+ * Broadcast Action: The "Media Button" was pressed. Includes a single
+ * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+ * caused the broadcast.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
+
+ /**
+ * Broadcast Action: The "Camera Button" was pressed. Includes a single
+ * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+ * caused the broadcast.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
+
+ // *** NOTE: @todo(*) The following really should go into a more domain-specific
+ // location; they are not general-purpose actions.
+
+ /**
+ * Broadcast Action: A GTalk connection has been established.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_GTALK_SERVICE_CONNECTED =
+ "android.intent.action.GTALK_CONNECTED";
+
+ /**
+ * Broadcast Action: A GTalk connection has been disconnected.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_GTALK_SERVICE_DISCONNECTED =
+ "android.intent.action.GTALK_DISCONNECTED";
+
+ /**
+ * Broadcast Action: An input method has been changed.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INPUT_METHOD_CHANGED =
+ "android.intent.action.INPUT_METHOD_CHANGED";
+
+ /**
+ * <p>Broadcast Action: The user has switched the phone into or out of Airplane Mode. One or
+ * more radios have been turned off or on. The intent will have the following extra value:</p>
+ * <ul>
+ * <li><em>state</em> - A boolean value indicating whether Airplane Mode is on. If true,
+ * then cell radio and possibly other radios such as bluetooth or WiFi may have also been
+ * turned off</li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE";
+
+ /**
+ * Broadcast Action: Some content providers have parts of their namespace
+ * where they publish new events or items that the user may be especially
+ * interested in. For these things, they may broadcast this action when the
+ * set of interesting items change.
+ *
+ * For example, GmailProvider sends this notification when the set of unread
+ * mail in the inbox changes.
+ *
+ * <p>The data of the intent identifies which part of which provider
+ * changed. When queried through the content resolver, the data URI will
+ * return the data set in question.
+ *
+ * <p>The intent will have the following extra values:
+ * <ul>
+ * <li><em>count</em> - The number of items in the data set. This is the
+ * same as the number of items in the cursor returned by querying the
+ * data URI. </li>
+ * </ul>
+ *
+ * This intent will be sent at boot (if the count is non-zero) and when the
+ * data set changes. It is possible for the data set to change without the
+ * count changing (for example, if a new unread message arrives in the same
+ * sync operation in which a message is archived). The phone should still
+ * ring/vibrate/etc as normal in this case.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PROVIDER_CHANGED =
+ "android.intent.action.PROVIDER_CHANGED";
+
+ /**
+ * Broadcast Action: Wired Headset plugged in or unplugged.
+ *
+ * Same as {@link android.media.AudioManager#ACTION_HEADSET_PLUG}, to be consulted for value
+ * and documentation.
+ * <p>If the minimum SDK version of your application is
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, it is recommended to refer
+ * to the <code>AudioManager</code> constant in your receiver registration code instead.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_HEADSET_PLUG = android.media.AudioManager.ACTION_HEADSET_PLUG;
+
+ /**
+ * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
+ * <ul>
+ * <li><em>state</em> - A boolean value indicating whether the settings is on or off.</li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide
+ */
+ //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ADVANCED_SETTINGS_CHANGED
+ = "android.intent.action.ADVANCED_SETTINGS";
+
+ /**
+ * Broadcast Action: Sent after application restrictions are changed.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APPLICATION_RESTRICTIONS_CHANGED =
+ "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED";
+
+ /**
+ * Broadcast Action: An outgoing call is about to be placed.
+ *
+ * <p>The Intent will have the following extra value:</p>
+ * <ul>
+ * <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> -
+ * the phone number originally intended to be dialed.</li>
+ * </ul>
+ * <p>Once the broadcast is finished, the resultData is used as the actual
+ * number to call. If <code>null</code>, no call will be placed.</p>
+ * <p>It is perfectly acceptable for multiple receivers to process the
+ * outgoing call in turn: for example, a parental control application
+ * might verify that the user is authorized to place the call at that
+ * time, then a number-rewriting application might add an area code if
+ * one was not specified.</p>
+ * <p>For consistency, any receiver whose purpose is to prohibit phone
+ * calls should have a priority of 0, to ensure it will see the final
+ * phone number to be dialed.
+ * Any receiver whose purpose is to rewrite phone numbers to be called
+ * should have a positive priority.
+ * Negative priorities are reserved for the system for this broadcast;
+ * using them may cause problems.</p>
+ * <p>Any BroadcastReceiver receiving this Intent <em>must not</em>
+ * abort the broadcast.</p>
+ * <p>Emergency calls cannot be intercepted using this mechanism, and
+ * other calls cannot be modified to call emergency numbers using this
+ * mechanism.
+ * <p>Some apps (such as VoIP apps) may want to redirect the outgoing
+ * call to use their own service instead. Those apps should first prevent
+ * the call from being placed by setting resultData to <code>null</code>
+ * and then start their own app to make the call.
+ * <p>You must hold the
+ * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
+ * permission to receive this Intent.</p>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * <p class="note">If the user has chosen a {@link android.telecom.CallRedirectionService} to
+ * handle redirection of outgoing calls, this intent will NOT be sent as an ordered broadcast.
+ * This means that attempts to re-write the outgoing call by other apps using this intent will
+ * be ignored.
+ * </p>
+ *
+ * @deprecated Apps that redirect outgoing calls should use the
+ * {@link android.telecom.CallRedirectionService} API. Apps that perform call screening
+ * should use the {@link android.telecom.CallScreeningService} API. Apps which need to be
+ * notified of basic call state should use
+ * {@link android.telephony.PhoneStateListener#onCallStateChanged(int, String)} to determine
+ * when a new outgoing call is placed.
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NEW_OUTGOING_CALL =
+ "android.intent.action.NEW_OUTGOING_CALL";
+
+ /**
+ * Broadcast Action: Have the device reboot. This is only for use by
+ * system code.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_REBOOT =
+ "android.intent.action.REBOOT";
+
+ /**
+ * Broadcast Action: A sticky broadcast for changes in the physical
+ * docking state of the device.
+ *
+ * <p>The intent will have the following extra values:
+ * <ul>
+ * <li><em>{@link #EXTRA_DOCK_STATE}</em> - the current dock
+ * state, indicating which dock the device is physically in.</li>
+ * </ul>
+ * <p>This is intended for monitoring the current physical dock state.
+ * See {@link android.app.UiModeManager} for the normal API dealing with
+ * dock mode changes.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DOCK_EVENT =
+ "android.intent.action.DOCK_EVENT";
+
+ /**
+ * Broadcast Action: A broadcast when idle maintenance can be started.
+ * This means that the user is not interacting with the device and is
+ * not expected to do so soon. Typical use of the idle maintenance is
+ * to perform somehow expensive tasks that can be postponed at a moment
+ * when they will not degrade user experience.
+ * <p>
+ * <p class="note">In order to keep the device responsive in case of an
+ * unexpected user interaction, implementations of a maintenance task
+ * should be interruptible. In such a scenario a broadcast with action
+ * {@link #ACTION_IDLE_MAINTENANCE_END} will be sent. In other words, you
+ * should not do the maintenance work in
+ * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather start a
+ * maintenance service by {@link Context#startService(Intent)}. Also
+ * you should hold a wake lock while your maintenance service is running
+ * to prevent the device going to sleep.
+ * </p>
+ * <p>
+ * <p class="note">This is a protected intent that can only be sent by
+ * the system.
+ * </p>
+ *
+ * @see #ACTION_IDLE_MAINTENANCE_END
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_MAINTENANCE_START =
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_START";
+
+ /**
+ * Broadcast Action: A broadcast when idle maintenance should be stopped.
+ * This means that the user was not interacting with the device as a result
+ * of which a broadcast with action {@link #ACTION_IDLE_MAINTENANCE_START}
+ * was sent and now the user started interacting with the device. Typical
+ * use of the idle maintenance is to perform somehow expensive tasks that
+ * can be postponed at a moment when they will not degrade user experience.
+ * <p>
+ * <p class="note">In order to keep the device responsive in case of an
+ * unexpected user interaction, implementations of a maintenance task
+ * should be interruptible. Hence, on receiving a broadcast with this
+ * action, the maintenance task should be interrupted as soon as possible.
+ * In other words, you should not do the maintenance work in
+ * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather stop the
+ * maintenance service that was started on receiving of
+ * {@link #ACTION_IDLE_MAINTENANCE_START}.Also you should release the wake
+ * lock you acquired when your maintenance service started.
+ * </p>
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see #ACTION_IDLE_MAINTENANCE_START
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_MAINTENANCE_END =
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_END";
+
+ /**
+ * Broadcast Action: a remote intent is to be broadcasted.
+ *
+ * A remote intent is used for remote RPC between devices. The remote intent
+ * is serialized and sent from one device to another device. The receiving
+ * device parses the remote intent and broadcasts it. Note that anyone can
+ * broadcast a remote intent. However, if the intent receiver of the remote intent
+ * does not trust intent broadcasts from arbitrary intent senders, it should require
+ * the sender to hold certain permissions so only trusted sender's broadcast will be
+ * let through.
+ * @hide
+ */
+ public static final String ACTION_REMOTE_INTENT =
+ "com.google.android.c2dm.intent.RECEIVE";
+
+ /**
+ * Broadcast Action: This is broadcast once when the user is booting after a
+ * system update. It can be used to perform cleanup or upgrades after a
+ * system update.
+ * <p>
+ * This broadcast is sent after the {@link #ACTION_LOCKED_BOOT_COMPLETED}
+ * broadcast but before the {@link #ACTION_BOOT_COMPLETED} broadcast. It's
+ * only sent when the {@link Build#FINGERPRINT} has changed, and it's only
+ * sent to receivers in the system image.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_PRE_BOOT_COMPLETED =
+ "android.intent.action.PRE_BOOT_COMPLETED";
+
+ /**
+ * Broadcast to a specific application to query any supported restrictions to impose
+ * on restricted users. The broadcast intent contains an extra
+ * {@link #EXTRA_RESTRICTIONS_BUNDLE} with the currently persisted
+ * restrictions as a Bundle of key/value pairs. The value types can be Boolean, String or
+ * String[] depending on the restriction type.<p/>
+ * The response should contain an extra {@link #EXTRA_RESTRICTIONS_LIST},
+ * which is of type <code>ArrayList<RestrictionEntry></code>. It can also
+ * contain an extra {@link #EXTRA_RESTRICTIONS_INTENT}, which is of type <code>Intent</code>.
+ * The activity specified by that intent will be launched for a result which must contain
+ * one of the extras {@link #EXTRA_RESTRICTIONS_LIST} or {@link #EXTRA_RESTRICTIONS_BUNDLE}.
+ * The keys and values of the returned restrictions will be persisted.
+ * @see RestrictionEntry
+ */
+ public static final String ACTION_GET_RESTRICTION_ENTRIES =
+ "android.intent.action.GET_RESTRICTION_ENTRIES";
+
+ /**
+ * Sent the first time a user is starting, to allow system apps to
+ * perform one time initialization. (This will not be seen by third
+ * party applications because a newly initialized user does not have any
+ * third party applications installed for it.) This is sent early in
+ * starting the user, around the time the home app is started, before
+ * {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground
+ * broadcast, since it is part of a visible user interaction; be as quick
+ * as possible when handling it.
+ */
+ public static final String ACTION_USER_INITIALIZE =
+ "android.intent.action.USER_INITIALIZE";
+
+ /**
+ * Sent when a user switch is happening, causing the process's user to be
+ * brought to the foreground. This is only sent to receivers registered
+ * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver}. It is sent to the user that is going to the
+ * foreground. This is sent as a foreground
+ * broadcast, since it is part of a visible user interaction; be as quick
+ * as possible when handling it.
+ */
+ public static final String ACTION_USER_FOREGROUND =
+ "android.intent.action.USER_FOREGROUND";
+
+ /**
+ * Sent when a user switch is happening, causing the process's user to be
+ * sent to the background. This is only sent to receivers registered
+ * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver}. It is sent to the user that is going to the
+ * background. This is sent as a foreground
+ * broadcast, since it is part of a visible user interaction; be as quick
+ * as possible when handling it.
+ */
+ public static final String ACTION_USER_BACKGROUND =
+ "android.intent.action.USER_BACKGROUND";
+
+ /**
+ * Broadcast sent to the system when a user is added. Carries an extra
+ * EXTRA_USER_HANDLE that has the userHandle of the new user. It is sent to
+ * all running users. You must hold
+ * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_USER_ADDED =
+ "android.intent.action.USER_ADDED";
+
+ /**
+ * Broadcast sent by the system when a user is started. Carries an extra
+ * {@link EXTRA_USER_HANDLE} that has the userHandle of the user. This is only sent to
+ * registered receivers, not manifest receivers. It is sent to the user
+ * that has been started. This is sent as a foreground
+ * broadcast, since it is part of a visible user interaction; be as quick
+ * as possible when handling it.
+ * @hide
+ */
+ public static final String ACTION_USER_STARTED =
+ "android.intent.action.USER_STARTED";
+
+ /**
+ * Broadcast sent when a user is in the process of starting. Carries an extra
+ * {@link EXTRA_USER_HANDLE} that has the userHandle of the user. This is only
+ * sent to registered receivers, not manifest receivers. It is sent to all
+ * users (including the one that is being started). You must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} to receive
+ * this broadcast. This is sent as a background broadcast, since
+ * its result is not part of the primary UX flow; to safely keep track of
+ * started/stopped state of a user you can use this in conjunction with
+ * {@link #ACTION_USER_STOPPING}. It is <b>not</b> generally safe to use with
+ * other user state broadcasts since those are foreground broadcasts so can
+ * execute in a different order.
+ * @hide
+ */
+ public static final String ACTION_USER_STARTING =
+ "android.intent.action.USER_STARTING";
+
+ /**
+ * Broadcast sent when a user is going to be stopped. Carries an extra
+ * {@link EXTRA_USER_HANDLE} that has the userHandle of the user. This is only
+ * sent to registered receivers, not manifest receivers. It is sent to all
+ * users (including the one that is being stopped). You must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} to receive
+ * this broadcast. The user will not stop until all receivers have
+ * handled the broadcast. This is sent as a background broadcast, since
+ * its result is not part of the primary UX flow; to safely keep track of
+ * started/stopped state of a user you can use this in conjunction with
+ * {@link #ACTION_USER_STARTING}. It is <b>not</b> generally safe to use with
+ * other user state broadcasts since those are foreground broadcasts so can
+ * execute in a different order.
+ * @hide
+ */
+ public static final String ACTION_USER_STOPPING =
+ "android.intent.action.USER_STOPPING";
+
+ /**
+ * Broadcast sent to the system when a user is stopped. Carries an extra
+ * {@link EXTRA_USER_HANDLE} that has the userHandle of the user. This is similar to
+ * {@link #ACTION_PACKAGE_RESTARTED}, but for an entire user instead of a
+ * specific package. This is only sent to registered receivers, not manifest
+ * receivers. It is sent to all running users <em>except</em> the one that
+ * has just been stopped (which is no longer running).
+ * @hide
+ */
+ @TestApi
+ public static final String ACTION_USER_STOPPED =
+ "android.intent.action.USER_STOPPED";
+
+ /**
+ * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user. It is sent to all running users except the
+ * one that has been removed. The user will not be completely removed until all receivers have
+ * handled the broadcast. You must hold
+ * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_USER_REMOVED =
+ "android.intent.action.USER_REMOVED";
+
+ /**
+ * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user to become the current one. This is only sent to
+ * registered receivers, not manifest receivers. It is sent to all running users.
+ * You must hold
+ * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final String ACTION_USER_SWITCHED =
+ "android.intent.action.USER_SWITCHED";
+
+ /**
+ * Broadcast Action: Sent when the credential-encrypted private storage has
+ * become unlocked for the target user. This is only sent to registered
+ * receivers, not manifest receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
+
+ /**
+ * Broadcast sent to the system when a user's information changes. Carries an extra
+ * {@link #EXTRA_USER_HANDLE} to indicate which user's information changed.
+ * This is only sent to registered receivers, not manifest receivers. It is sent to all users.
+ * @hide
+ */
+ public static final String ACTION_USER_INFO_CHANGED =
+ "android.intent.action.USER_INFO_CHANGED";
+
+ /**
+ * Broadcast sent to the primary user when an associated managed profile is added (the profile
+ * was created and is ready to be used). Carries an extra {@link #EXTRA_USER} that specifies
+ * the UserHandle of the profile that was added. Only applications (for example Launchers)
+ * that need to display merged content across both primary and managed profiles need to
+ * worry about this broadcast. This is only sent to registered receivers,
+ * not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_ADDED =
+ "android.intent.action.MANAGED_PROFILE_ADDED";
+
+ /**
+ * Broadcast sent to the primary user when an associated managed profile is removed. Carries an
+ * extra {@link #EXTRA_USER} that specifies the UserHandle of the profile that was removed.
+ * Only applications (for example Launchers) that need to display merged content across both
+ * primary and managed profiles need to worry about this broadcast. This is only sent to
+ * registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_REMOVED =
+ "android.intent.action.MANAGED_PROFILE_REMOVED";
+
+ /**
+ * Broadcast sent to the primary user when the credential-encrypted private storage for
+ * an associated managed profile is unlocked. Carries an extra {@link #EXTRA_USER} that
+ * specifies the UserHandle of the profile that was unlocked. Only applications (for example
+ * Launchers) that need to display merged content across both primary and managed profiles
+ * need to worry about this broadcast. This is only sent to registered receivers,
+ * not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_UNLOCKED =
+ "android.intent.action.MANAGED_PROFILE_UNLOCKED";
+
+ /**
+ * Broadcast sent to the primary user when an associated managed profile has become available.
+ * Currently this includes when the user disables quiet mode for the profile. Carries an extra
+ * {@link #EXTRA_USER} that specifies the UserHandle of the profile. When quiet mode is changed,
+ * this broadcast will carry a boolean extra {@link #EXTRA_QUIET_MODE} indicating the new state
+ * of quiet mode. This is only sent to registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_AVAILABLE =
+ "android.intent.action.MANAGED_PROFILE_AVAILABLE";
+
+ /**
+ * Broadcast sent to the primary user when an associated managed profile has become unavailable.
+ * Currently this includes when the user enables quiet mode for the profile. Carries an extra
+ * {@link #EXTRA_USER} that specifies the UserHandle of the profile. When quiet mode is changed,
+ * this broadcast will carry a boolean extra {@link #EXTRA_QUIET_MODE} indicating the new state
+ * of quiet mode. This is only sent to registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_UNAVAILABLE =
+ "android.intent.action.MANAGED_PROFILE_UNAVAILABLE";
+
+ /**
+ * Broadcast sent to the parent user when an associated profile has been started and unlocked.
+ * Carries an extra {@link #EXTRA_USER} that specifies the {@link UserHandle} of the profile.
+ * This is only sent to registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_PROFILE_ACCESSIBLE =
+ "android.intent.action.PROFILE_ACCESSIBLE";
+
+ /**
+ * Broadcast sent to the parent user when an associated profile has stopped.
+ * Carries an extra {@link #EXTRA_USER} that specifies the {@link UserHandle} of the profile.
+ * This is only sent to registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_PROFILE_INACCESSIBLE =
+ "android.intent.action.PROFILE_INACCESSIBLE";
+
+ /**
+ * Broadcast sent to the system user when the 'device locked' state changes for any user.
+ * Carries an extra {@link #EXTRA_USER_HANDLE} that specifies the ID of the user for which
+ * the device was locked or unlocked.
+ *
+ * This is only sent to registered receivers.
+ *
+ * @hide
+ */
+ public static final String ACTION_DEVICE_LOCKED_CHANGED =
+ "android.intent.action.DEVICE_LOCKED_CHANGED";
+
+ /**
+ * Sent when the user taps on the clock widget in the system's "quick settings" area.
+ */
+ public static final String ACTION_QUICK_CLOCK =
+ "android.intent.action.QUICK_CLOCK";
+
+ /**
+ * Activity Action: Shows the brightness setting dialog.
+ * @hide
+ */
+ public static final String ACTION_SHOW_BRIGHTNESS_DIALOG =
+ "com.android.intent.action.SHOW_BRIGHTNESS_DIALOG";
+
+ /**
+ * Broadcast Action: A global button was pressed. Includes a single
+ * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+ * caused the broadcast.
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
+
+ /**
+ * Broadcast Action: Sent when media resource is granted.
+ * <p>
+ * {@link #EXTRA_PACKAGES} specifies the packages on the process holding the media resource
+ * granted.
+ * </p>
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ * <p class="note">
+ * This requires {@link android.Manifest.permission#RECEIVE_MEDIA_RESOURCE_USAGE} permission.
+ * </p>
+ *
+ * @hide
+ */
+ public static final String ACTION_MEDIA_RESOURCE_GRANTED =
+ "android.intent.action.MEDIA_RESOURCE_GRANTED";
+
+ /**
+ * Broadcast Action: An overlay package has changed. The data contains the
+ * name of the overlay package which has changed. This is broadcast on all
+ * changes to the OverlayInfo returned by {@link
+ * android.content.om.IOverlayManager#getOverlayInfo(String, int)}. The
+ * most common change is a state change that will change whether the
+ * overlay is enabled or not.
+ * @hide
+ */
+ public static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+
+ /**
+ * Activity Action: Allow the user to select and return one or more existing
+ * documents. When invoked, the system will display the various
+ * {@link DocumentsProvider} instances installed on the device, letting the
+ * user interactively navigate through them. These documents include local
+ * media, such as photos and video, and documents provided by installed
+ * cloud storage providers.
+ * <p>
+ * Each document is represented as a {@code content://} URI backed by a
+ * {@link DocumentsProvider}, which can be opened as a stream with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for
+ * {@link android.provider.DocumentsContract.Document} metadata.
+ * <p>
+ * All selected documents are returned to the calling application with
+ * persistable read and write permission grants. If you want to maintain
+ * access to the documents across device reboots, you need to explicitly
+ * take the persistable permissions using
+ * {@link ContentResolver#takePersistableUriPermission(Uri, int)}.
+ * <p>
+ * Callers must indicate the acceptable document MIME types through
+ * {@link #setType(String)}. For example, to select photos, use
+ * {@code image/*}. If multiple disjoint MIME types are acceptable, define
+ * them in {@link #EXTRA_MIME_TYPES} and {@link #setType(String)} to
+ * {@literal *}/*.
+ * <p>
+ * If the caller can handle multiple returned items (the user performing
+ * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
+ * to indicate this.
+ * <p>
+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+ * URIs that can be opened with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}.
+ * <p>
+ * Callers can set a document URI through
+ * {@link DocumentsContract#EXTRA_INITIAL_URI} to indicate the initial
+ * location of documents navigator. System will do its best to launch the
+ * navigator in the specified document if it's a folder, or the folder that
+ * contains the specified document if not.
+ * <p>
+ * Output: The URI of the item that was picked, returned in
+ * {@link #getData()}. This must be a {@code content://} URI so that any
+ * receiver can access it. If multiple documents were selected, they are
+ * returned in {@link #getClipData()}.
+ *
+ * @see DocumentsContract
+ * @see #ACTION_OPEN_DOCUMENT_TREE
+ * @see #ACTION_CREATE_DOCUMENT
+ * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
+
+ /**
+ * Activity Action: Allow the user to create a new document. When invoked,
+ * the system will display the various {@link DocumentsProvider} instances
+ * installed on the device, letting the user navigate through them. The
+ * returned document may be a newly created document with no content, or it
+ * may be an existing document with the requested MIME type.
+ * <p>
+ * Each document is represented as a {@code content://} URI backed by a
+ * {@link DocumentsProvider}, which can be opened as a stream with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for
+ * {@link android.provider.DocumentsContract.Document} metadata.
+ * <p>
+ * Callers must indicate the concrete MIME type of the document being
+ * created by setting {@link #setType(String)}. This MIME type cannot be
+ * changed after the document is created.
+ * <p>
+ * Callers can provide an initial display name through {@link #EXTRA_TITLE},
+ * but the user may change this value before creating the file.
+ * <p>
+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+ * URIs that can be opened with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}.
+ * <p>
+ * Callers can set a document URI through
+ * {@link DocumentsContract#EXTRA_INITIAL_URI} to indicate the initial
+ * location of documents navigator. System will do its best to launch the
+ * navigator in the specified document if it's a folder, or the folder that
+ * contains the specified document if not.
+ * <p>
+ * Output: The URI of the item that was created. This must be a
+ * {@code content://} URI so that any receiver can access it.
+ *
+ * @see DocumentsContract
+ * @see #ACTION_OPEN_DOCUMENT
+ * @see #ACTION_OPEN_DOCUMENT_TREE
+ * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+
+ /**
+ * Activity Action: Allow the user to pick a directory subtree. When
+ * invoked, the system will display the various {@link DocumentsProvider}
+ * instances installed on the device, letting the user navigate through
+ * them. Apps can fully manage documents within the returned directory.
+ * <p>
+ * To gain access to descendant (child, grandchild, etc) documents, use
+ * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
+ * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)}
+ * with the returned URI.
+ * <p>
+ * Callers can set a document URI through
+ * {@link DocumentsContract#EXTRA_INITIAL_URI} to indicate the initial
+ * location of documents navigator. System will do its best to launch the
+ * navigator in the specified document if it's a folder, or the folder that
+ * contains the specified document if not.
+ * <p>
+ * Output: The URI representing the selected directory tree.
+ *
+ * @see DocumentsContract
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String
+ ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+
+
+ /**
+ * Activity Action: Perform text translation.
+ * <p>
+ * Input: {@link #EXTRA_TEXT getCharSequence(EXTRA_TEXT)} is the text to translate.
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_TRANSLATE = "android.intent.action.TRANSLATE";
+
+ /**
+ * Activity Action: Define the meaning of the selected word(s).
+ * <p>
+ * Input: {@link #EXTRA_TEXT getCharSequence(EXTRA_TEXT)} is the text to define.
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DEFINE = "android.intent.action.DEFINE";
+
+ /**
+ * Broadcast Action: List of dynamic sensor is changed due to new sensor being connected or
+ * exisiting sensor being disconnected.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ *
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String
+ ACTION_DYNAMIC_SENSOR_CHANGED = "android.intent.action.DYNAMIC_SENSOR_CHANGED";
+
+ /**
+ * Deprecated - use ACTION_FACTORY_RESET instead.
+ * @hide
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
+
+ /**
+ * Broadcast intent sent by the RecoverySystem to inform listeners that a global clear (wipe)
+ * is about to be performed.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MASTER_CLEAR_NOTIFICATION
+ = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
+
+ /**
+ * Boolean intent extra to be used with {@link #ACTION_MASTER_CLEAR} in order to force a factory
+ * reset even if {@link android.os.UserManager#DISALLOW_FACTORY_RESET} is set.
+ *
+ * <p>Deprecated - use {@link #EXTRA_FORCE_FACTORY_RESET} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_FORCE_MASTER_CLEAR =
+ "android.intent.extra.FORCE_MASTER_CLEAR";
+
+ /**
+ * A broadcast action to trigger a factory reset.
+ *
+ * <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
+ * reason for the factory reset should be specified as {@link #EXTRA_REASON}.
+ *
+ * <p>Not for use by third-party applications.
+ *
+ * @see #EXTRA_FORCE_FACTORY_RESET
+ *
+ * {@hide}
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";
+
+ /**
+ * Boolean intent extra to be used with {@link #ACTION_MASTER_CLEAR} in order to force a factory
+ * reset even if {@link android.os.UserManager#DISALLOW_FACTORY_RESET} is set.
+ *
+ * <p>Not for use by third-party applications.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_FORCE_FACTORY_RESET =
+ "android.intent.extra.FORCE_FACTORY_RESET";
+
+ /**
+ * Broadcast action: report that a settings element is being restored from backup. The intent
+ * contains four extras: EXTRA_SETTING_NAME is a string naming the restored setting,
+ * EXTRA_SETTING_NEW_VALUE is the value being restored, EXTRA_SETTING_PREVIOUS_VALUE
+ * is the value of that settings entry prior to the restore operation, and
+ * EXTRA_SETTING_RESTORED_FROM_SDK_INT is the version of the SDK that the setting has been
+ * restored from (corresponds to {@link android.os.Build.VERSION#SDK_INT}). The first three
+ * values are represented as strings, the fourth one as int.
+ *
+ * <p>This broadcast is sent only for settings provider entries known to require special handling
+ * around restore time. These entries are found in the BROADCAST_ON_RESTORE table within
+ * the provider's backup agent implementation.
+ *
+ * @see #EXTRA_SETTING_NAME
+ * @see #EXTRA_SETTING_PREVIOUS_VALUE
+ * @see #EXTRA_SETTING_NEW_VALUE
+ * @see #EXTRA_SETTING_RESTORED_FROM_SDK_INT
+ * {@hide}
+ */
+ public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED";
+
+ /** {@hide} */
+ public static final String EXTRA_SETTING_NAME = "setting_name";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_RESTORED_FROM_SDK_INT = "restored_from_sdk_int";
+
+ /**
+ * Activity Action: Process a piece of text.
+ * <p>Input: {@link #EXTRA_PROCESS_TEXT} contains the text to be processed.
+ * {@link #EXTRA_PROCESS_TEXT_READONLY} states if the resulting text will be read-only.</p>
+ * <p>Output: {@link #EXTRA_PROCESS_TEXT} contains the processed text.</p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
+
+ /**
+ * Broadcast Action: The sim card state has changed.
+ * For more details see TelephonyIntents.ACTION_SIM_STATE_CHANGED. This is here
+ * because TelephonyIntents is an internal class.
+ * The intent will have following extras.</p>
+ * <p>
+ * @see #EXTRA_SIM_STATE
+ * @see #EXTRA_SIM_LOCKED_REASON
+ * @see #EXTRA_REBROADCAST_ON_UNLOCK
+ *
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
+ * {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
+
+ /**
+ * The extra used with {@link #ACTION_SIM_STATE_CHANGED} for broadcasting SIM STATE.
+ * This will have one of the following intent values.
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_NOT_READY
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_PRESENT
+ * @see #SIM_STATE_CARD_IO_ERROR
+ * @see #SIM_STATE_CARD_RESTRICTED
+ * @see #SIM_STATE_LOCKED
+ * @see #SIM_STATE_READY
+ * @see #SIM_STATE_IMSI
+ * @see #SIM_STATE_LOADED
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String EXTRA_SIM_STATE = "ss";
+
+ /**
+ * The intent value UNKNOWN represents the SIM state unknown
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_UNKNOWN = "UNKNOWN";
+
+ /**
+ * The intent value NOT_READY means that the SIM is not ready eg. radio is off or powering on
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_NOT_READY = "NOT_READY";
+
+ /**
+ * The intent value ABSENT means the SIM card is missing
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_ABSENT = "ABSENT";
+
+ /**
+ * The intent value PRESENT means the device has a SIM card inserted
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_PRESENT = "PRESENT";
+
+ /**
+ * The intent value CARD_IO_ERROR means for three consecutive times there was SIM IO error
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ static public final String SIM_STATE_CARD_IO_ERROR = "CARD_IO_ERROR";
+
+ /**
+ * The intent value CARD_RESTRICTED means card is present but not usable due to carrier
+ * restrictions
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ static public final String SIM_STATE_CARD_RESTRICTED = "CARD_RESTRICTED";
+
+ /**
+ * The intent value LOCKED means the SIM is locked by PIN or by network
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_LOCKED = "LOCKED";
+
+ /**
+ * The intent value READY means the SIM is ready to be accessed
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_READY = "READY";
+
+ /**
+ * The intent value IMSI means the SIM IMSI is ready in property
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_IMSI = "IMSI";
+
+ /**
+ * The intent value LOADED means all SIM records, including IMSI, are loaded
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ public static final String SIM_STATE_LOADED = "LOADED";
+
+ /**
+ * The extra used with {@link #ACTION_SIM_STATE_CHANGED} for broadcasting SIM STATE.
+ * This extra will have one of the following intent values.
+ * <p>
+ * @see #SIM_LOCKED_ON_PIN
+ * @see #SIM_LOCKED_ON_PUK
+ * @see #SIM_LOCKED_NETWORK
+ * @see #SIM_ABSENT_ON_PERM_DISABLED
+ *
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ public static final String EXTRA_SIM_LOCKED_REASON = "reason";
+
+ /**
+ * The intent value PIN means the SIM is locked on PIN1
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ public static final String SIM_LOCKED_ON_PIN = "PIN";
+
+ /**
+ * The intent value PUK means the SIM is locked on PUK1
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ /* PUK means ICC is locked on PUK1 */
+ public static final String SIM_LOCKED_ON_PUK = "PUK";
+
+ /**
+ * The intent value NETWORK means the SIM is locked on NETWORK PERSONALIZATION
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ public static final String SIM_LOCKED_NETWORK = "NETWORK";
+
+ /**
+ * The intent value PERM_DISABLED means SIM is permanently disabled due to puk fails
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
+
+ /**
+ * The extra used with {@link #ACTION_SIM_STATE_CHANGED} for indicating whether this broadcast
+ * is a rebroadcast on unlock. Defaults to {@code false} if not specified.
+ *
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
+ * {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ public static final String EXTRA_REBROADCAST_ON_UNLOCK = "rebroadcastOnUnlock";
+
+ /**
+ * Broadcast Action: indicate that the phone service state has changed.
+ * The intent will have the following extra values:</p>
+ * <p>
+ * @see #EXTRA_VOICE_REG_STATE
+ * @see #EXTRA_DATA_REG_STATE
+ * @see #EXTRA_VOICE_ROAMING_TYPE
+ * @see #EXTRA_DATA_ROAMING_TYPE
+ * @see #EXTRA_OPERATOR_ALPHA_LONG
+ * @see #EXTRA_OPERATOR_ALPHA_SHORT
+ * @see #EXTRA_OPERATOR_NUMERIC
+ * @see #EXTRA_DATA_OPERATOR_ALPHA_LONG
+ * @see #EXTRA_DATA_OPERATOR_ALPHA_SHORT
+ * @see #EXTRA_DATA_OPERATOR_NUMERIC
+ * @see #EXTRA_MANUAL
+ * @see #EXTRA_VOICE_RADIO_TECH
+ * @see #EXTRA_DATA_RADIO_TECH
+ * @see #EXTRA_CSS_INDICATOR
+ * @see #EXTRA_NETWORK_ID
+ * @see #EXTRA_SYSTEM_ID
+ * @see #EXTRA_CDMA_ROAMING_INDICATOR
+ * @see #EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR
+ * @see #EXTRA_EMERGENCY_ONLY
+ * @see #EXTRA_IS_DATA_ROAMING_FROM_REGISTRATION
+ * @see #EXTRA_IS_USING_CARRIER_AGGREGATION
+ * @see #EXTRA_LTE_EARFCN_RSRP_BOOST
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable} and the helper
+ * functions {@code ServiceStateTable.getUriForSubscriptionIdAndField} and
+ * {@code ServiceStateTable.getUriForSubscriptionId} to subscribe to changes to the ServiceState
+ * for a given subscription id and field with a ContentObserver or using JobScheduler.
+ */
+ @Deprecated
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
+
+ /**
+ * Used by {@link services.core.java.com.android.server.pm.DataLoaderManagerService}
+ * for querying Data Loader Service providers. Data loader service providers register this
+ * intent filter in their manifests, so that they can be looked up and bound to by
+ * {@code DataLoaderManagerService}.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * Data loader service providers must be privileged apps.
+ * See {@link com.android.server.pm.PackageManagerShellCommandDataLoader} as an example of such
+ * data loader service provider.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
+
+ /**
+ * An int extra used with {@link #ACTION_SERVICE_STATE} which indicates voice registration
+ * state.
+ * @see android.telephony.ServiceState#STATE_EMERGENCY_ONLY
+ * @see android.telephony.ServiceState#STATE_IN_SERVICE
+ * @see android.telephony.ServiceState#STATE_OUT_OF_SERVICE
+ * @see android.telephony.ServiceState#STATE_POWER_OFF
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#VOICE_REG_STATE}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_VOICE_REG_STATE = "voiceRegState";
+
+ /**
+ * An int extra used with {@link #ACTION_SERVICE_STATE} which indicates data registration state.
+ * @see android.telephony.ServiceState#STATE_EMERGENCY_ONLY
+ * @see android.telephony.ServiceState#STATE_IN_SERVICE
+ * @see android.telephony.ServiceState#STATE_OUT_OF_SERVICE
+ * @see android.telephony.ServiceState#STATE_POWER_OFF
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#DATA_REG_STATE}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_REG_STATE = "dataRegState";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which indicates the voice roaming
+ * type.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#VOICE_ROAMING_TYPE}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_VOICE_ROAMING_TYPE = "voiceRoamingType";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which indicates the data roaming
+ * type.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#DATA_ROAMING_TYPE}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_ROAMING_TYPE = "dataRoamingType";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered voice operator name in long alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#VOICE_OPERATOR_ALPHA_LONG}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_OPERATOR_ALPHA_LONG = "operator-alpha-long";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered voice operator name in short alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#VOICE_OPERATOR_ALPHA_SHORT}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_OPERATOR_ALPHA_SHORT = "operator-alpha-short";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} containing the MCC
+ * (Mobile Country Code, 3 digits) and MNC (Mobile Network code, 2-3 digits) for the mobile
+ * network.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#VOICE_OPERATOR_NUMERIC}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_OPERATOR_NUMERIC = "operator-numeric";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered data operator name in long alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#DATA_OPERATOR_ALPHA_LONG}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_OPERATOR_ALPHA_LONG = "data-operator-alpha-long";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered data operator name in short alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#DATA_OPERATOR_ALPHA_SHORT}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_OPERATOR_ALPHA_SHORT = "data-operator-alpha-short";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} containing the MCC
+ * (Mobile Country Code, 3 digits) and MNC (Mobile Network code, 2-3 digits) for the
+ * data operator.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#DATA_OPERATOR_NUMERIC}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_OPERATOR_NUMERIC = "data-operator-numeric";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates whether the current
+ * network selection mode is manual.
+ * Will be {@code true} if manual mode, {@code false} if automatic mode.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#IS_MANUAL_NETWORK_SELECTION}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_MANUAL = "manual";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the current voice
+ * radio technology.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#RIL_VOICE_RADIO_TECHNOLOGY}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_VOICE_RADIO_TECH = "radioTechnology";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the current data
+ * radio technology.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#RIL_DATA_RADIO_TECHNOLOGY}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_RADIO_TECH = "dataRadioTechnology";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which represents concurrent service
+ * support on CDMA network.
+ * Will be {@code true} if support, {@code false} otherwise.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#CSS_INDICATOR}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_CSS_INDICATOR = "cssIndicator";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the CDMA network
+ * id. {@code Integer.MAX_VALUE} if unknown.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#NETWORK_ID}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_NETWORK_ID = "networkId";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the CDMA system id.
+ * {@code Integer.MAX_VALUE} if unknown.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#SYSTEM_ID}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_SYSTEM_ID = "systemId";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} represents the TSB-58 roaming
+ * indicator if registered on a CDMA or EVDO system or {@code -1} if not.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#CDMA_ROAMING_INDICATOR}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} represents the default roaming
+ * indicator from the PRL if registered on a CDMA or EVDO system {@code -1} if not.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#CDMA_DEFAULT_ROAMING_INDICATOR}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates if under emergency
+ * only mode.
+ * {@code true} if in emergency only mode, {@code false} otherwise.
+ * @hide
+ * @removed
+ * @deprecated Use {@link android.provider.Telephony.ServiceStateTable#IS_EMERGENCY_ONLY}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_EMERGENCY_ONLY = "emergencyOnly";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates whether data network
+ * registration state is roaming.
+ * {@code true} if registration indicates roaming, {@code false} otherwise
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#IS_DATA_ROAMING_FROM_REGISTRATION}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_IS_DATA_ROAMING_FROM_REGISTRATION =
+ "isDataRoamingFromRegistration";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates if carrier
+ * aggregation is in use.
+ * {@code true} if carrier aggregation is in use, {@code false} otherwise.
+ * @hide
+ * @removed
+ * @deprecated Use
+ * {@link android.provider.Telephony.ServiceStateTable#IS_USING_CARRIER_AGGREGATION}.
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_IS_USING_CARRIER_AGGREGATION = "isUsingCarrierAggregation";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} representing the offset which
+ * is reduced from the rsrp threshold while calculating signal strength level.
+ * @hide
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_LTE_EARFCN_RSRP_BOOST = "LteEarfcnRsrpBoost";
+
+ /**
+ * The name of the extra used to define the text to be processed, as a
+ * CharSequence. Note that this may be a styled CharSequence, so you must use
+ * {@link Bundle#getCharSequence(String) Bundle.getCharSequence()} to retrieve it.
+ */
+ public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
+ /**
+ * The name of the boolean extra used to define if the processed text will be used as read-only.
+ */
+ public static final String EXTRA_PROCESS_TEXT_READONLY =
+ "android.intent.extra.PROCESS_TEXT_READONLY";
+
+ /**
+ * Broadcast action: reports when a new thermal event has been reached. When the device
+ * is reaching its maximum temperatue, the thermal level reported
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_THERMAL_EVENT = "android.intent.action.THERMAL_EVENT";
+
+ /** {@hide} */
+ public static final String EXTRA_THERMAL_STATE = "android.intent.extra.THERMAL_STATE";
+
+ /**
+ * Thermal state when the device is normal. This state is sent in the
+ * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
+ * {@hide}
+ */
+ public static final int EXTRA_THERMAL_STATE_NORMAL = 0;
+
+ /**
+ * Thermal state where the device is approaching its maximum threshold. This state is sent in
+ * the {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
+ * {@hide}
+ */
+ public static final int EXTRA_THERMAL_STATE_WARNING = 1;
+
+ /**
+ * Thermal state where the device has reached its maximum threshold. This state is sent in the
+ * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
+ * {@hide}
+ */
+ public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2;
+
+ /**
+ * Broadcast Action: Indicates the dock in idle state while device is docked.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide
+ */
+ public static final String ACTION_DOCK_IDLE = "android.intent.action.DOCK_IDLE";
+
+ /**
+ * Broadcast Action: Indicates the dock in active state while device is docked.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide
+ */
+ public static final String ACTION_DOCK_ACTIVE = "android.intent.action.DOCK_ACTIVE";
+
+ /**
+ * Broadcast Action: Indicates that a new device customization has been
+ * downloaded and applied (packages installed, runtime resource overlays
+ * enabled, xml files copied, ...), and that it is time for components that
+ * need to for example clear their caches to do so now.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_DEVICE_CUSTOMIZATION_READY =
+ "android.intent.action.DEVICE_CUSTOMIZATION_READY";
+
+
+ /**
+ * Activity Action: Display an activity state associated with an unique {@link LocusId}.
+ *
+ * <p>For example, a chat app could use the context to resume a conversation between 2 users.
+ *
+ * <p>Input: {@link #EXTRA_LOCUS_ID} specifies the unique identifier of the locus in the
+ * app domain. Should be stable across reboots and backup / restore.
+ * <p>Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
+
+ /**
+ * Broadcast Action: Sent to the integrity component when a package
+ * needs to be verified. The data contains the package URI along with other relevant
+ * information.
+ *
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION =
+ "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION";
+
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Standard intent categories (see addCategory()).
+
+ /**
+ * Set if the activity should be an option for the default action
+ * (center press) to perform on a piece of data. Setting this will
+ * hide from the user any activities without it set when performing an
+ * action on some data. Note that this is normally -not- set in the
+ * Intent when initiating an action -- it is for use in intent filters
+ * specified in packages.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_DEFAULT = "android.intent.category.DEFAULT";
+ /**
+ * Activities that can be safely invoked from a browser must support this
+ * category. For example, if the user is viewing a web page or an e-mail
+ * and clicks on a link in the text, the Intent generated execute that
+ * link will require the BROWSABLE category, so that only activities
+ * supporting this category will be considered as possible actions. By
+ * supporting this category, you are promising that there is nothing
+ * damaging (without user intervention) that can happen by invoking any
+ * matching Intent.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
+ /**
+ * Categories for activities that can participate in voice interaction.
+ * An activity that supports this category must be prepared to run with
+ * no UI shown at all (though in some case it may have a UI shown), and
+ * rely on {@link android.app.VoiceInteractor} to interact with the user.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
+ /**
+ * Set if the activity should be considered as an alternative action to
+ * the data the user is currently viewing. See also
+ * {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that
+ * applies to the selection in a list of items.
+ *
+ * <p>Supporting this category means that you would like your activity to be
+ * displayed in the set of alternative things the user can do, usually as
+ * part of the current activity's options menu. You will usually want to
+ * include a specific label in the <intent-filter> of this action
+ * describing to the user what it does.
+ *
+ * <p>The action of IntentFilter with this category is important in that it
+ * describes the specific action the target will perform. This generally
+ * should not be a generic action (such as {@link #ACTION_VIEW}, but rather
+ * a specific name such as "com.android.camera.action.CROP. Only one
+ * alternative of any particular action will be shown to the user, so using
+ * a specific action like this makes sure that your alternative will be
+ * displayed while also allowing other applications to provide their own
+ * overrides of that particular action.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
+ /**
+ * Set if the activity should be considered as an alternative selection
+ * action to the data the user has currently selected. This is like
+ * {@link #CATEGORY_ALTERNATIVE}, but is used in activities showing a list
+ * of items from which the user can select, giving them alternatives to the
+ * default action that will be performed on it.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE";
+ /**
+ * Intended to be used as a tab inside of a containing TabActivity.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_TAB = "android.intent.category.TAB";
+ /**
+ * Should be displayed in the top-level launcher.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER";
+ /**
+ * Indicates an activity optimized for Leanback mode, and that should
+ * be displayed in the Leanback launcher.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
+ /**
+ * Indicates the preferred entry-point activity when an application is launched from a Car
+ * launcher. If not present, Car launcher can optionally use {@link #CATEGORY_LAUNCHER} as a
+ * fallback, or exclude the application entirely.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_CAR_LAUNCHER = "android.intent.category.CAR_LAUNCHER";
+ /**
+ * Indicates a Leanback settings activity to be displayed in the Leanback launcher.
+ * @hide
+ */
+ @SystemApi
+ public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
+ /**
+ * Provides information about the package it is in; typically used if
+ * a package does not contain a {@link #CATEGORY_LAUNCHER} to provide
+ * a front-door to the user without having to be shown in the all apps list.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_INFO = "android.intent.category.INFO";
+ /**
+ * This is the home activity, that is the first activity that is displayed
+ * when the device boots.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_HOME = "android.intent.category.HOME";
+ /**
+ * This is the home activity that is displayed when the device is finished setting up and ready
+ * for use.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_HOME_MAIN = "android.intent.category.HOME_MAIN";
+ /**
+ * The home activity shown on secondary displays that support showing home activities.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_SECONDARY_HOME = "android.intent.category.SECONDARY_HOME";
+ /**
+ * This is the setup wizard activity, that is the first activity that is displayed
+ * when the user sets up the device for the first time.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_SETUP_WIZARD = "android.intent.category.SETUP_WIZARD";
+ /**
+ * This is the home activity, that is the activity that serves as the launcher app
+ * from there the user can start other apps. Often components with lower/higher
+ * priority intent filters handle the home intent, for example SetupWizard, to
+ * setup the device and we need to be able to distinguish the home app from these
+ * setup helpers.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_LAUNCHER_APP = "android.intent.category.LAUNCHER_APP";
+ /**
+ * This activity is a preference panel.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_PREFERENCE = "android.intent.category.PREFERENCE";
+ /**
+ * This activity is a development preference panel.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_DEVELOPMENT_PREFERENCE = "android.intent.category.DEVELOPMENT_PREFERENCE";
+ /**
+ * Capable of running inside a parent activity container.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_EMBED = "android.intent.category.EMBED";
+ /**
+ * This activity allows the user to browse and download new applications.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_MARKET = "android.intent.category.APP_MARKET";
+ /**
+ * This activity may be exercised by the monkey or other automated test tools.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_MONKEY = "android.intent.category.MONKEY";
+ /**
+ * To be used as a test (not part of the normal user experience).
+ */
+ public static final String CATEGORY_TEST = "android.intent.category.TEST";
+ /**
+ * To be used as a unit test (run through the Test Harness).
+ */
+ public static final String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
+ /**
+ * To be used as a sample code example (not part of the normal user
+ * experience).
+ */
+ public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE";
+
+ /**
+ * Used to indicate that an intent only wants URIs that can be opened with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}. Openable URIs
+ * must support at least the columns defined in {@link OpenableColumns} when
+ * queried.
+ *
+ * @see #ACTION_GET_CONTENT
+ * @see #ACTION_OPEN_DOCUMENT
+ * @see #ACTION_CREATE_DOCUMENT
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_OPENABLE = "android.intent.category.OPENABLE";
+
+ /**
+ * Used to indicate that an intent filter can accept files which are not necessarily
+ * openable by {@link ContentResolver#openFileDescriptor(Uri, String)}, but
+ * at least streamable via
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}
+ * using one of the stream types exposed via
+ * {@link ContentResolver#getStreamTypes(Uri, String)}.
+ *
+ * @see #ACTION_SEND
+ * @see #ACTION_SEND_MULTIPLE
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_TYPED_OPENABLE =
+ "android.intent.category.TYPED_OPENABLE";
+
+ /**
+ * To be used as code under test for framework instrumentation tests.
+ */
+ public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST =
+ "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST";
+ /**
+ * An activity to run when device is inserted into a car dock.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_CAR_DOCK = "android.intent.category.CAR_DOCK";
+ /**
+ * An activity to run when device is inserted into a car dock.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_DESK_DOCK = "android.intent.category.DESK_DOCK";
+ /**
+ * An activity to run when device is inserted into a analog (low end) dock.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_LE_DESK_DOCK = "android.intent.category.LE_DESK_DOCK";
+
+ /**
+ * An activity to run when device is inserted into a digital (high end) dock.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_HE_DESK_DOCK = "android.intent.category.HE_DESK_DOCK";
+
+ /**
+ * Used to indicate that the activity can be used in a car environment.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
+
+ /**
+ * An activity to use for the launcher when the device is placed in a VR Headset viewer.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_VR_HOME = "android.intent.category.VR_HOME";
+
+ /**
+ * The accessibility shortcut is a global gesture for users with disabilities to trigger an
+ * important for them accessibility feature to help developers determine whether they want to
+ * make their activity a shortcut target.
+ * <p>
+ * An activity of interest to users with accessibility needs may request to be the target of
+ * the accessibility shortcut. It handles intent {@link #ACTION_MAIN} with this category,
+ * which will be dispatched by the system when the user activates the shortcut when it is
+ * configured to point at this target.
+ * </p>
+ * <p>
+ * An activity declared itself to be a target of the shortcut in AndroidManifest.xml. It must
+ * also do two things:
+ * <ul>
+ * <ol>
+ * Specify that it handles the <code>android.intent.action.MAIN</code>
+ * {@link android.content.Intent}
+ * with category <code>android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET</code>.
+ * </ol>
+ * <ol>
+ * Provide a meta-data entry <code>android.accessibilityshortcut.target</code> in the
+ * manifest when declaring the activity.
+ * </ol>
+ * </ul>
+ * If either of these items is missing, the system will ignore the accessibility shortcut
+ * target. Following is an example declaration:
+ * </p>
+ * <pre>
+ * <activity android:name=".MainActivity"
+ * . . .
+ * <intent-filter>
+ * <action android:name="android.intent.action.MAIN" />
+ * <category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET" />
+ * </intent-filter>
+ * <meta-data android:name="android.accessibilityshortcut.target"
+ * android:resource="@xml/accessibilityshortcut" />
+ * </activity>
+ * </pre>
+ * <p> This is a sample XML file configuring a accessibility shortcut target: </p>
+ * <pre>
+ * <accessibility-shortcut-target
+ * android:description="@string/shortcut_target_description"
+ * android:summary="@string/shortcut_target_summary"
+ * android:animatedImageDrawable="@drawable/shortcut_target_animated_image"
+ * android:htmlDescription="@string/shortcut_target_html_description"
+ * android:settingsActivity="com.example.android.shortcut.target.SettingsActivity" />
+ * </pre>
+ * <p>
+ * Both description and summary are necessary. The system will ignore the accessibility
+ * shortcut target if they are missing. The animated image and html description are supported
+ * to help users understand how to use the shortcut target. The settings activity is a
+ * component name that allows the user to modify the settings for this accessibility shortcut
+ * target.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET =
+ "android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET";
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Application launch intent categories (see addCategory()).
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the browser application.
+ * The activity should be able to browse the Internet.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the calculator application.
+ * The activity should be able to perform standard arithmetic operations.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_CALCULATOR = "android.intent.category.APP_CALCULATOR";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the calendar application.
+ * The activity should be able to view and manipulate calendar entries.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_CALENDAR = "android.intent.category.APP_CALENDAR";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the contacts application.
+ * The activity should be able to view and manipulate address book entries.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_CONTACTS = "android.intent.category.APP_CONTACTS";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the email application.
+ * The activity should be able to send and receive email.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_EMAIL = "android.intent.category.APP_EMAIL";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the gallery application.
+ * The activity should be able to view and manipulate image and video files
+ * stored on the device.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_GALLERY = "android.intent.category.APP_GALLERY";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the maps application.
+ * The activity should be able to show the user's current location and surroundings.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_MAPS = "android.intent.category.APP_MAPS";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the messaging application.
+ * The activity should be able to send and receive text messages.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_MESSAGING = "android.intent.category.APP_MESSAGING";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the music application.
+ * The activity should be able to play, browse, or manipulate music files
+ * stored on the device.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC";
+
+ /**
+ * Used with {@link #ACTION_MAIN} to launch the files application.
+ * The activity should be able to browse and manage files stored on the device.
+ * <p>NOTE: This should not be used as the primary key of an Intent,
+ * since it will not result in the app launching with the correct
+ * action and category. Instead, use this with
+ * {@link #makeMainSelectorActivity(String, String)} to generate a main
+ * Intent with this category in the selector.</p>
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_APP_FILES = "android.intent.category.APP_FILES";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Standard extra data keys.
+
+ /**
+ * The initial data to place in a newly created record. Use with
+ * {@link #ACTION_INSERT}. The data here is a Map containing the same
+ * fields as would be given to the underlying ContentProvider.insert()
+ * call.
+ */
+ public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
+
+ /**
+ * A constant CharSequence that is associated with the Intent, used with
+ * {@link #ACTION_SEND} to supply the literal data to be sent. Note that
+ * this may be a styled CharSequence, so you must use
+ * {@link Bundle#getCharSequence(String) Bundle.getCharSequence()} to
+ * retrieve it.
+ */
+ public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
+
+ /**
+ * A constant String that is associated with the Intent, used with
+ * {@link #ACTION_SEND} to supply an alternative to {@link #EXTRA_TEXT}
+ * as HTML formatted text. Note that you <em>must</em> also supply
+ * {@link #EXTRA_TEXT}.
+ */
+ public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+
+ /**
+ * A content: URI holding a stream of data associated with the Intent,
+ * used with {@link #ACTION_SEND} to supply the data being sent.
+ */
+ public static final String EXTRA_STREAM = "android.intent.extra.STREAM";
+
+ /**
+ * A String[] holding e-mail addresses that should be delivered to.
+ */
+ public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL";
+
+ /**
+ * A String[] holding e-mail addresses that should be carbon copied.
+ */
+ public static final String EXTRA_CC = "android.intent.extra.CC";
+
+ /**
+ * A String[] holding e-mail addresses that should be blind carbon copied.
+ */
+ public static final String EXTRA_BCC = "android.intent.extra.BCC";
+
+ /**
+ * A constant string holding the desired subject line of a message.
+ */
+ public static final String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
+
+ /**
+ * An Intent describing the choices you would like shown with
+ * {@link #ACTION_PICK_ACTIVITY} or {@link #ACTION_CHOOSER}.
+ */
+ public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+
+ /**
+ * An int representing the user id to be used.
+ *
+ * @hide
+ */
+ public static final String EXTRA_USER_ID = "android.intent.extra.USER_ID";
+
+ /**
+ * An int representing the task id to be retrieved. This is used when a launch from recents is
+ * intercepted by another action such as credentials confirmation to remember which task should
+ * be resumed when complete.
+ *
+ * @hide
+ */
+ public static final String EXTRA_TASK_ID = "android.intent.extra.TASK_ID";
+
+ /**
+ * A String[] holding attribution tags when used with
+ * {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ *
+ * E.g. an attribution tag could be location_provider, com.google.android.gms.*, etc.
+ */
+ public static final String EXTRA_ATTRIBUTION_TAGS = "android.intent.extra.ATTRIBUTION_TAGS";
+
+ /**
+ * A long representing the start timestamp (epoch time in millis) of the permission usage
+ * when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ */
+ public static final String EXTRA_START_TIME = "android.intent.extra.START_TIME";
+
+ /**
+ * A long representing the end timestamp (epoch time in millis) of the permission usage when
+ * used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ */
+ public static final String EXTRA_END_TIME = "android.intent.extra.END_TIME";
+
+ /**
+ * An Intent[] describing additional, alternate choices you would like shown with
+ * {@link #ACTION_CHOOSER}.
+ *
+ * <p>An app may be capable of providing several different payload types to complete a
+ * user's intended action. For example, an app invoking {@link #ACTION_SEND} to share photos
+ * with another app may use EXTRA_ALTERNATE_INTENTS to have the chooser transparently offer
+ * several different supported sending mechanisms for sharing, such as the actual "image/*"
+ * photo data or a hosted link where the photos can be viewed.</p>
+ *
+ * <p>The intent present in {@link #EXTRA_INTENT} will be treated as the
+ * first/primary/preferred intent in the set. Additional intents specified in
+ * this extra are ordered; by default intents that appear earlier in the array will be
+ * preferred over intents that appear later in the array as matches for the same
+ * target component. To alter this preference, a calling app may also supply
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p>
+ */
+ public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
+
+ /**
+ * A {@link ComponentName ComponentName[]} describing components that should be filtered out
+ * and omitted from a list of components presented to the user.
+ *
+ * <p>When used with {@link #ACTION_CHOOSER}, the chooser will omit any of the components
+ * in this array if it otherwise would have shown them. Useful for omitting specific targets
+ * from your own package or other apps from your organization if the idea of sending to those
+ * targets would be redundant with other app functionality. Filtered components will not
+ * be able to present targets from an associated <code>ChooserTargetService</code>.</p>
+ */
+ public static final String EXTRA_EXCLUDE_COMPONENTS
+ = "android.intent.extra.EXCLUDE_COMPONENTS";
+
+ /**
+ * A {@link android.service.chooser.ChooserTarget ChooserTarget[]} for {@link #ACTION_CHOOSER}
+ * describing additional high-priority deep-link targets for the chooser to present to the user.
+ *
+ * <p>Targets provided in this way will be presented inline with all other targets provided
+ * by services from other apps. They will be prioritized before other service targets, but
+ * after those targets provided by sources that the user has manually pinned to the front.</p>
+ *
+ * @see #ACTION_CHOOSER
+ */
+ public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
+
+ /**
+ * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection
+ * from the chooser activity presented by {@link #ACTION_CHOOSER}.
+ *
+ * <p>An app preparing an action for another app to complete may wish to allow the user to
+ * disambiguate between several options for completing the action based on the chosen target
+ * or otherwise refine the action before it is invoked.
+ * </p>
+ *
+ * <p>When sent, this IntentSender may be filled in with the following extras:</p>
+ * <ul>
+ * <li>{@link #EXTRA_INTENT} The first intent that matched the user's chosen target</li>
+ * <li>{@link #EXTRA_ALTERNATE_INTENTS} Any additional intents that also matched the user's
+ * chosen target beyond the first</li>
+ * <li>{@link #EXTRA_RESULT_RECEIVER} A {@link ResultReceiver} that the refinement activity
+ * should fill in and send once the disambiguation is complete</li>
+ * </ul>
+ */
+ public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER
+ = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
+
+ /**
+ * An {@code ArrayList} of {@code String} annotations describing content for
+ * {@link #ACTION_CHOOSER}.
+ *
+ * <p>If {@link #EXTRA_CONTENT_ANNOTATIONS} is present in an intent used to start a
+ * {@link #ACTION_CHOOSER} activity, the first three annotations will be used to rank apps.</p>
+ *
+ * <p>Annotations should describe the major components or topics of the content. It is up to
+ * apps initiating {@link #ACTION_CHOOSER} to learn and add annotations. Annotations should be
+ * learned in advance, e.g., when creating or saving content, to avoid increasing latency to
+ * start {@link #ACTION_CHOOSER}. Names of customized annotations should not contain the colon
+ * character. Performance on customized annotations can suffer, if they are rarely used for
+ * {@link #ACTION_CHOOSER} in the past 14 days. Therefore, it is recommended to use the
+ * following annotations when applicable.</p>
+ * <ul>
+ * <li>"product" represents that the topic of the content is mainly about products, e.g.,
+ * health & beauty, and office supplies.</li>
+ * <li>"emotion" represents that the topic of the content is mainly about emotions, e.g.,
+ * happy, and sad.</li>
+ * <li>"person" represents that the topic of the content is mainly about persons, e.g.,
+ * face, finger, standing, and walking.</li>
+ * <li>"child" represents that the topic of the content is mainly about children, e.g.,
+ * child, and baby.</li>
+ * <li>"selfie" represents that the topic of the content is mainly about selfies.</li>
+ * <li>"crowd" represents that the topic of the content is mainly about crowds.</li>
+ * <li>"party" represents that the topic of the content is mainly about parties.</li>
+ * <li>"animal" represent that the topic of the content is mainly about animals.</li>
+ * <li>"plant" represents that the topic of the content is mainly about plants, e.g.,
+ * flowers.</li>
+ * <li>"vacation" represents that the topic of the content is mainly about vacations.</li>
+ * <li>"fashion" represents that the topic of the content is mainly about fashion, e.g.
+ * sunglasses, jewelry, handbags and clothing.</li>
+ * <li>"material" represents that the topic of the content is mainly about materials, e.g.,
+ * paper, and silk.</li>
+ * <li>"vehicle" represents that the topic of the content is mainly about vehicles, like
+ * cars, and boats.</li>
+ * <li>"document" represents that the topic of the content is mainly about documents, e.g.
+ * posters.</li>
+ * <li>"design" represents that the topic of the content is mainly about design, e.g. arts
+ * and designs of houses.</li>
+ * <li>"holiday" represents that the topic of the content is mainly about holidays, e.g.,
+ * Christmas and Thanksgiving.</li>
+ * </ul>
+ */
+ public static final String EXTRA_CONTENT_ANNOTATIONS
+ = "android.intent.extra.CONTENT_ANNOTATIONS";
+
+ /**
+ * A {@link ResultReceiver} used to return data back to the sender.
+ *
+ * <p>Used to complete an app-specific
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER refinement} for {@link #ACTION_CHOOSER}.</p>
+ *
+ * <p>If {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} is present in the intent
+ * used to start a {@link #ACTION_CHOOSER} activity this extra will be
+ * {@link #fillIn(Intent, int) filled in} to that {@link IntentSender} and sent
+ * when the user selects a target component from the chooser. It is up to the recipient
+ * to send a result to this ResultReceiver to signal that disambiguation is complete
+ * and that the chooser should invoke the user's choice.</p>
+ *
+ * <p>The disambiguator should provide a Bundle to the ResultReceiver with an intent
+ * assigned to the key {@link #EXTRA_INTENT}. This supplied intent will be used by the chooser
+ * to match and fill in the final Intent or ChooserTarget before starting it.
+ * The supplied intent must {@link #filterEquals(Intent) match} one of the intents from
+ * {@link #EXTRA_INTENT} or {@link #EXTRA_ALTERNATE_INTENTS} passed to
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} to be accepted.</p>
+ *
+ * <p>The result code passed to the ResultReceiver should be
+ * {@link android.app.Activity#RESULT_OK} if the refinement succeeded and the supplied intent's
+ * target in the chooser should be started, or {@link android.app.Activity#RESULT_CANCELED} if
+ * the chooser should finish without starting a target.</p>
+ */
+ public static final String EXTRA_RESULT_RECEIVER
+ = "android.intent.extra.RESULT_RECEIVER";
+
+ /**
+ * A CharSequence dialog title to provide to the user when used with a
+ * {@link #ACTION_CHOOSER}.
+ */
+ public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
+
+ /**
+ * A Parcelable[] of {@link Intent} or
+ * {@link android.content.pm.LabeledIntent} objects as set with
+ * {@link #putExtra(String, Parcelable[])} of additional activities to place
+ * a the front of the list of choices, when shown to the user with a
+ * {@link #ACTION_CHOOSER}.
+ */
+ public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
+
+ /**
+ * 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 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 instant app resolution.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_INSTANT_APP_HOSTNAME =
+ "android.intent.extra.INSTANT_APP_HOSTNAME";
+
+ /**
+ * 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";
+
+ /**
+ * 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
+ */
+ @Deprecated
+ public static final String EXTRA_VERSION_CODE = "android.intent.extra.VERSION_CODE";
+
+ /**
+ * 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 instant app installation.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_CALLING_PACKAGE
+ = "android.intent.extra.CALLING_PACKAGE";
+
+ /**
+ * Optional calling app provided bundle containing additional launch information the
+ * installer may use.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_VERIFICATION_BUNDLE
+ = "android.intent.extra.VERIFICATION_BUNDLE";
+
+ /**
+ * A Bundle forming a mapping of potential target package names to different extras Bundles
+ * to add to the default intent extras in {@link #EXTRA_INTENT} when used with
+ * {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not
+ * be currently installed on the device.
+ *
+ * <p>An application may choose to provide alternate extras for the case where a user
+ * selects an activity from a predetermined set of target packages. If the activity
+ * the user selects from the chooser belongs to a package with its package name as
+ * a key in this bundle, the corresponding extras for that package will be merged with
+ * the extras already present in the intent at {@link #EXTRA_INTENT}. If a replacement
+ * extra has the same key as an extra already present in the intent it will overwrite
+ * the extra from the intent.</p>
+ *
+ * <p><em>Examples:</em>
+ * <ul>
+ * <li>An application may offer different {@link #EXTRA_TEXT} to an application
+ * when sharing with it via {@link #ACTION_SEND}, augmenting a link with additional query
+ * parameters for that target.</li>
+ * <li>An application may offer additional metadata for known targets of a given intent
+ * to pass along information only relevant to that target such as account or content
+ * identifiers already known to that application.</li>
+ * </ul></p>
+ */
+ public static final String EXTRA_REPLACEMENT_EXTRAS =
+ "android.intent.extra.REPLACEMENT_EXTRAS";
+
+ /**
+ * An {@link IntentSender} that will be notified if a user successfully chooses a target
+ * component to handle an action in an {@link #ACTION_CHOOSER} activity. The IntentSender
+ * will have the extra {@link #EXTRA_CHOSEN_COMPONENT} appended to it containing the
+ * {@link ComponentName} of the chosen component.
+ *
+ * <p>In some situations this callback may never come, for example if the user abandons
+ * the chooser, switches to another task or any number of other reasons. Apps should not
+ * be written assuming that this callback will always occur.</p>
+ */
+ public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER =
+ "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+
+ /**
+ * The {@link ComponentName} chosen by the user to complete an action.
+ *
+ * @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
+ */
+ public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
+
+ /**
+ * A {@link android.view.KeyEvent} object containing the event that
+ * triggered the creation of the Intent it is in.
+ */
+ public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
+
+ /**
+ * Set to true in {@link #ACTION_REQUEST_SHUTDOWN} to request confirmation from the user
+ * before shutting down.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_KEY_CONFIRM = "android.intent.extra.KEY_CONFIRM";
+
+ /**
+ * Set to true in {@link #ACTION_REQUEST_SHUTDOWN} to indicate that the shutdown is
+ * requested by the user.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_USER_REQUESTED_SHUTDOWN =
+ "android.intent.extra.USER_REQUESTED_SHUTDOWN";
+
+ /**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or
+ * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} intents to override the default action
+ * of restarting the application.
+ */
+ public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
+
+ /**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to signal that the application was removed with the user-initiated action.
+ */
+ public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
+
+ /**
+ * A String holding the phone number originally entered in
+ * {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL}, or the actual
+ * number to call in a {@link android.content.Intent#ACTION_CALL}.
+ */
+ public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+
+ /**
+ * Used as an int extra field in {@link android.content.Intent#ACTION_UID_REMOVED}
+ * intents to supply the uid the package had been assigned. Also an optional
+ * extra in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or
+ * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} for the same
+ * purpose.
+ */
+ public static final String EXTRA_UID = "android.intent.extra.UID";
+
+ /**
+ * @hide String array of package names.
+ */
+ @SystemApi
+ public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
+
+ /**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate whether this represents a full uninstall (removing
+ * both the code and its data) or a partial uninstall (leaving its data,
+ * implying that this is an update).
+ */
+ public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
+
+ /**
+ * @hide
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate that at this point the package has been removed for
+ * all users on the device.
+ */
+ public static final String EXTRA_REMOVED_FOR_ALL_USERS
+ = "android.intent.extra.REMOVED_FOR_ALL_USERS";
+
+ /**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate that this is a replacement of the package, so this
+ * broadcast will immediately be followed by an add broadcast for a
+ * different version of the same package.
+ */
+ public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
+
+ /**
+ * Used as an int extra field in {@link android.app.AlarmManager} pending intents
+ * to tell the application being invoked how many pending alarms are being
+ * delivered with the intent. For one-shot alarms this will always be 1.
+ * For recurring alarms, this might be greater than 1 if the device was
+ * asleep or powered off at the time an earlier alarm would have been
+ * delivered.
+ *
+ * <p>Note: You must supply a <b>mutable</b> {@link android.app.PendingIntent} to
+ * {@code AlarmManager} while setting your alarms to be able to read this value on receiving
+ * them. <em>Mutability of pending intents must be explicitly specified by apps targeting
+ * {@link Build.VERSION_CODES#S} or higher</em>.
+ *
+ * @see android.app.PendingIntent#FLAG_MUTABLE
+ *
+ */
+ public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
+
+ /**
+ * Used as an int extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
+ * intents to request the dock state. Possible values are
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED},
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}, or
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_LE_DESK}, or
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_HE_DESK}.
+ */
+ public static final String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
+
+ /**
+ * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+ * to represent that the phone is not in any dock.
+ */
+ public static final int EXTRA_DOCK_STATE_UNDOCKED = 0;
+
+ /**
+ * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+ * to represent that the phone is in a desk dock.
+ */
+ public static final int EXTRA_DOCK_STATE_DESK = 1;
+
+ /**
+ * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+ * to represent that the phone is in a car dock.
+ */
+ public static final int EXTRA_DOCK_STATE_CAR = 2;
+
+ /**
+ * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+ * to represent that the phone is in a analog (low end) dock.
+ */
+ public static final int EXTRA_DOCK_STATE_LE_DESK = 3;
+
+ /**
+ * Used as an int value for {@link android.content.Intent#EXTRA_DOCK_STATE}
+ * to represent that the phone is in a digital (high end) dock.
+ */
+ public static final int EXTRA_DOCK_STATE_HE_DESK = 4;
+
+ /**
+ * Boolean that can be supplied as meta-data with a dock activity, to
+ * indicate that the dock should take over the home key when it is active.
+ */
+ public static final String METADATA_DOCK_HOME = "android.dock_home";
+
+ /**
+ * Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing
+ * the bug report.
+ */
+ public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
+
+ /**
+ * Used in the extra field in the remote intent. It's a string token passed with the
+ * remote intent.
+ */
+ public static final String EXTRA_REMOTE_INTENT_TOKEN =
+ "android.intent.extra.remote_intent_token";
+
+ /**
+ * @deprecated See {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST}; this field
+ * will contain only the first name in the list.
+ */
+ @Deprecated public static final String EXTRA_CHANGED_COMPONENT_NAME =
+ "android.intent.extra.changed_component_name";
+
+ /**
+ * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED},
+ * and contains a string array of all of the components that have changed. If
+ * the state of the overall package has changed, then it will contain an entry
+ * with the package name itself.
+ */
+ public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST =
+ "android.intent.extra.changed_component_name_list";
+
+ /**
+ * This field is part of
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE},
+ * {@link android.content.Intent#ACTION_PACKAGES_SUSPENDED},
+ * {@link android.content.Intent#ACTION_PACKAGES_UNSUSPENDED}
+ * and contains a string array of all of the components that have changed.
+ */
+ public static final String EXTRA_CHANGED_PACKAGE_LIST =
+ "android.intent.extra.changed_package_list";
+
+ /**
+ * This field is part of
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
+ * and contains an integer array of uids of all of the components
+ * that have changed.
+ */
+ public static final String EXTRA_CHANGED_UID_LIST =
+ "android.intent.extra.changed_uid_list";
+
+ /**
+ * An integer denoting a bitwise combination of restrictions set on distracting packages via
+ * {@link PackageManager#setDistractingPackageRestrictions(String[], int)}
+ *
+ * @hide
+ * @see PackageManager.DistractionRestriction
+ * @see PackageManager#setDistractingPackageRestrictions(String[], int)
+ */
+ public static final String EXTRA_DISTRACTION_RESTRICTIONS =
+ "android.intent.extra.distraction_restrictions";
+
+ /**
+ * @hide
+ * Magic extra system code can use when binding, to give a label for
+ * who it is that has bound to a service. This is an integer giving
+ * a framework string resource that can be displayed to the user.
+ */
+ public static final String EXTRA_CLIENT_LABEL =
+ "android.intent.extra.client_label";
+
+ /**
+ * @hide
+ * Magic extra system code can use when binding, to give a PendingIntent object
+ * that can be launched for the user to disable the system's use of this
+ * service.
+ */
+ public static final String EXTRA_CLIENT_INTENT =
+ "android.intent.extra.client_intent";
+
+ /**
+ * Extra used to indicate that an intent should only return data that is on
+ * the local device. This is a boolean extra; the default is false. If true,
+ * an implementation should only allow the user to select data that is
+ * already on the device, not requiring it be downloaded from a remote
+ * service when opened.
+ *
+ * @see #ACTION_GET_CONTENT
+ * @see #ACTION_OPEN_DOCUMENT
+ * @see #ACTION_OPEN_DOCUMENT_TREE
+ * @see #ACTION_CREATE_DOCUMENT
+ */
+ public static final String EXTRA_LOCAL_ONLY =
+ "android.intent.extra.LOCAL_ONLY";
+
+ /**
+ * Extra used to indicate that an intent can allow the user to select and
+ * return multiple items. This is a boolean extra; the default is false. If
+ * true, an implementation is allowed to present the user with a UI where
+ * they can pick multiple items that are all returned to the caller. When
+ * this happens, they should be returned as the {@link #getClipData()} part
+ * of the result Intent.
+ *
+ * @see #ACTION_GET_CONTENT
+ * @see #ACTION_OPEN_DOCUMENT
+ */
+ public static final String EXTRA_ALLOW_MULTIPLE =
+ "android.intent.extra.ALLOW_MULTIPLE";
+
+ /**
+ * The integer userHandle (i.e. userId) carried with broadcast intents related to addition,
+ * removal and switching of users and managed profiles - {@link #ACTION_USER_ADDED},
+ * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_USER_HANDLE =
+ "android.intent.extra.user_handle";
+
+ /**
+ * The UserHandle carried with intents.
+ */
+ public static final String EXTRA_USER =
+ "android.intent.extra.USER";
+
+ /**
+ * Extra used in the response from a BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is
+ * <code>ArrayList<RestrictionEntry></code>.
+ */
+ public static final String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
+
+ /**
+ * Extra sent in the intent to the BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is a Bundle containing
+ * the restrictions as key/value pairs.
+ */
+ public static final String EXTRA_RESTRICTIONS_BUNDLE =
+ "android.intent.extra.restrictions_bundle";
+
+ /**
+ * Extra used in the response from a BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}.
+ */
+ public static final String EXTRA_RESTRICTIONS_INTENT =
+ "android.intent.extra.restrictions_intent";
+
+ /**
+ * Extra used to communicate a set of acceptable MIME types. The type of the
+ * extra is {@code String[]}. Values may be a combination of concrete MIME
+ * types (such as "image/png") and/or partial MIME types (such as
+ * "audio/*").
+ *
+ * @see #ACTION_GET_CONTENT
+ * @see #ACTION_OPEN_DOCUMENT
+ */
+ public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
+
+ /**
+ * Optional extra for {@link #ACTION_SHUTDOWN} that allows the sender to qualify that
+ * this shutdown is only for the user space of the system, not a complete shutdown.
+ * When this is true, hardware devices can use this information to determine that
+ * they shouldn't do a complete shutdown of their device since this is not a
+ * complete shutdown down to the kernel, but only user space restarting.
+ * The default if not supplied is false.
+ */
+ public static final String EXTRA_SHUTDOWN_USERSPACE_ONLY
+ = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
+
+ /**
+ * Optional extra specifying a time in milliseconds since the Epoch. The value must be
+ * non-negative.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_TIME = "android.intent.extra.TIME";
+
+ /**
+ * Extra sent with {@link #ACTION_TIMEZONE_CHANGED} specifying the new time zone of the device.
+ *
+ * <p>Type: String, the same as returned by {@link TimeZone#getID()} to identify time zones.
+ */
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_TIMEZONE = "time-zone";
+
+ /**
+ * Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
+ * user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
+ * {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
+ * {@link #EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT}. The value must not be negative.
+ *
+ * @hide for internal use only.
+ */
+ public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT =
+ "android.intent.extra.TIME_PREF_24_HOUR_FORMAT";
+ /** @hide */
+ public static final int EXTRA_TIME_PREF_VALUE_USE_12_HOUR = 0;
+ /** @hide */
+ public static final int EXTRA_TIME_PREF_VALUE_USE_24_HOUR = 1;
+ /** @hide */
+ public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
+
+ /**
+ * Intent extra: the reason that the operation associated with this intent is being performed.
+ *
+ * <p>Type: String
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REASON = "android.intent.extra.REASON";
+
+ /**
+ * {@hide}
+ * This extra will be send together with {@link #ACTION_FACTORY_RESET}
+ */
+ public static final String EXTRA_WIPE_EXTERNAL_STORAGE = "android.intent.extra.WIPE_EXTERNAL_STORAGE";
+
+ /**
+ * {@hide}
+ * This extra will be set to true when the user choose to wipe the data on eSIM during factory
+ * reset for the device with eSIM. This extra will be sent together with
+ * {@link #ACTION_FACTORY_RESET}
+ */
+ public static final String EXTRA_WIPE_ESIMS = "com.android.internal.intent.extra.WIPE_ESIMS";
+
+ /**
+ * Optional {@link android.app.PendingIntent} extra used to deliver the result of the SIM
+ * activation request.
+ * TODO: Add information about the structure and response data used with the pending intent.
+ * @hide
+ */
+ public static final String EXTRA_SIM_ACTIVATION_RESPONSE =
+ "android.intent.extra.SIM_ACTIVATION_RESPONSE";
+
+ /**
+ * Optional index with semantics depending on the intent action.
+ *
+ * <p>The value must be an integer greater or equal to 0.
+ * @see #ACTION_QUICK_VIEW
+ */
+ public static final String EXTRA_INDEX = "android.intent.extra.INDEX";
+
+ /**
+ * Tells the quick viewer to show additional UI actions suitable for the passed Uris,
+ * such as opening in other apps, sharing, opening, editing, printing, deleting,
+ * casting, etc.
+ *
+ * <p>The value is boolean. By default false.
+ * @see #ACTION_QUICK_VIEW
+ * @removed
+ */
+ @Deprecated
+ public static final String EXTRA_QUICK_VIEW_ADVANCED =
+ "android.intent.extra.QUICK_VIEW_ADVANCED";
+
+ /**
+ * An optional extra of {@code String[]} indicating which quick view features should be made
+ * available to the user in the quick view UI while handing a
+ * {@link Intent#ACTION_QUICK_VIEW} intent.
+ * <li>Enumeration of features here is not meant to restrict capabilities of the quick viewer.
+ * Quick viewer can implement features not listed below.
+ * <li>Features included at this time are: {@link QuickViewConstants#FEATURE_VIEW},
+ * {@link QuickViewConstants#FEATURE_EDIT}, {@link QuickViewConstants#FEATURE_DELETE},
+ * {@link QuickViewConstants#FEATURE_DOWNLOAD}, {@link QuickViewConstants#FEATURE_SEND},
+ * {@link QuickViewConstants#FEATURE_PRINT}.
+ * <p>
+ * Requirements:
+ * <li>Quick viewer shouldn't show a feature if the feature is absent in
+ * {@link #EXTRA_QUICK_VIEW_FEATURES}.
+ * <li>When {@link #EXTRA_QUICK_VIEW_FEATURES} is not present, quick viewer should follow
+ * internal policies.
+ * <li>Presence of an feature in {@link #EXTRA_QUICK_VIEW_FEATURES}, does not constitute a
+ * requirement that the feature be shown. Quick viewer may, according to its own policies,
+ * disable or hide features.
+ *
+ * @see #ACTION_QUICK_VIEW
+ */
+ public static final String EXTRA_QUICK_VIEW_FEATURES =
+ "android.intent.extra.QUICK_VIEW_FEATURES";
+
+ /**
+ * Optional boolean extra indicating whether quiet mode has been switched on or off.
+ * When a profile goes into quiet mode, all apps in the profile are killed and the
+ * profile user is stopped. Widgets originating from the profile are masked, and app
+ * launcher icons are grayed out.
+ */
+ public static final String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
+
+ /**
+ * Optional CharSequence extra to provide a search query.
+ * The format of this query is dependent on the receiving application.
+ *
+ * <p>Applicable to {@link Intent} with actions:
+ * <ul>
+ * <li>{@link Intent#ACTION_GET_CONTENT}</li>
+ * <li>{@link Intent#ACTION_OPEN_DOCUMENT}</li>
+ * </ul>
+ */
+ public static final String EXTRA_CONTENT_QUERY = "android.intent.extra.CONTENT_QUERY";
+
+ /**
+ * Used as an int extra field in {@link #ACTION_MEDIA_RESOURCE_GRANTED}
+ * intents to specify the resource type granted. Possible values are
+ * {@link #EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC} or
+ * {@link #EXTRA_MEDIA_RESOURCE_TYPE_AUDIO_CODEC}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_MEDIA_RESOURCE_TYPE =
+ "android.intent.extra.MEDIA_RESOURCE_TYPE";
+
+ /**
+ * Used as a boolean extra field in {@link #ACTION_CHOOSER} intents to specify
+ * whether to show the chooser or not when there is only one application available
+ * to choose from.
+ */
+ public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE =
+ "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
+
+ /**
+ * Used as an int value for {@link #EXTRA_MEDIA_RESOURCE_TYPE}
+ * to represent that a video codec is allowed to use.
+ *
+ * @hide
+ */
+ public static final int EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC = 0;
+
+ /**
+ * Used as an int value for {@link #EXTRA_MEDIA_RESOURCE_TYPE}
+ * to represent that a audio codec is allowed to use.
+ *
+ * @hide
+ */
+ public static final int EXTRA_MEDIA_RESOURCE_TYPE_AUDIO_CODEC = 1;
+
+ /**
+ * Intent extra: ID of the context used on {@link #ACTION_VIEW_LOCUS}.
+ *
+ * <p>
+ * Type: {@link LocusId}
+ * </p>
+ */
+ public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
+
+ /**
+ * Used as an int array extra field in
+ * {@link android.content.Intent#ACTION_PACKAGE_REMOVED_INTERNAL}
+ * intents to indicate that visibility allow list of this removed package.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VISIBILITY_ALLOW_LIST =
+ "android.intent.extra.VISIBILITY_ALLOW_LIST";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Intent flags (see mFlags variable).
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_GRANT_" }, value = {
+ FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION,
+ FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_PREFIX_URI_PERMISSION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GrantUriMode {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_GRANT_" }, value = {
+ FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AccessUriMode {}
+
+ /**
+ * Test if given mode flags specify an access mode, which must be at least
+ * read and/or write.
+ *
+ * @hide
+ */
+ public static boolean isAccessUriMode(int modeFlags) {
+ return (modeFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) != 0;
+ }
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_GRANT_READ_URI_PERMISSION,
+ FLAG_GRANT_WRITE_URI_PERMISSION,
+ FLAG_FROM_BACKGROUND,
+ FLAG_DEBUG_LOG_RESOLUTION,
+ FLAG_EXCLUDE_STOPPED_PACKAGES,
+ FLAG_INCLUDE_STOPPED_PACKAGES,
+ FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+ 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,
+ FLAG_ACTIVITY_MULTIPLE_TASK,
+ FLAG_ACTIVITY_CLEAR_TOP,
+ FLAG_ACTIVITY_FORWARD_RESULT,
+ FLAG_ACTIVITY_PREVIOUS_IS_TOP,
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
+ FLAG_ACTIVITY_BROUGHT_TO_FRONT,
+ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
+ FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY,
+ FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
+ FLAG_ACTIVITY_NEW_DOCUMENT,
+ FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
+ FLAG_ACTIVITY_NO_USER_ACTION,
+ FLAG_ACTIVITY_REORDER_TO_FRONT,
+ FLAG_ACTIVITY_NO_ANIMATION,
+ FLAG_ACTIVITY_CLEAR_TASK,
+ FLAG_ACTIVITY_TASK_ON_HOME,
+ FLAG_ACTIVITY_RETAIN_IN_RECENTS,
+ FLAG_ACTIVITY_LAUNCH_ADJACENT,
+ FLAG_ACTIVITY_REQUIRE_NON_BROWSER,
+ FLAG_ACTIVITY_REQUIRE_DEFAULT,
+ FLAG_RECEIVER_REGISTERED_ONLY,
+ FLAG_RECEIVER_REPLACE_PENDING,
+ FLAG_RECEIVER_FOREGROUND,
+ FLAG_RECEIVER_NO_ABORT,
+ FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT,
+ FLAG_RECEIVER_BOOT_UPGRADE,
+ FLAG_RECEIVER_INCLUDE_BACKGROUND,
+ FLAG_RECEIVER_EXCLUDE_BACKGROUND,
+ FLAG_RECEIVER_FROM_SHELL,
+ FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS,
+ FLAG_RECEIVER_OFFLOAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_FROM_BACKGROUND,
+ FLAG_DEBUG_LOG_RESOLUTION,
+ FLAG_EXCLUDE_STOPPED_PACKAGES,
+ 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,
+ FLAG_ACTIVITY_MULTIPLE_TASK,
+ FLAG_ACTIVITY_CLEAR_TOP,
+ FLAG_ACTIVITY_FORWARD_RESULT,
+ FLAG_ACTIVITY_PREVIOUS_IS_TOP,
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
+ FLAG_ACTIVITY_BROUGHT_TO_FRONT,
+ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
+ FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY,
+ FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
+ FLAG_ACTIVITY_NEW_DOCUMENT,
+ FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
+ FLAG_ACTIVITY_NO_USER_ACTION,
+ FLAG_ACTIVITY_REORDER_TO_FRONT,
+ FLAG_ACTIVITY_NO_ANIMATION,
+ FLAG_ACTIVITY_CLEAR_TASK,
+ FLAG_ACTIVITY_TASK_ON_HOME,
+ FLAG_ACTIVITY_RETAIN_IN_RECENTS,
+ FLAG_ACTIVITY_LAUNCH_ADJACENT,
+ FLAG_RECEIVER_REGISTERED_ONLY,
+ FLAG_RECEIVER_REPLACE_PENDING,
+ FLAG_RECEIVER_FOREGROUND,
+ FLAG_RECEIVER_NO_ABORT,
+ FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT,
+ FLAG_RECEIVER_BOOT_UPGRADE,
+ FLAG_RECEIVER_INCLUDE_BACKGROUND,
+ FLAG_RECEIVER_EXCLUDE_BACKGROUND,
+ FLAG_RECEIVER_FROM_SHELL,
+ FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS,
+ FLAG_RECEIVER_OFFLOAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MutableFlags {}
+
+ /**
+ * If set, the recipient of this Intent will be granted permission to
+ * perform read operations on the URI in the Intent's data and any URIs
+ * specified in its ClipData. When applying to an Intent's ClipData,
+ * all URIs as well as recursive traversals through data or other ClipData
+ * in Intent items will be granted; only the grant flags of the top-level
+ * Intent are used.
+ */
+ public static final int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001;
+ /**
+ * If set, the recipient of this Intent will be granted permission to
+ * perform write operations on the URI in the Intent's data and any URIs
+ * specified in its ClipData. When applying to an Intent's ClipData,
+ * all URIs as well as recursive traversals through data or other ClipData
+ * in Intent items will be granted; only the grant flags of the top-level
+ * Intent are used.
+ */
+ public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002;
+ /**
+ * Can be set by the caller to indicate that this Intent is coming from
+ * a background operation, not from direct user interaction.
+ */
+ public static final int FLAG_FROM_BACKGROUND = 0x00000004;
+ /**
+ * A flag you can enable for debugging: when set, log messages will be
+ * printed during the resolution of this intent to show you what has
+ * been found to create the final resolved list.
+ */
+ public static final int FLAG_DEBUG_LOG_RESOLUTION = 0x00000008;
+ /**
+ * If set, this intent will not match any components in packages that
+ * are currently stopped. If this is not set, then the default behavior
+ * is to include such applications in the result.
+ */
+ public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 0x00000010;
+ /**
+ * If set, this intent will always match any components in packages that
+ * are currently stopped. This is the default behavior when
+ * {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set. If both of these
+ * flags are set, this one wins (it allows overriding of exclude for
+ * places where the framework may automatically set the exclude flag).
+ */
+ public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020;
+
+ /**
+ * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant can be
+ * persisted across device reboots until explicitly revoked with
+ * {@link Context#revokeUriPermission(Uri, int)}. This flag only offers the
+ * grant for possible persisting; the receiving application must call
+ * {@link ContentResolver#takePersistableUriPermission(Uri, int)} to
+ * actually persist.
+ *
+ * @see ContentResolver#takePersistableUriPermission(Uri, int)
+ * @see ContentResolver#releasePersistableUriPermission(Uri, int)
+ * @see ContentResolver#getPersistedUriPermissions()
+ * @see ContentResolver#getOutgoingPersistedUriPermissions()
+ */
+ public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040;
+
+ /**
+ * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant
+ * applies to any URI that is a prefix match against the original granted
+ * URI. (Without this flag, the URI must match exactly for access to be
+ * granted.) Another URI is considered a prefix match only when scheme,
+ * authority, and all path segments defined by the prefix are an exact
+ * match.
+ */
+ public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 0x00000080;
+
+ /**
+ * Flag used to automatically match intents based on their Direct Boot
+ * awareness and the current user state.
+ * <p>
+ * Since the default behavior is to automatically apply the current user
+ * state, this is effectively a sentinel value that doesn't change the
+ * output of any queries based on its presence or absence.
+ * <p>
+ * Instead, this value can be useful in conjunction with
+ * {@link android.os.StrictMode.VmPolicy.Builder#detectImplicitDirectBoot()}
+ * to detect when a caller is relying on implicit automatic matching,
+ * instead of confirming the explicit behavior they want.
+ */
+ public static final int FLAG_DIRECT_BOOT_AUTO = 0x00000100;
+
+ /** {@hide} */
+ @Deprecated
+ public static final int FLAG_DEBUG_TRIAGED_MISSING = FLAG_DIRECT_BOOT_AUTO;
+
+ /**
+ * Internal flag used to indicate ephemeral applications should not be
+ * considered when resolving the intent.
+ *
+ * @hide
+ */
+ public static final int FLAG_IGNORE_EPHEMERAL = 0x00000200;
+
+ /**
+ * If set, the new activity is not kept in the history stack. As soon as
+ * the user navigates away from it, the activity is finished. This may also
+ * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
+ * noHistory} attribute.
+ *
+ * <p>If set, {@link android.app.Activity#onActivityResult onActivityResult()}
+ * is never invoked when the current activity starts a new activity which
+ * sets a result and finishes.
+ */
+ public static final int FLAG_ACTIVITY_NO_HISTORY = 0x40000000;
+ /**
+ * If set, the activity will not be launched if it is already running
+ * at the top of the history stack. See
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html#TaskLaunchModes">
+ * Tasks and Back Stack</a> for more information.
+ */
+ public static final int FLAG_ACTIVITY_SINGLE_TOP = 0x20000000;
+ /**
+ * If set, this activity will become the start of a new task on this
+ * history stack. A task (from the activity that started it to the
+ * next task activity) defines an atomic group of activities that the
+ * user can move to. Tasks can be moved to the foreground and background;
+ * all of the activities inside of a particular task always remain in
+ * the same order. See
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+ * Stack</a> for more information about tasks.
+ *
+ * <p>This flag is generally used by activities that want
+ * to present a "launcher" style behavior: they give the user a list of
+ * separate things that can be done, which otherwise run completely
+ * independently of the activity launching them.
+ *
+ * <p>When using this flag, if a task is already running for the activity
+ * you are now starting, then a new activity will not be started; instead,
+ * the current task will simply be brought to the front of the screen with
+ * the state it was last in. See {@link #FLAG_ACTIVITY_MULTIPLE_TASK} for a flag
+ * to disable this behavior.
+ *
+ * <p>This flag can not be used when the caller is requesting a result from
+ * the activity being launched.
+ */
+ public static final int FLAG_ACTIVITY_NEW_TASK = 0x10000000;
+ /**
+ * This flag is used to create a new task and launch an activity into it.
+ * This flag is always paired with either {@link #FLAG_ACTIVITY_NEW_DOCUMENT}
+ * or {@link #FLAG_ACTIVITY_NEW_TASK}. In both cases these flags alone would
+ * search through existing tasks for ones matching this Intent. Only if no such
+ * task is found would a new task be created. When paired with
+ * FLAG_ACTIVITY_MULTIPLE_TASK both of these behaviors are modified to skip
+ * the search for a matching task and unconditionally start a new task.
+ *
+ * <strong>When used with {@link #FLAG_ACTIVITY_NEW_TASK} do not use this
+ * flag unless you are implementing your own
+ * top-level application launcher.</strong> Used in conjunction with
+ * {@link #FLAG_ACTIVITY_NEW_TASK} to disable the
+ * behavior of bringing an existing task to the foreground. When set,
+ * a new task is <em>always</em> started to host the Activity for the
+ * Intent, regardless of whether there is already an existing task running
+ * the same thing.
+ *
+ * <p><strong>Because the default system does not include graphical task management,
+ * you should not use this flag unless you provide some way for a user to
+ * return back to the tasks you have launched.</strong>
+ *
+ * See {@link #FLAG_ACTIVITY_NEW_DOCUMENT} for details of this flag's use for
+ * creating new document tasks.
+ *
+ * <p>This flag is ignored if one of {@link #FLAG_ACTIVITY_NEW_TASK} or
+ * {@link #FLAG_ACTIVITY_NEW_DOCUMENT} is not also set.
+ *
+ * <p>See
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+ * Stack</a> for more information about tasks.
+ *
+ * @see #FLAG_ACTIVITY_NEW_DOCUMENT
+ * @see #FLAG_ACTIVITY_NEW_TASK
+ */
+ public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000;
+ /**
+ * If set, and the activity being launched is already running in the
+ * current task, then instead of launching a new instance of that activity,
+ * all of the other activities on top of it will be closed and this Intent
+ * will be delivered to the (now on top) old activity as a new Intent.
+ *
+ * <p>For example, consider a task consisting of the activities: A, B, C, D.
+ * If D calls startActivity() with an Intent that resolves to the component
+ * of activity B, then C and D will be finished and B receive the given
+ * Intent, resulting in the stack now being: A, B.
+ *
+ * <p>The currently running instance of activity B in the above example will
+ * either receive the new intent you are starting here in its
+ * onNewIntent() method, or be itself finished and restarted with the
+ * new intent. If it has declared its launch mode to be "multiple" (the
+ * default) and you have not set {@link #FLAG_ACTIVITY_SINGLE_TOP} in
+ * the same intent, then it will be finished and re-created; for all other
+ * launch modes or if {@link #FLAG_ACTIVITY_SINGLE_TOP} is set then this
+ * Intent will be delivered to the current instance's onNewIntent().
+ *
+ * <p>This launch mode can also be used to good effect in conjunction with
+ * {@link #FLAG_ACTIVITY_NEW_TASK}: if used to start the root activity
+ * of a task, it will bring any currently running instance of that task
+ * to the foreground, and then clear it to its root state. This is
+ * especially useful, for example, when launching an activity from the
+ * notification manager.
+ *
+ * <p>See
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+ * Stack</a> for more information about tasks.
+ */
+ public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000;
+ /**
+ * If set and this intent is being used to launch a new activity from an
+ * existing one, then the reply target of the existing activity will be
+ * transferred to the new activity. This way, the new activity can call
+ * {@link android.app.Activity#setResult} and have that result sent back to
+ * the reply target of the original activity.
+ */
+ public static final int FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000;
+ /**
+ * If set and this intent is being used to launch a new activity from an
+ * existing one, the current activity will not be counted as the top
+ * activity for deciding whether the new intent should be delivered to
+ * the top instead of starting a new one. The previous activity will
+ * be used as the top, with the assumption being that the current activity
+ * will finish itself immediately.
+ */
+ public static final int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000;
+ /**
+ * If set, the new activity is not kept in the list of recently launched
+ * activities.
+ */
+ public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000;
+ /**
+ * This flag is not normally set by application code, but set for you by
+ * the system as described in the
+ * {@link android.R.styleable#AndroidManifestActivity_launchMode
+ * launchMode} documentation for the singleTask mode.
+ */
+ public static final int FLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000;
+ /**
+ * If set, and this activity is either being started in a new task or
+ * bringing to the top an existing task, then it will be launched as
+ * the front door of the task. This will result in the application of
+ * any affinities needed to have that task in the proper state (either
+ * moving activities to or from it), or simply resetting that task to
+ * its initial state if needed.
+ *
+ * @see android.R.attr#allowTaskReparenting
+ * @see android.R.attr#clearTaskOnLaunch
+ * @see android.R.attr#finishOnTaskLaunch
+ */
+ public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000;
+ /**
+ * This flag is not normally set by application code, but set for you by
+ * the system if this activity is being launched from history.
+ */
+ public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000;
+ /**
+ * @deprecated As of API 21 this performs identically to
+ * {@link #FLAG_ACTIVITY_NEW_DOCUMENT} which should be used instead of this.
+ */
+ @Deprecated
+ public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
+ /**
+ * This flag is used to open a document into a new task rooted at the activity launched
+ * by this Intent. Through the use of this flag, or its equivalent attribute,
+ * {@link android.R.attr#documentLaunchMode} multiple instances of the same activity
+ * containing different documents will appear in the recent tasks list.
+ *
+ * <p>The use of the activity attribute form of this,
+ * {@link android.R.attr#documentLaunchMode}, is
+ * preferred over the Intent flag described here. The attribute form allows the
+ * Activity to specify multiple document behavior for all launchers of the Activity
+ * whereas using this flag requires each Intent that launches the Activity to specify it.
+ *
+ * <p>Note that the default semantics of this flag w.r.t. whether the recents entry for
+ * it is kept after the activity is finished is different than the use of
+ * {@link #FLAG_ACTIVITY_NEW_TASK} and {@link android.R.attr#documentLaunchMode} -- if
+ * this flag is being used to create a new recents entry, then by default that entry
+ * will be removed once the activity is finished. You can modify this behavior with
+ * {@link #FLAG_ACTIVITY_RETAIN_IN_RECENTS}.
+ *
+ * <p>FLAG_ACTIVITY_NEW_DOCUMENT may be used in conjunction with {@link
+ * #FLAG_ACTIVITY_MULTIPLE_TASK}. When used alone it is the
+ * equivalent of the Activity manifest specifying {@link
+ * android.R.attr#documentLaunchMode}="intoExisting". When used with
+ * FLAG_ACTIVITY_MULTIPLE_TASK it is the equivalent of the Activity manifest specifying
+ * {@link android.R.attr#documentLaunchMode}="always". The flag is ignored even in
+ * conjunction with {@link #FLAG_ACTIVITY_MULTIPLE_TASK} when the Activity manifest specifies
+ * {@link android.R.attr#documentLaunchMode}="never".
+ *
+ * Refer to {@link android.R.attr#documentLaunchMode} for more information.
+ *
+ * @see android.R.attr#documentLaunchMode
+ * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+ */
+ public static final int FLAG_ACTIVITY_NEW_DOCUMENT = FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;
+ /**
+ * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint}
+ * callback from occurring on the current frontmost activity before it is
+ * paused as the newly-started activity is brought to the front.
+ *
+ * <p>Typically, an activity can rely on that callback to indicate that an
+ * explicit user action has caused their activity to be moved out of the
+ * foreground. The callback marks an appropriate point in the activity's
+ * lifecycle for it to dismiss any notifications that it intends to display
+ * "until the user has seen them," such as a blinking LED.
+ *
+ * <p>If an activity is ever started via any non-user-driven events such as
+ * phone-call receipt or an alarm handler, this flag should be passed to {@link
+ * Context#startActivity Context.startActivity}, ensuring that the pausing
+ * activity does not think the user has acknowledged its notification.
+ */
+ public static final int FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000;
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will cause the launched activity to be brought to the front of its
+ * task's history stack if it is already running.
+ *
+ * <p>For example, consider a task consisting of four activities: A, B, C, D.
+ * If D calls startActivity() with an Intent that resolves to the component
+ * of activity B, then B will be brought to the front of the history stack,
+ * with this resulting order: A, C, D, B.
+ *
+ * This flag will be ignored if {@link #FLAG_ACTIVITY_CLEAR_TOP} is also
+ * specified.
+ */
+ public static final int FLAG_ACTIVITY_REORDER_TO_FRONT = 0X00020000;
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will prevent the system from applying an activity transition
+ * animation to go to the next activity state. This doesn't mean an
+ * animation will never run -- if another activity change happens that doesn't
+ * specify this flag before the activity started here is displayed, then
+ * that transition will be used. This flag can be put to good use
+ * when you are going to do a series of activity operations but the
+ * animation seen by the user shouldn't be driven by the first activity
+ * change but rather a later one.
+ */
+ public static final int FLAG_ACTIVITY_NO_ANIMATION = 0X00010000;
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will cause any existing task that would be associated with the
+ * activity to be cleared before the activity is started. That is, the activity
+ * becomes the new root of an otherwise empty task, and any old activities
+ * are finished. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}.
+ */
+ public static final int FLAG_ACTIVITY_CLEAR_TASK = 0X00008000;
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will cause a newly launching task to be placed on top of the current
+ * home activity task (if there is one). That is, pressing back from the task
+ * will always return the user to home even if that was not the last activity they
+ * saw. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}.
+ */
+ public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000;
+ /**
+ * By default a document created by {@link #FLAG_ACTIVITY_NEW_DOCUMENT} will
+ * have its entry in recent tasks removed when the user closes it (with back
+ * or however else it may finish()). If you would like to instead allow the
+ * document to be kept in recents so that it can be re-launched, you can use
+ * this flag. When set and the task's activity is finished, the recents
+ * entry will remain in the interface for the user to re-launch it, like a
+ * recents entry for a top-level application.
+ * <p>
+ * The receiving activity can override this request with
+ * {@link android.R.attr#autoRemoveFromRecents} or by explcitly calling
+ * {@link android.app.Activity#finishAndRemoveTask()
+ * Activity.finishAndRemoveTask()}.
+ */
+ public static final int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000;
+
+ /**
+ * This flag is only used for split-screen multi-window mode. The new activity will be displayed
+ * adjacent to the one launching it. This can only be used in conjunction with
+ * {@link #FLAG_ACTIVITY_NEW_TASK}. Also, setting {@link #FLAG_ACTIVITY_MULTIPLE_TASK} is
+ * required if you want a new instance of an existing activity to be created.
+ */
+ public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000;
+
+
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will attempt to launch an instant app if no full app on the device can already
+ * handle the intent.
+ * <p>
+ * When attempting to resolve instant apps externally, the following {@link Intent} properties
+ * are supported:
+ * <ul>
+ * <li>{@link Intent#setAction(String)}</li>
+ * <li>{@link Intent#addCategory(String)}</li>
+ * <li>{@link Intent#setData(Uri)}</li>
+ * <li>{@link Intent#setType(String)}</li>
+ * <li>{@link Intent#setPackage(String)}</li>
+ * <li>{@link Intent#addFlags(int)}</li>
+ * </ul>
+ * <p>
+ * In the case that no instant app can be found, the installer will be launched to notify the
+ * user that the intent could not be resolved. On devices that do not support instant apps,
+ * the flag will be ignored.
+ */
+ public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800;
+
+ /**
+ * If set in an intent passed to {@link Context#startActivity Context.startActivity()}, this
+ * flag will only launch the intent if it resolves to a result that is not a browser. If no such
+ * result exists, an {@link ActivityNotFoundException} will be thrown.
+ */
+ public static final int FLAG_ACTIVITY_REQUIRE_NON_BROWSER = 0x00000400;
+
+ /**
+ * If set in an intent passed to {@link Context#startActivity Context.startActivity()}, this
+ * flag will only launch the intent if it resolves to a single result. If no such result exists
+ * or if the system chooser would otherwise be displayed, an {@link ActivityNotFoundException}
+ * will be thrown.
+ */
+ public static final int FLAG_ACTIVITY_REQUIRE_DEFAULT = 0x00000200;
+
+ /**
+ * If set, when sending a broadcast only registered receivers will be
+ * called -- no BroadcastReceiver components will be launched.
+ */
+ public static final int FLAG_RECEIVER_REGISTERED_ONLY = 0x40000000;
+ /**
+ * If set, when sending a broadcast the new broadcast will replace
+ * any existing pending broadcast that matches it. Matching is defined
+ * by {@link Intent#filterEquals(Intent) Intent.filterEquals} returning
+ * true for the intents of the two broadcasts. When a match is found,
+ * the new broadcast (and receivers associated with it) will replace the
+ * existing one in the pending broadcast list, remaining at the same
+ * position in the list.
+ *
+ * <p>This flag is most typically used with sticky broadcasts, which
+ * only care about delivering the most recent values of the broadcast
+ * to their receivers.
+ */
+ public static final int FLAG_RECEIVER_REPLACE_PENDING = 0x20000000;
+ /**
+ * If set, when sending a broadcast the recipient is allowed to run at
+ * foreground priority, with a shorter timeout interval. During normal
+ * broadcasts the receivers are not automatically hoisted out of the
+ * background priority class.
+ */
+ public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000;
+ /**
+ * If set, when sending a broadcast the recipient will be run on the offload queue.
+ *
+ * @hide
+ */
+ public static final int FLAG_RECEIVER_OFFLOAD = 0x80000000;
+ /**
+ * If this is an ordered broadcast, don't allow receivers to abort the broadcast.
+ * They can still propagate results through to later receivers, but they can not prevent
+ * later receivers from seeing the broadcast.
+ */
+ public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
+ /**
+ * If set, when sending a broadcast <i>before the system has fully booted up
+ * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
+ * registered receivers will be called -- no BroadcastReceiver components
+ * will be launched. Sticky intent state will be recorded properly even
+ * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
+ * is specified in the broadcast intent, this flag is unnecessary.
+ *
+ * <p>This flag is only for use by system services (even services from mainline modules) as a
+ * convenience to avoid having to implement a more complex mechanism around detection
+ * of boot completion.
+ *
+ * <p>This is useful to system server mainline modules
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
+ /**
+ * Set when this broadcast is for a boot upgrade, a special mode that
+ * allows the broadcast to be sent before the system is ready and launches
+ * the app process with no providers running in it.
+ * @hide
+ */
+ public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x02000000;
+ /**
+ * If set, the broadcast will always go to manifest receivers in background (cached
+ * or not running) apps, regardless of whether that would be done by default. By
+ * default they will only receive broadcasts if the broadcast has specified an
+ * explicit component or package name.
+ *
+ * NOTE: dumpstate uses this flag numerically, so when its value is changed
+ * the broadcast code there must also be changed to match.
+ *
+ * @hide
+ */
+ public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
+ /**
+ * If set, the broadcast will never go to manifest receivers in background (cached
+ * or not running) apps, regardless of whether that would be done by default. By
+ * default they will receive broadcasts if the broadcast has specified an
+ * explicit component or package name.
+ * @hide
+ */
+ public static final int FLAG_RECEIVER_EXCLUDE_BACKGROUND = 0x00800000;
+ /**
+ * If set, this broadcast is being sent from the shell.
+ * @hide
+ */
+ public static final int FLAG_RECEIVER_FROM_SHELL = 0x00400000;
+
+ /**
+ * If set, the broadcast will be visible to receivers in Instant Apps. By default Instant Apps
+ * will not receive broadcasts.
+ *
+ * <em>This flag has no effect when used by an Instant App.</em>
+ */
+ public static final int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 0x00200000;
+
+ /**
+ * @hide Flags that can't be changed with PendingIntent.
+ */
+ public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+ /**
+ * Local flag indicating this instance was created by copy constructor.
+ */
+ private static final int LOCAL_FLAG_FROM_COPY = 1 << 0;
+
+ /**
+ * Local flag indicating this instance was created from a {@link Parcel}.
+ */
+ private static final int LOCAL_FLAG_FROM_PARCEL = 1 << 1;
+
+ /**
+ * Local flag indicating this instance was delivered through a protected
+ * component, such as an activity that requires a signature permission, or a
+ * protected broadcast. Note that this flag <em>cannot</em> be recursively
+ * applied to any contained instances, since a malicious app may have
+ * controlled them via {@link #fillIn(Intent, int)}.
+ */
+ private static final int LOCAL_FLAG_FROM_PROTECTED_COMPONENT = 1 << 2;
+
+ /**
+ * Local flag indicating this instance had unfiltered extras copied into it. This could be
+ * from either {@link #putExtras(Intent)} when an unparceled Intent is provided or {@link
+ * #putExtras(Bundle)} when the provided Bundle has not been unparceled.
+ */
+ private static final int LOCAL_FLAG_UNFILTERED_EXTRAS = 1 << 3;
+
+ /**
+ * Local flag indicating this instance was created from a {@link Uri}.
+ */
+ private static final int LOCAL_FLAG_FROM_URI = 1 << 4;
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // toUri() and parseUri() options.
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "URI_" }, value = {
+ URI_ALLOW_UNSAFE,
+ URI_ANDROID_APP_SCHEME,
+ URI_INTENT_SCHEME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UriFlags {}
+
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+ * always has the "intent:" scheme. This syntax can be used when you want
+ * to later disambiguate between URIs that are intended to describe an
+ * Intent vs. all others that should be treated as raw URIs. When used
+ * with {@link #parseUri}, any other scheme will result in a generic
+ * VIEW action for that raw URI.
+ */
+ public static final int URI_INTENT_SCHEME = 1<<0;
+
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+ * always has the "android-app:" scheme. This is a variation of
+ * {@link #URI_INTENT_SCHEME} whose format is simpler for the case of an
+ * http/https URI being delivered to a specific package name. The format
+ * is:
+ *
+ * <pre class="prettyprint">
+ * android-app://{package_id}[/{scheme}[/{host}[/{path}]]][#Intent;{...}]</pre>
+ *
+ * <p>In this scheme, only the <code>package_id</code> is required. If you include a host,
+ * you must also include a scheme; including a path also requires both a host and a scheme.
+ * The final #Intent; fragment can be used without a scheme, host, or path.
+ * Note that this can not be
+ * used with intents that have a {@link #setSelector}, since the base intent
+ * will always have an explicit package name.</p>
+ *
+ * <p>Some examples of how this scheme maps to Intent objects:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * <colgroup align="left" />
+ * <colgroup align="left" />
+ * <thead>
+ * <tr><th>URI</th> <th>Intent</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr><td><code>android-app://com.example.app</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_MAIN}</td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_VIEW}</td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com/foo?1234</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_VIEW}</td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/foo?1234</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;end</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com/foo?1234<br />#Intent;action=com.example.MY_ACTION;end</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/foo?1234</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;<br />i.some_int=100;S.some_str=hello;end</code></td>
+ * <td><table border="" style="margin:0" >
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * <tr><td>Extras: </td><td><code>some_int=(int)100<br />some_str=(String)hello</code></td></tr>
+ * </table></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ */
+ public static final int URI_ANDROID_APP_SCHEME = 1<<1;
+
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: allow parsing
+ * of unsafe information. In particular, the flags {@link #FLAG_GRANT_READ_URI_PERMISSION},
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, {@link #FLAG_GRANT_PERSISTABLE_URI_PERMISSION},
+ * and {@link #FLAG_GRANT_PREFIX_URI_PERMISSION} flags can not be set, so that the
+ * generated Intent can not cause unexpected data access to happen.
+ *
+ * <p>If you do not trust the source of the URI being parsed, you should still do further
+ * processing to protect yourself from it. In particular, when using it to start an
+ * activity you should usually add in {@link #CATEGORY_BROWSABLE} to limit the activities
+ * that can handle it.</p>
+ */
+ public static final int URI_ALLOW_UNSAFE = 1<<2;
+
+ // ---------------------------------------------------------------------
+
+ private String mAction;
+ private Uri mData;
+ private String mType;
+ private String mIdentifier;
+ private String mPackage;
+ private ComponentName mComponent;
+ private int mFlags;
+ /** Set of in-process flags which are never parceled */
+ private int mLocalFlags;
+ private ArraySet<String> mCategories;
+ @UnsupportedAppUsage
+ private Bundle mExtras;
+ private Rect mSourceBounds;
+ private Intent mSelector;
+ private ClipData mClipData;
+ private int mContentUserHint = UserHandle.USER_CURRENT;
+ /** Token to track instant app launches. Local only; do not copy cross-process. */
+ private String mLaunchToken;
+
+ // ---------------------------------------------------------------------
+
+ private static final int COPY_MODE_ALL = 0;
+ private static final int COPY_MODE_FILTER = 1;
+ private static final int COPY_MODE_HISTORY = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "COPY_MODE_" }, value = {
+ COPY_MODE_ALL,
+ COPY_MODE_FILTER,
+ COPY_MODE_HISTORY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CopyMode {}
+
+ /**
+ * Create an empty intent.
+ */
+ public Intent() {
+ }
+
+ /**
+ * Copy constructor.
+ */
+ public Intent(Intent o) {
+ this(o, COPY_MODE_ALL);
+ }
+
+ private Intent(Intent o, @CopyMode int copyMode) {
+ this.mAction = o.mAction;
+ this.mData = o.mData;
+ this.mType = o.mType;
+ this.mIdentifier = o.mIdentifier;
+ this.mPackage = o.mPackage;
+ this.mComponent = o.mComponent;
+
+ if (o.mCategories != null) {
+ this.mCategories = new ArraySet<>(o.mCategories);
+ }
+
+ // Inherit flags from the original, plus mark that we were
+ // created by this copy constructor
+ this.mLocalFlags = o.mLocalFlags;
+ this.mLocalFlags |= LOCAL_FLAG_FROM_COPY;
+
+ if (copyMode != COPY_MODE_FILTER) {
+ this.mFlags = o.mFlags;
+ this.mContentUserHint = o.mContentUserHint;
+ this.mLaunchToken = o.mLaunchToken;
+ if (o.mSourceBounds != null) {
+ this.mSourceBounds = new Rect(o.mSourceBounds);
+ }
+ if (o.mSelector != null) {
+ this.mSelector = new Intent(o.mSelector);
+ }
+
+ if (copyMode != COPY_MODE_HISTORY) {
+ if (o.mExtras != null) {
+ this.mExtras = new Bundle(o.mExtras);
+ }
+ if (o.mClipData != null) {
+ this.mClipData = new ClipData(o.mClipData);
+ }
+ } else {
+ if (o.mExtras != null && !o.mExtras.isDefinitelyEmpty()) {
+ this.mExtras = Bundle.STRIPPED;
+ }
+
+ // Also set "stripped" clip data when we ever log mClipData in the (broadcast)
+ // history.
+ }
+ }
+ }
+
+ @Override
+ public Object clone() {
+ return new Intent(this);
+ }
+
+ /**
+ * Make a clone of only the parts of the Intent that are relevant for
+ * filter matching: the action, data, type, component, and categories.
+ */
+ public @NonNull Intent cloneFilter() {
+ return new Intent(this, COPY_MODE_FILTER);
+ }
+
+ /**
+ * Create an intent with a given action. All other fields (data, type,
+ * class) are null. Note that the action <em>must</em> be in a
+ * namespace because Intents are used globally in the system -- for
+ * example the system VIEW action is android.intent.action.VIEW; an
+ * application's custom action would be something like
+ * com.google.app.myapp.CUSTOM_ACTION.
+ *
+ * @param action The Intent action, such as ACTION_VIEW.
+ */
+ public Intent(String action) {
+ setAction(action);
+ }
+
+ /**
+ * Create an intent with a given action and for a given data url. Note
+ * that the action <em>must</em> be in a namespace because Intents are
+ * used globally in the system -- for example the system VIEW action is
+ * android.intent.action.VIEW; an application's custom action would be
+ * something like com.google.app.myapp.CUSTOM_ACTION.
+ *
+ * <p><em>Note: scheme and host name matching in the Android framework is
+ * case-sensitive, unlike the formal RFC. As a result,
+ * you should always ensure that you write your Uri with these elements
+ * using lower case letters, and normalize any Uris you receive from
+ * outside of Android to ensure the scheme and host is lower case.</em></p>
+ *
+ * @param action The Intent action, such as ACTION_VIEW.
+ * @param uri The Intent data URI.
+ */
+ public Intent(String action, Uri uri) {
+ setAction(action);
+ mData = uri;
+ }
+
+ /**
+ * Create an intent for a specific component. All other fields (action, data,
+ * type, class) are null, though they can be modified later with explicit
+ * calls. This provides a convenient way to create an intent that is
+ * intended to execute a hard-coded class name, rather than relying on the
+ * system to find an appropriate class for you; see {@link #setComponent}
+ * for more information on the repercussions of this.
+ *
+ * @param packageContext A Context of the application package implementing
+ * this class.
+ * @param cls The component class that is to be used for the intent.
+ *
+ * @see #setClass
+ * @see #setComponent
+ * @see #Intent(String, android.net.Uri , Context, Class)
+ */
+ public Intent(Context packageContext, Class<?> cls) {
+ mComponent = new ComponentName(packageContext, cls);
+ }
+
+ /**
+ * Create an intent for a specific component with a specified action and data.
+ * This is equivalent to using {@link #Intent(String, android.net.Uri)} to
+ * construct the Intent and then calling {@link #setClass} to set its
+ * class.
+ *
+ * <p><em>Note: scheme and host name matching in the Android framework is
+ * case-sensitive, unlike the formal RFC. As a result,
+ * you should always ensure that you write your Uri with these elements
+ * using lower case letters, and normalize any Uris you receive from
+ * outside of Android to ensure the scheme and host is lower case.</em></p>
+ *
+ * @param action The Intent action, such as ACTION_VIEW.
+ * @param uri The Intent data URI.
+ * @param packageContext A Context of the application package implementing
+ * this class.
+ * @param cls The component class that is to be used for the intent.
+ *
+ * @see #Intent(String, android.net.Uri)
+ * @see #Intent(Context, Class)
+ * @see #setClass
+ * @see #setComponent
+ */
+ public Intent(String action, Uri uri,
+ Context packageContext, Class<?> cls) {
+ setAction(action);
+ mData = uri;
+ mComponent = new ComponentName(packageContext, cls);
+ }
+
+ /**
+ * Create an intent to launch the main (root) activity of a task. This
+ * is the Intent that is started when the application's is launched from
+ * Home. For anything else that wants to launch an application in the
+ * same way, it is important that they use an Intent structured the same
+ * way, and can use this function to ensure this is the case.
+ *
+ * <p>The returned Intent has the given Activity component as its explicit
+ * component, {@link #ACTION_MAIN} as its action, and includes the
+ * category {@link #CATEGORY_LAUNCHER}. This does <em>not</em> have
+ * {@link #FLAG_ACTIVITY_NEW_TASK} set, though typically you will want
+ * to do that through {@link #addFlags(int)} on the returned Intent.
+ *
+ * @param mainActivity The main activity component that this Intent will
+ * launch.
+ * @return Returns a newly created Intent that can be used to launch the
+ * activity as a main application entry.
+ *
+ * @see #setClass
+ * @see #setComponent
+ */
+ public static Intent makeMainActivity(ComponentName mainActivity) {
+ Intent intent = new Intent(ACTION_MAIN);
+ intent.setComponent(mainActivity);
+ intent.addCategory(CATEGORY_LAUNCHER);
+ return intent;
+ }
+
+ /**
+ * Make an Intent for the main activity of an application, without
+ * specifying a specific activity to run but giving a selector to find
+ * the activity. This results in a final Intent that is structured
+ * the same as when the application is launched from
+ * Home. For anything else that wants to launch an application in the
+ * same way, it is important that they use an Intent structured the same
+ * way, and can use this function to ensure this is the case.
+ *
+ * <p>The returned Intent has {@link #ACTION_MAIN} as its action, and includes the
+ * category {@link #CATEGORY_LAUNCHER}. This does <em>not</em> have
+ * {@link #FLAG_ACTIVITY_NEW_TASK} set, though typically you will want
+ * to do that through {@link #addFlags(int)} on the returned Intent.
+ *
+ * @param selectorAction The action name of the Intent's selector.
+ * @param selectorCategory The name of a category to add to the Intent's
+ * selector.
+ * @return Returns a newly created Intent that can be used to launch the
+ * activity as a main application entry.
+ *
+ * @see #setSelector(Intent)
+ */
+ public static Intent makeMainSelectorActivity(String selectorAction,
+ String selectorCategory) {
+ Intent intent = new Intent(ACTION_MAIN);
+ intent.addCategory(CATEGORY_LAUNCHER);
+ Intent selector = new Intent();
+ selector.setAction(selectorAction);
+ selector.addCategory(selectorCategory);
+ intent.setSelector(selector);
+ return intent;
+ }
+
+ /**
+ * Make an Intent that can be used to re-launch an application's task
+ * in its base state. This is like {@link #makeMainActivity(ComponentName)},
+ * but also sets the flags {@link #FLAG_ACTIVITY_NEW_TASK} and
+ * {@link #FLAG_ACTIVITY_CLEAR_TASK}.
+ *
+ * @param mainActivity The activity component that is the root of the
+ * task; this is the activity that has been published in the application's
+ * manifest as the main launcher icon.
+ *
+ * @return Returns a newly created Intent that can be used to relaunch the
+ * activity's task in its root state.
+ */
+ public static Intent makeRestartActivityTask(ComponentName mainActivity) {
+ Intent intent = makeMainActivity(mainActivity);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return intent;
+ }
+
+ /**
+ * Call {@link #parseUri} with 0 flags.
+ * @deprecated Use {@link #parseUri} instead.
+ */
+ @Deprecated
+ public static Intent getIntent(String uri) throws URISyntaxException {
+ return parseUri(uri, 0);
+ }
+
+ /**
+ * Create an intent from a URI. This URI may encode the action,
+ * category, and other intent fields, if it was returned by
+ * {@link #toUri}. If the Intent was not generate by toUri(), its data
+ * will be the entire URI and its action will be ACTION_VIEW.
+ *
+ * <p>The URI given here must not be relative -- that is, it must include
+ * the scheme and full path.
+ *
+ * @param uri The URI to turn into an Intent.
+ * @param flags Additional processing flags.
+ *
+ * @return Intent The newly created Intent object.
+ *
+ * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax
+ * it bad (as parsed by the Uri class) or the Intent data within the
+ * URI is invalid.
+ *
+ * @see #toUri
+ */
+ public static Intent parseUri(String uri, @UriFlags int flags) throws URISyntaxException {
+ Intent intent = parseUriInternal(uri, flags);
+ intent.mLocalFlags |= LOCAL_FLAG_FROM_URI;
+ return intent;
+ }
+
+ /**
+ * @see #parseUri(String, int)
+ */
+ private static Intent parseUriInternal(String uri, @UriFlags int flags)
+ throws URISyntaxException {
+ int i = 0;
+ try {
+ final boolean androidApp = uri.startsWith("android-app:");
+
+ // Validate intent scheme if requested.
+ if ((flags&(URI_INTENT_SCHEME|URI_ANDROID_APP_SCHEME)) != 0) {
+ if (!uri.startsWith("intent:") && !androidApp) {
+ Intent intent = new Intent(ACTION_VIEW);
+ try {
+ intent.setData(Uri.parse(uri));
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(uri, e.getMessage());
+ }
+ return intent;
+ }
+ }
+
+ i = uri.lastIndexOf("#");
+ // simple case
+ if (i == -1) {
+ if (!androidApp) {
+ return new Intent(ACTION_VIEW, Uri.parse(uri));
+ }
+
+ // old format Intent URI
+ } else if (!uri.startsWith("#Intent;", i)) {
+ if (!androidApp) {
+ return getIntentOld(uri, flags);
+ } else {
+ i = -1;
+ }
+ }
+
+ // new format
+ Intent intent = new Intent(ACTION_VIEW);
+ Intent baseIntent = intent;
+ boolean explicitAction = false;
+ boolean inSelector = false;
+
+ // fetch data part, if present
+ String scheme = null;
+ String data;
+ if (i >= 0) {
+ data = uri.substring(0, i);
+ i += 8; // length of "#Intent;"
+ } else {
+ data = uri;
+ }
+
+ // loop over contents of Intent, all name=value;
+ while (i >= 0 && !uri.startsWith("end", i)) {
+ int eq = uri.indexOf('=', i);
+ if (eq < 0) eq = i-1;
+ int semi = uri.indexOf(';', i);
+ String value = eq < semi ? Uri.decode(uri.substring(eq + 1, semi)) : "";
+
+ // action
+ if (uri.startsWith("action=", i)) {
+ intent.setAction(value);
+ if (!inSelector) {
+ explicitAction = true;
+ }
+ }
+
+ // categories
+ else if (uri.startsWith("category=", i)) {
+ intent.addCategory(value);
+ }
+
+ // type
+ else if (uri.startsWith("type=", i)) {
+ intent.mType = value;
+ }
+
+ // identifier
+ else if (uri.startsWith("identifier=", i)) {
+ intent.mIdentifier = value;
+ }
+
+ // launch flags
+ else if (uri.startsWith("launchFlags=", i)) {
+ intent.mFlags = Integer.decode(value).intValue();
+ if ((flags& URI_ALLOW_UNSAFE) == 0) {
+ intent.mFlags &= ~IMMUTABLE_FLAGS;
+ }
+ }
+
+ // package
+ else if (uri.startsWith("package=", i)) {
+ intent.mPackage = value;
+ }
+
+ // component
+ else if (uri.startsWith("component=", i)) {
+ intent.mComponent = ComponentName.unflattenFromString(value);
+ }
+
+ // scheme
+ else if (uri.startsWith("scheme=", i)) {
+ if (inSelector) {
+ intent.mData = Uri.parse(value + ":");
+ } else {
+ scheme = value;
+ }
+ }
+
+ // source bounds
+ else if (uri.startsWith("sourceBounds=", i)) {
+ intent.mSourceBounds = Rect.unflattenFromString(value);
+ }
+
+ // selector
+ else if (semi == (i+3) && uri.startsWith("SEL", i)) {
+ intent = new Intent();
+ inSelector = true;
+ }
+
+ // extra
+ else {
+ String key = Uri.decode(uri.substring(i + 2, eq));
+ // create Bundle if it doesn't already exist
+ if (intent.mExtras == null) intent.mExtras = new Bundle();
+ Bundle b = intent.mExtras;
+ // add EXTRA
+ if (uri.startsWith("S.", i)) b.putString(key, value);
+ else if (uri.startsWith("B.", i)) b.putBoolean(key, Boolean.parseBoolean(value));
+ else if (uri.startsWith("b.", i)) b.putByte(key, Byte.parseByte(value));
+ else if (uri.startsWith("c.", i)) b.putChar(key, value.charAt(0));
+ else if (uri.startsWith("d.", i)) b.putDouble(key, Double.parseDouble(value));
+ else if (uri.startsWith("f.", i)) b.putFloat(key, Float.parseFloat(value));
+ else if (uri.startsWith("i.", i)) b.putInt(key, Integer.parseInt(value));
+ else if (uri.startsWith("l.", i)) b.putLong(key, Long.parseLong(value));
+ else if (uri.startsWith("s.", i)) b.putShort(key, Short.parseShort(value));
+ else throw new URISyntaxException(uri, "unknown EXTRA type", i);
+ }
+
+ // move to the next item
+ i = semi + 1;
+ }
+
+ if (inSelector) {
+ // The Intent had a selector; fix it up.
+ if (baseIntent.mPackage == null) {
+ baseIntent.setSelector(intent);
+ }
+ intent = baseIntent;
+ }
+
+ if (data != null) {
+ if (data.startsWith("intent:")) {
+ data = data.substring(7);
+ if (scheme != null) {
+ data = scheme + ':' + data;
+ }
+ } else if (data.startsWith("android-app:")) {
+ if (data.charAt(12) == '/' && data.charAt(13) == '/') {
+ // Correctly formed android-app, first part is package name.
+ int end = data.indexOf('/', 14);
+ if (end < 0) {
+ // All we have is a package name.
+ intent.mPackage = data.substring(14);
+ if (!explicitAction) {
+ intent.setAction(ACTION_MAIN);
+ }
+ data = "";
+ } else {
+ // Target the Intent at the given package name always.
+ String authority = null;
+ intent.mPackage = data.substring(14, end);
+ int newEnd;
+ if ((end+1) < data.length()) {
+ if ((newEnd=data.indexOf('/', end+1)) >= 0) {
+ // Found a scheme, remember it.
+ scheme = data.substring(end+1, newEnd);
+ end = newEnd;
+ if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) {
+ // Found a authority, remember it.
+ authority = data.substring(end+1, newEnd);
+ end = newEnd;
+ }
+ } else {
+ // All we have is a scheme.
+ scheme = data.substring(end+1);
+ }
+ }
+ if (scheme == null) {
+ // If there was no scheme, then this just targets the package.
+ if (!explicitAction) {
+ intent.setAction(ACTION_MAIN);
+ }
+ data = "";
+ } else if (authority == null) {
+ data = scheme + ":";
+ } else {
+ data = scheme + "://" + authority + data.substring(end);
+ }
+ }
+ } else {
+ data = "";
+ }
+ }
+
+ if (data.length() > 0) {
+ try {
+ intent.mData = Uri.parse(data);
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(uri, e.getMessage());
+ }
+ }
+ }
+
+ return intent;
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new URISyntaxException(uri, "illegal Intent URI format", i);
+ }
+ }
+
+ public static Intent getIntentOld(String uri) throws URISyntaxException {
+ Intent intent = getIntentOld(uri, 0);
+ intent.mLocalFlags |= LOCAL_FLAG_FROM_URI;
+ return intent;
+ }
+
+ private static Intent getIntentOld(String uri, int flags) throws URISyntaxException {
+ Intent intent;
+
+ int i = uri.lastIndexOf('#');
+ if (i >= 0) {
+ String action = null;
+ final int intentFragmentStart = i;
+ boolean isIntentFragment = false;
+
+ i++;
+
+ if (uri.regionMatches(i, "action(", 0, 7)) {
+ isIntentFragment = true;
+ i += 7;
+ int j = uri.indexOf(')', i);
+ action = uri.substring(i, j);
+ i = j + 1;
+ }
+
+ intent = new Intent(action);
+
+ if (uri.regionMatches(i, "categories(", 0, 11)) {
+ isIntentFragment = true;
+ i += 11;
+ int j = uri.indexOf(')', i);
+ while (i < j) {
+ int sep = uri.indexOf('!', i);
+ if (sep < 0 || sep > j) sep = j;
+ if (i < sep) {
+ intent.addCategory(uri.substring(i, sep));
+ }
+ i = sep + 1;
+ }
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "type(", 0, 5)) {
+ isIntentFragment = true;
+ i += 5;
+ int j = uri.indexOf(')', i);
+ intent.mType = uri.substring(i, j);
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "launchFlags(", 0, 12)) {
+ isIntentFragment = true;
+ i += 12;
+ int j = uri.indexOf(')', i);
+ intent.mFlags = Integer.decode(uri.substring(i, j)).intValue();
+ if ((flags& URI_ALLOW_UNSAFE) == 0) {
+ intent.mFlags &= ~IMMUTABLE_FLAGS;
+ }
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "component(", 0, 10)) {
+ isIntentFragment = true;
+ i += 10;
+ int j = uri.indexOf(')', i);
+ int sep = uri.indexOf('!', i);
+ if (sep >= 0 && sep < j) {
+ String pkg = uri.substring(i, sep);
+ String cls = uri.substring(sep + 1, j);
+ intent.mComponent = new ComponentName(pkg, cls);
+ }
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "extras(", 0, 7)) {
+ isIntentFragment = true;
+ i += 7;
+
+ final int closeParen = uri.indexOf(')', i);
+ if (closeParen == -1) throw new URISyntaxException(uri,
+ "EXTRA missing trailing ')'", i);
+
+ while (i < closeParen) {
+ // fetch the key value
+ int j = uri.indexOf('=', i);
+ if (j <= i + 1 || i >= closeParen) {
+ throw new URISyntaxException(uri, "EXTRA missing '='", i);
+ }
+ char type = uri.charAt(i);
+ i++;
+ String key = uri.substring(i, j);
+ i = j + 1;
+
+ // get type-value
+ j = uri.indexOf('!', i);
+ if (j == -1 || j >= closeParen) j = closeParen;
+ if (i >= j) throw new URISyntaxException(uri, "EXTRA missing '!'", i);
+ String value = uri.substring(i, j);
+ i = j;
+
+ // create Bundle if it doesn't already exist
+ if (intent.mExtras == null) intent.mExtras = new Bundle();
+
+ // add item to bundle
+ try {
+ switch (type) {
+ case 'S':
+ intent.mExtras.putString(key, Uri.decode(value));
+ break;
+ case 'B':
+ intent.mExtras.putBoolean(key, Boolean.parseBoolean(value));
+ break;
+ case 'b':
+ intent.mExtras.putByte(key, Byte.parseByte(value));
+ break;
+ case 'c':
+ intent.mExtras.putChar(key, Uri.decode(value).charAt(0));
+ break;
+ case 'd':
+ intent.mExtras.putDouble(key, Double.parseDouble(value));
+ break;
+ case 'f':
+ intent.mExtras.putFloat(key, Float.parseFloat(value));
+ break;
+ case 'i':
+ intent.mExtras.putInt(key, Integer.parseInt(value));
+ break;
+ case 'l':
+ intent.mExtras.putLong(key, Long.parseLong(value));
+ break;
+ case 's':
+ intent.mExtras.putShort(key, Short.parseShort(value));
+ break;
+ default:
+ throw new URISyntaxException(uri, "EXTRA has unknown type", i);
+ }
+ } catch (NumberFormatException e) {
+ throw new URISyntaxException(uri, "EXTRA value can't be parsed", i);
+ }
+
+ char ch = uri.charAt(i);
+ if (ch == ')') break;
+ if (ch != '!') throw new URISyntaxException(uri, "EXTRA missing '!'", i);
+ i++;
+ }
+ }
+
+ if (isIntentFragment) {
+ intent.mData = Uri.parse(uri.substring(0, intentFragmentStart));
+ } else {
+ intent.mData = Uri.parse(uri);
+ }
+
+ if (intent.mAction == null) {
+ // By default, if no action is specified, then use VIEW.
+ intent.mAction = ACTION_VIEW;
+ }
+
+ } else {
+ intent = new Intent(ACTION_VIEW, Uri.parse(uri));
+ }
+
+ return intent;
+ }
+
+ /** @hide */
+ public interface CommandOptionHandler {
+ boolean handleOption(String opt, ShellCommand cmd);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SuppressWarnings("AndroidFrameworkEfficientCollections")
+ public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
+ throws URISyntaxException {
+ Intent intent = new Intent();
+ Intent baseIntent = intent;
+ boolean hasIntentInfo = false;
+
+ Uri data = null;
+ String type = null;
+
+ String opt;
+ while ((opt=cmd.getNextOption()) != null) {
+ switch (opt) {
+ case "-a":
+ intent.setAction(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-d":
+ data = Uri.parse(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-t":
+ type = cmd.getNextArgRequired();
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-i":
+ intent.setIdentifier(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-c":
+ intent.addCategory(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-e":
+ case "--es": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, value);
+ }
+ break;
+ case "--esn": {
+ String key = cmd.getNextArgRequired();
+ intent.putExtra(key, (String) null);
+ }
+ break;
+ case "--ei": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Integer.decode(value));
+ }
+ break;
+ case "--eu": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Uri.parse(value));
+ }
+ break;
+ case "--ecn": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ ComponentName cn = ComponentName.unflattenFromString(value);
+ if (cn == null)
+ throw new IllegalArgumentException("Bad component name: " + value);
+ intent.putExtra(key, cn);
+ }
+ break;
+ case "--eia": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ int[] list = new int[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Integer.decode(strings[i]);
+ }
+ intent.putExtra(key, list);
+ }
+ break;
+ case "--eial": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ ArrayList<Integer> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(Integer.decode(strings[i]));
+ }
+ intent.putExtra(key, list);
+ }
+ break;
+ case "--el": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Long.valueOf(value));
+ }
+ break;
+ case "--ela": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ long[] list = new long[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Long.valueOf(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--elal": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ ArrayList<Long> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(Long.valueOf(strings[i]));
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--ef": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Float.valueOf(value));
+ hasIntentInfo = true;
+ }
+ break;
+ case "--efa": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ float[] list = new float[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Float.valueOf(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--efal": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ ArrayList<Float> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(Float.valueOf(strings[i]));
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--esa": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ // Split on commas unless they are preceeded by an escape.
+ // The escape character must be escaped for the string and
+ // again for the regex, thus four escape characters become one.
+ String[] strings = value.split("(?<!\\\\),");
+ intent.putExtra(key, strings);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--esal": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ // Split on commas unless they are preceeded by an escape.
+ // The escape character must be escaped for the string and
+ // again for the regex, thus four escape characters become one.
+ String[] strings = value.split("(?<!\\\\),");
+ ArrayList<String> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--ez": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired().toLowerCase();
+ // Boolean.valueOf() results in false for anything that is not "true", which is
+ // error-prone in shell commands
+ boolean arg;
+ if ("true".equals(value) || "t".equals(value)) {
+ arg = true;
+ } else if ("false".equals(value) || "f".equals(value)) {
+ arg = false;
+ } else {
+ try {
+ arg = Integer.decode(value) != 0;
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException("Invalid boolean value: " + value);
+ }
+ }
+
+ intent.putExtra(key, arg);
+ }
+ break;
+ case "-n": {
+ String str = cmd.getNextArgRequired();
+ ComponentName cn = ComponentName.unflattenFromString(str);
+ if (cn == null)
+ throw new IllegalArgumentException("Bad component name: " + str);
+ intent.setComponent(cn);
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ }
+ break;
+ case "-p": {
+ String str = cmd.getNextArgRequired();
+ intent.setPackage(str);
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ }
+ break;
+ case "-f":
+ String str = cmd.getNextArgRequired();
+ intent.setFlags(Integer.decode(str).intValue());
+ break;
+ case "--grant-read-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ break;
+ case "--grant-write-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ break;
+ case "--grant-persistable-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ break;
+ case "--grant-prefix-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ break;
+ case "--exclude-stopped-packages":
+ intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+ break;
+ case "--include-stopped-packages":
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ break;
+ case "--debug-log-resolution":
+ intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+ break;
+ case "--activity-brought-to-front":
+ intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+ break;
+ case "--activity-clear-top":
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ break;
+ case "--activity-clear-when-task-reset":
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ break;
+ case "--activity-exclude-from-recents":
+ intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ break;
+ case "--activity-launched-from-history":
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+ break;
+ case "--activity-multiple-task":
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ break;
+ case "--activity-no-animation":
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ break;
+ case "--activity-no-history":
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+ break;
+ case "--activity-no-user-action":
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ break;
+ case "--activity-previous-is-top":
+ intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ break;
+ case "--activity-reorder-to-front":
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ break;
+ case "--activity-reset-task-if-needed":
+ intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ break;
+ case "--activity-single-top":
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ break;
+ case "--activity-clear-task":
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ break;
+ case "--activity-task-on-home":
+ intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ break;
+ case "--activity-match-external":
+ intent.addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
+ break;
+ case "--receiver-registered-only":
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ break;
+ case "--receiver-replace-pending":
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ break;
+ case "--receiver-foreground":
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ break;
+ case "--receiver-no-abort":
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+ break;
+ case "--receiver-include-background":
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ break;
+ case "--selector":
+ intent.setDataAndType(data, type);
+ intent = new Intent();
+ break;
+ default:
+ if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
+ // Okay, caller handled this option.
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ break;
+ }
+ }
+ intent.setDataAndType(data, type);
+
+ final boolean hasSelector = intent != baseIntent;
+ if (hasSelector) {
+ // A selector was specified; fix up.
+ baseIntent.setSelector(intent);
+ intent = baseIntent;
+ }
+
+ String arg = cmd.getNextArg();
+ baseIntent = null;
+ if (arg == null) {
+ if (hasSelector) {
+ // If a selector has been specified, and no arguments
+ // have been supplied for the main Intent, then we can
+ // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
+ // need to have a component name specified yet, the
+ // selector will take care of that.
+ baseIntent = new Intent(Intent.ACTION_MAIN);
+ baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ }
+ } else if (arg.indexOf(':') >= 0) {
+ // The argument is a URI. Fully parse it, and use that result
+ // to fill in any data not specified so far.
+ baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
+ | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
+ } else if (arg.indexOf('/') >= 0) {
+ // The argument is a component name. Build an Intent to launch
+ // it.
+ baseIntent = new Intent(Intent.ACTION_MAIN);
+ baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ baseIntent.setComponent(ComponentName.unflattenFromString(arg));
+ } else {
+ // Assume the argument is a package name.
+ baseIntent = new Intent(Intent.ACTION_MAIN);
+ baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ baseIntent.setPackage(arg);
+ }
+ if (baseIntent != null) {
+ Bundle extras = intent.getExtras();
+ intent.replaceExtras((Bundle)null);
+ Bundle uriExtras = baseIntent.getExtras();
+ baseIntent.replaceExtras((Bundle)null);
+ if (intent.getAction() != null && baseIntent.getCategories() != null) {
+ HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
+ for (String c : cats) {
+ baseIntent.removeCategory(c);
+ }
+ }
+ intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
+ if (extras == null) {
+ extras = uriExtras;
+ } else if (uriExtras != null) {
+ uriExtras.putAll(extras);
+ extras = uriExtras;
+ }
+ intent.replaceExtras(extras);
+ hasIntentInfo = true;
+ }
+
+ if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
+ return intent;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static void printIntentArgsHelp(PrintWriter pw, String prefix) {
+ final String[] lines = new String[] {
+ "<INTENT> specifications include these flags and arguments:",
+ " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>] [-i <IDENTIFIER>]",
+ " [-c <CATEGORY> [-c <CATEGORY>] ...]",
+ " [-n <COMPONENT_NAME>]",
+ " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]",
+ " [--esn <EXTRA_KEY> ...]",
+ " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]",
+ " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]",
+ " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]",
+ " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]",
+ " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]",
+ " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]",
+ " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+ " (mutiple extras passed as Integer[])",
+ " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+ " (mutiple extras passed as List<Integer>)",
+ " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+ " (mutiple extras passed as Long[])",
+ " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+ " (mutiple extras passed as List<Long>)",
+ " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+ " (mutiple extras passed as Float[])",
+ " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+ " (mutiple extras passed as List<Float>)",
+ " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+ " (mutiple extras passed as String[]; to embed a comma into a string,",
+ " escape it using \"\\,\")",
+ " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+ " (mutiple extras passed as List<String>; to embed a comma into a string,",
+ " escape it using \"\\,\")",
+ " [-f <FLAG>]",
+ " [--grant-read-uri-permission] [--grant-write-uri-permission]",
+ " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]",
+ " [--debug-log-resolution] [--exclude-stopped-packages]",
+ " [--include-stopped-packages]",
+ " [--activity-brought-to-front] [--activity-clear-top]",
+ " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]",
+ " [--activity-launched-from-history] [--activity-multiple-task]",
+ " [--activity-no-animation] [--activity-no-history]",
+ " [--activity-no-user-action] [--activity-previous-is-top]",
+ " [--activity-reorder-to-front] [--activity-reset-task-if-needed]",
+ " [--activity-single-top] [--activity-clear-task]",
+ " [--activity-task-on-home] [--activity-match-external]",
+ " [--receiver-registered-only] [--receiver-replace-pending]",
+ " [--receiver-foreground] [--receiver-no-abort]",
+ " [--receiver-include-background]",
+ " [--selector]",
+ " [<URI> | <PACKAGE> | <COMPONENT>]"
+ };
+ for (String line : lines) {
+ pw.print(prefix);
+ pw.println(line);
+ }
+ }
+
+ /**
+ * Retrieve the general action to be performed, such as
+ * {@link #ACTION_VIEW}. The action describes the general way the rest of
+ * the information in the intent should be interpreted -- most importantly,
+ * what to do with the data returned by {@link #getData}.
+ *
+ * @return The action of this intent or null if none is specified.
+ *
+ * @see #setAction
+ */
+ public @Nullable String getAction() {
+ return mAction;
+ }
+
+ /**
+ * Retrieve data this intent is operating on. This URI specifies the name
+ * of the data; often it uses the content: scheme, specifying data in a
+ * content provider. Other schemes may be handled by specific activities,
+ * such as http: by the web browser.
+ *
+ * @return The URI of the data this intent is targeting or null.
+ *
+ * @see #getScheme
+ * @see #setData
+ */
+ public @Nullable Uri getData() {
+ return mData;
+ }
+
+ /**
+ * The same as {@link #getData()}, but returns the URI as an encoded
+ * String.
+ */
+ public @Nullable String getDataString() {
+ return mData != null ? mData.toString() : null;
+ }
+
+ /**
+ * Return the scheme portion of the intent's data. If the data is null or
+ * does not include a scheme, null is returned. Otherwise, the scheme
+ * prefix without the final ':' is returned, i.e. "http".
+ *
+ * <p>This is the same as calling getData().getScheme() (and checking for
+ * null data).
+ *
+ * @return The scheme of this intent.
+ *
+ * @see #getData
+ */
+ public @Nullable String getScheme() {
+ return mData != null ? mData.getScheme() : null;
+ }
+
+ /**
+ * Retrieve any explicit MIME type included in the intent. This is usually
+ * null, as the type is determined by the intent data.
+ *
+ * @return If a type was manually set, it is returned; else null is
+ * returned.
+ *
+ * @see #resolveType(ContentResolver)
+ * @see #setType
+ */
+ public @Nullable String getType() {
+ return mType;
+ }
+
+ /**
+ * Return the MIME data type of this intent. If the type field is
+ * explicitly set, that is simply returned. Otherwise, if the data is set,
+ * the type of that data is returned. If neither fields are set, a null is
+ * returned.
+ *
+ * @return The MIME type of this intent.
+ *
+ * @see #getType
+ * @see #resolveType(ContentResolver)
+ */
+ public @Nullable String resolveType(@NonNull Context context) {
+ return resolveType(context.getContentResolver());
+ }
+
+ /**
+ * Return the MIME data type of this intent. If the type field is
+ * explicitly set, that is simply returned. Otherwise, if the data is set,
+ * the type of that data is returned. If neither fields are set, a null is
+ * returned.
+ *
+ * @param resolver A ContentResolver that can be used to determine the MIME
+ * type of the intent's data.
+ *
+ * @return The MIME type of this intent.
+ *
+ * @see #getType
+ * @see #resolveType(Context)
+ */
+ public @Nullable String resolveType(@NonNull ContentResolver resolver) {
+ if (mType != null) {
+ return mType;
+ }
+ if (mData != null) {
+ if ("content".equals(mData.getScheme())) {
+ return resolver.getType(mData);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the MIME data type of this intent, only if it will be needed for
+ * intent resolution. This is not generally useful for application code;
+ * it is used by the frameworks for communicating with back-end system
+ * services.
+ *
+ * @param resolver A ContentResolver that can be used to determine the MIME
+ * type of the intent's data.
+ *
+ * @return The MIME type of this intent, or null if it is unknown or not
+ * needed.
+ */
+ public @Nullable String resolveTypeIfNeeded(@NonNull ContentResolver resolver) {
+ if (mComponent != null) {
+ return mType;
+ }
+ return resolveType(resolver);
+ }
+
+ /**
+ * Retrieve the identifier for this Intent. If non-null, this is an arbitrary identity
+ * of the Intent to distinguish it from other Intents.
+ *
+ * @return The identifier of this intent or null if none is specified.
+ *
+ * @see #setIdentifier
+ */
+ public @Nullable String getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * Check if a category exists in the intent.
+ *
+ * @param category The category to check.
+ *
+ * @return boolean True if the intent contains the category, else false.
+ *
+ * @see #getCategories
+ * @see #addCategory
+ */
+ public boolean hasCategory(String category) {
+ return mCategories != null && mCategories.contains(category);
+ }
+
+ /**
+ * Return the set of all categories in the intent. If there are no categories,
+ * returns NULL.
+ *
+ * @return The set of categories you can examine. Do not modify!
+ *
+ * @see #hasCategory
+ * @see #addCategory
+ */
+ public Set<String> getCategories() {
+ return mCategories;
+ }
+
+ /**
+ * Return the specific selector associated with this Intent. If there is
+ * none, returns null. See {@link #setSelector} for more information.
+ *
+ * @see #setSelector
+ */
+ public @Nullable Intent getSelector() {
+ return mSelector;
+ }
+
+ /**
+ * Return the {@link ClipData} associated with this Intent. If there is
+ * none, returns null. See {@link #setClipData} for more information.
+ *
+ * @see #setClipData
+ */
+ public @Nullable ClipData getClipData() {
+ return mClipData;
+ }
+
+ /** @hide */
+ public int getContentUserHint() {
+ return mContentUserHint;
+ }
+
+ /** @hide */
+ public String getLaunchToken() {
+ return mLaunchToken;
+ }
+
+ /** @hide */
+ public void setLaunchToken(String launchToken) {
+ mLaunchToken = launchToken;
+ }
+
+ /**
+ * Sets the ClassLoader that will be used when unmarshalling
+ * any Parcelable values from the extras of this Intent.
+ *
+ * @param loader a ClassLoader, or null to use the default loader
+ * at the time of unmarshalling.
+ */
+ public void setExtrasClassLoader(@Nullable ClassLoader loader) {
+ if (mExtras != null) {
+ mExtras.setClassLoader(loader);
+ }
+ }
+
+ /**
+ * Returns true if an extra value is associated with the given name.
+ * @param name the extra's name
+ * @return true if the given extra is present.
+ */
+ public boolean hasExtra(String name) {
+ return mExtras != null && mExtras.containsKey(name);
+ }
+
+ /**
+ * Returns true if the Intent's extras contain a parcelled file descriptor.
+ * @return true if the Intent contains a parcelled file descriptor.
+ */
+ public boolean hasFileDescriptors() {
+ return mExtras != null && mExtras.hasFileDescriptors();
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void setAllowFds(boolean allowFds) {
+ if (mExtras != null) {
+ mExtras.setAllowFds(allowFds);
+ }
+ }
+
+ /** {@hide} */
+ public void setDefusable(boolean defusable) {
+ if (mExtras != null) {
+ mExtras.setDefusable(defusable);
+ }
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if none was found.
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public Object getExtra(String name) {
+ return getExtra(name, null);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, boolean)
+ */
+ public boolean getBooleanExtra(String name, boolean defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getBoolean(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, byte)
+ */
+ public byte getByteExtra(String name, byte defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getByte(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, short)
+ */
+ public short getShortExtra(String name, short defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getShort(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, char)
+ */
+ public char getCharExtra(String name, char defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getChar(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, int)
+ */
+ public int getIntExtra(String name, int defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getInt(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, long)
+ */
+ public long getLongExtra(String name, long defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getLong(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if no such item is present
+ *
+ * @see #putExtra(String, float)
+ */
+ public float getFloatExtra(String name, float defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getFloat(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @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 previously added with putExtra(),
+ * or the default value if none was found.
+ *
+ * @see #putExtra(String, double)
+ */
+ public double getDoubleExtra(String name, double defaultValue) {
+ return mExtras == null ? defaultValue :
+ mExtras.getDouble(name, defaultValue);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no String value was found.
+ *
+ * @see #putExtra(String, String)
+ */
+ public @Nullable String getStringExtra(String name) {
+ return mExtras == null ? null : mExtras.getString(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no CharSequence value was found.
+ *
+ * @see #putExtra(String, CharSequence)
+ */
+ public @Nullable CharSequence getCharSequenceExtra(String name) {
+ return mExtras == null ? null : mExtras.getCharSequence(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no Parcelable value was found.
+ *
+ * @see #putExtra(String, Parcelable)
+ */
+ public @Nullable <T extends Parcelable> T getParcelableExtra(String name) {
+ return mExtras == null ? null : mExtras.<T>getParcelable(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no Parcelable[] value was found.
+ *
+ * @see #putExtra(String, Parcelable[])
+ */
+ public @Nullable Parcelable[] getParcelableArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getParcelableArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with
+ * putParcelableArrayListExtra(), or null if no
+ * ArrayList<Parcelable> value was found.
+ *
+ * @see #putParcelableArrayListExtra(String, ArrayList)
+ */
+ public @Nullable <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
+ return mExtras == null ? null : mExtras.<T>getParcelableArrayList(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no Serializable value was found.
+ *
+ * @see #putExtra(String, Serializable)
+ */
+ public @Nullable Serializable getSerializableExtra(String name) {
+ return mExtras == null ? null : mExtras.getSerializable(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with
+ * putIntegerArrayListExtra(), or null if no
+ * ArrayList<Integer> value was found.
+ *
+ * @see #putIntegerArrayListExtra(String, ArrayList)
+ */
+ public @Nullable ArrayList<Integer> getIntegerArrayListExtra(String name) {
+ return mExtras == null ? null : mExtras.getIntegerArrayList(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with
+ * putStringArrayListExtra(), or null if no
+ * ArrayList<String> value was found.
+ *
+ * @see #putStringArrayListExtra(String, ArrayList)
+ */
+ public @Nullable ArrayList<String> getStringArrayListExtra(String name) {
+ return mExtras == null ? null : mExtras.getStringArrayList(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with
+ * putCharSequenceArrayListExtra, or null if no
+ * ArrayList<CharSequence> value was found.
+ *
+ * @see #putCharSequenceArrayListExtra(String, ArrayList)
+ */
+ public @Nullable ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
+ return mExtras == null ? null : mExtras.getCharSequenceArrayList(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no boolean array value was found.
+ *
+ * @see #putExtra(String, boolean[])
+ */
+ public @Nullable boolean[] getBooleanArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getBooleanArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no byte array value was found.
+ *
+ * @see #putExtra(String, byte[])
+ */
+ public @Nullable byte[] getByteArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getByteArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no short array value was found.
+ *
+ * @see #putExtra(String, short[])
+ */
+ public @Nullable short[] getShortArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getShortArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no char array value was found.
+ *
+ * @see #putExtra(String, char[])
+ */
+ public @Nullable char[] getCharArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getCharArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no int array value was found.
+ *
+ * @see #putExtra(String, int[])
+ */
+ public @Nullable int[] getIntArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getIntArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no long array value was found.
+ *
+ * @see #putExtra(String, long[])
+ */
+ public @Nullable long[] getLongArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getLongArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no float array value was found.
+ *
+ * @see #putExtra(String, float[])
+ */
+ public @Nullable float[] getFloatArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getFloatArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no double array value was found.
+ *
+ * @see #putExtra(String, double[])
+ */
+ public @Nullable double[] getDoubleArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getDoubleArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no String array value was found.
+ *
+ * @see #putExtra(String, String[])
+ */
+ public @Nullable String[] getStringArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getStringArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no CharSequence array value was found.
+ *
+ * @see #putExtra(String, CharSequence[])
+ */
+ public @Nullable CharSequence[] getCharSequenceArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getCharSequenceArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no Bundle value was found.
+ *
+ * @see #putExtra(String, Bundle)
+ */
+ public @Nullable Bundle getBundleExtra(String name) {
+ return mExtras == null ? null : mExtras.getBundle(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no IBinder value was found.
+ *
+ * @see #putExtra(String, IBinder)
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public IBinder getIBinderExtra(String name) {
+ return mExtras == null ? null : mExtras.getIBinder(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ * @param defaultValue The default value to return in case no item is
+ * associated with the key 'name'
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or defaultValue if none was found.
+ *
+ * @see #putExtra
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public Object getExtra(String name, Object defaultValue) {
+ Object result = defaultValue;
+ if (mExtras != null) {
+ Object result2 = mExtras.get(name);
+ if (result2 != null) {
+ result = result2;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Retrieves a map of extended data from the intent.
+ *
+ * @return the map of all extras previously added with putExtra(),
+ * or null if none have been added.
+ */
+ public @Nullable Bundle getExtras() {
+ return (mExtras != null)
+ ? new Bundle(mExtras)
+ : null;
+ }
+
+ /**
+ * Filter extras to only basic types.
+ * @hide
+ */
+ public void removeUnsafeExtras() {
+ if (mExtras != null) {
+ mExtras = mExtras.filterValues();
+ }
+ }
+
+ /**
+ * @return Whether {@link #maybeStripForHistory} will return an lightened intent or
+ * return itself as-is.
+ * @hide
+ */
+ public boolean canStripForHistory() {
+ return ((mExtras != null) && mExtras.isParcelled()) || (mClipData != null);
+ }
+
+ /**
+ * Call it when the system needs to keep an intent for logging purposes to remove fields
+ * that are not needed for logging.
+ * @hide
+ */
+ public Intent maybeStripForHistory() {
+ // TODO Scan and remove possibly heavy instances like Bitmaps from unparcelled extras?
+
+ if (!canStripForHistory()) {
+ return this;
+ }
+ return new Intent(this, COPY_MODE_HISTORY);
+ }
+
+ /**
+ * Retrieve any special flags associated with this intent. You will
+ * normally just set them with {@link #setFlags} and let the system
+ * take the appropriate action with them.
+ *
+ * @return The currently set flags.
+ * @see #setFlags
+ * @see #addFlags
+ * @see #removeFlags
+ */
+ public @Flags int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public boolean isExcludingStopped() {
+ return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES))
+ == FLAG_EXCLUDE_STOPPED_PACKAGES;
+ }
+
+ /**
+ * Retrieve the application package name this Intent is limited to. When
+ * resolving an Intent, if non-null this limits the resolution to only
+ * components in the given application package.
+ *
+ * @return The name of the application package for the Intent.
+ *
+ * @see #resolveActivity
+ * @see #setPackage
+ */
+ public @Nullable String getPackage() {
+ return mPackage;
+ }
+
+ /**
+ * Retrieve the concrete component associated with the intent. When receiving
+ * an intent, this is the component that was found to best handle it (that is,
+ * yourself) and will always be non-null; in all other cases it will be
+ * null unless explicitly set.
+ *
+ * @return The name of the application component to handle the intent.
+ *
+ * @see #resolveActivity
+ * @see #setComponent
+ */
+ public @Nullable ComponentName getComponent() {
+ return mComponent;
+ }
+
+ /**
+ * Get the bounds of the sender of this intent, in screen coordinates. This can be
+ * used as a hint to the receiver for animations and the like. Null means that there
+ * is no source bounds.
+ */
+ public @Nullable Rect getSourceBounds() {
+ return mSourceBounds;
+ }
+
+ /**
+ * Return the Activity component that should be used to handle this intent.
+ * The appropriate component is determined based on the information in the
+ * intent, evaluated as follows:
+ *
+ * <p>If {@link #getComponent} returns an explicit class, that is returned
+ * without any further consideration.
+ *
+ * <p>The activity must handle the {@link Intent#CATEGORY_DEFAULT} Intent
+ * category to be considered.
+ *
+ * <p>If {@link #getAction} is non-NULL, the activity must handle this
+ * action.
+ *
+ * <p>If {@link #resolveType} returns non-NULL, the activity must handle
+ * this type.
+ *
+ * <p>If {@link #addCategory} has added any categories, the activity must
+ * handle ALL of the categories specified.
+ *
+ * <p>If {@link #getPackage} is non-NULL, only activity components in
+ * that application package will be considered.
+ *
+ * <p>If there are no activities that satisfy all of these conditions, a
+ * null string is returned.
+ *
+ * <p>If multiple activities are found to satisfy the intent, the one with
+ * the highest priority will be used. If there are multiple activities
+ * with the same priority, the system will either pick the best activity
+ * based on user preference, or resolve to a system class that will allow
+ * the user to pick an activity and forward from there.
+ *
+ * <p>This method is implemented simply by calling
+ * {@link PackageManager#resolveActivity} with the "defaultOnly" parameter
+ * true.</p>
+ * <p> This API is called for you as part of starting an activity from an
+ * intent. You do not normally need to call it yourself.</p>
+ *
+ * @param pm The package manager with which to resolve the Intent.
+ *
+ * @return Name of the component implementing an activity that can
+ * display the intent.
+ *
+ * @see #setComponent
+ * @see #getComponent
+ * @see #resolveActivityInfo
+ */
+ public ComponentName resolveActivity(@NonNull PackageManager pm) {
+ if (mComponent != null) {
+ return mComponent;
+ }
+
+ ResolveInfo info = pm.resolveActivity(
+ this, PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null) {
+ return new ComponentName(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name);
+ }
+
+ return null;
+ }
+
+ /**
+ * Resolve the Intent into an {@link ActivityInfo}
+ * describing the activity that should execute the intent. Resolution
+ * follows the same rules as described for {@link #resolveActivity}, but
+ * you get back the completely information about the resolved activity
+ * instead of just its class name.
+ *
+ * @param pm The package manager with which to resolve the Intent.
+ * @param flags Addition information to retrieve as per
+ * {@link PackageManager#getActivityInfo(ComponentName, int)
+ * PackageManager.getActivityInfo()}.
+ *
+ * @return PackageManager.ActivityInfo
+ *
+ * @see #resolveActivity
+ */
+ public ActivityInfo resolveActivityInfo(@NonNull PackageManager pm,
+ @PackageManager.ComponentInfoFlags int flags) {
+ ActivityInfo ai = null;
+ if (mComponent != null) {
+ try {
+ ai = pm.getActivityInfo(mComponent, flags);
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignore
+ }
+ } else {
+ ResolveInfo info = pm.resolveActivity(
+ this, PackageManager.MATCH_DEFAULT_ONLY | flags);
+ if (info != null) {
+ ai = info.activityInfo;
+ }
+ }
+
+ return ai;
+ }
+
+ /**
+ * Special function for use by the system to resolve service
+ * intents to system apps. Throws an exception if there are
+ * multiple potential matches to the Intent. Returns null if
+ * there are no matches.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
+ @PackageManager.ComponentInfoFlags int flags) {
+ if (mComponent != null) {
+ return mComponent;
+ }
+
+ List<ResolveInfo> results = pm.queryIntentServices(this, flags);
+ if (results == null) {
+ return null;
+ }
+ ComponentName comp = null;
+ for (int i=0; i<results.size(); i++) {
+ ResolveInfo ri = results.get(i);
+ if ((ri.serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+ continue;
+ }
+ ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
+ ri.serviceInfo.name);
+ if (comp != null) {
+ throw new IllegalStateException("Multiple system services handle " + this
+ + ": " + comp + ", " + foundComp);
+ }
+ comp = foundComp;
+ }
+ return comp;
+ }
+
+ /**
+ * Set the general action to be performed.
+ *
+ * @param action An action name, such as ACTION_VIEW. Application-specific
+ * actions should be prefixed with the vendor's package name.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getAction
+ */
+ public @NonNull Intent setAction(@Nullable String action) {
+ mAction = action != null ? action.intern() : null;
+ return this;
+ }
+
+ /**
+ * Set the data this intent is operating on. This method automatically
+ * clears any type that was previously set by {@link #setType} or
+ * {@link #setTypeAndNormalize}.
+ *
+ * <p><em>Note: scheme matching in the Android framework is
+ * case-sensitive, unlike the formal RFC. As a result,
+ * you should always write your Uri with a lower case scheme,
+ * or use {@link Uri#normalizeScheme} or
+ * {@link #setDataAndNormalize}
+ * to ensure that the scheme is converted to lower case.</em>
+ *
+ * @param data The Uri of the data this intent is now targeting.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getData
+ * @see #setDataAndNormalize
+ * @see android.net.Uri#normalizeScheme()
+ */
+ public @NonNull Intent setData(@Nullable Uri data) {
+ mData = data;
+ mType = null;
+ return this;
+ }
+
+ /**
+ * Normalize and set the data this intent is operating on.
+ *
+ * <p>This method automatically clears any type that was
+ * previously set (for example, by {@link #setType}).
+ *
+ * <p>The data Uri is normalized using
+ * {@link android.net.Uri#normalizeScheme} before it is set,
+ * so really this is just a convenience method for
+ * <pre>
+ * setData(data.normalize())
+ * </pre>
+ *
+ * @param data The Uri of the data this intent is now targeting.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getData
+ * @see #setType
+ * @see android.net.Uri#normalizeScheme
+ */
+ public @NonNull Intent setDataAndNormalize(@NonNull Uri data) {
+ return setData(data.normalizeScheme());
+ }
+
+ /**
+ * Set an explicit MIME data type.
+ *
+ * <p>This is used to create intents that only specify a type and not data,
+ * for example to indicate the type of data to return.
+ *
+ * <p>This method automatically clears any data that was
+ * previously set (for example by {@link #setData}).
+ *
+ * <p><em>Note: MIME type matching in the Android framework is
+ * case-sensitive, unlike formal RFC MIME types. As a result,
+ * you should always write your MIME types with lower case letters,
+ * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize}
+ * to ensure that it is converted to lower case.</em>
+ *
+ * @param type The MIME type of the data being handled by this intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getType
+ * @see #setTypeAndNormalize
+ * @see #setDataAndType
+ * @see #normalizeMimeType
+ */
+ public @NonNull Intent setType(@Nullable String type) {
+ mData = null;
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Normalize and set an explicit MIME data type.
+ *
+ * <p>This is used to create intents that only specify a type and not data,
+ * for example to indicate the type of data to return.
+ *
+ * <p>This method automatically clears any data that was
+ * previously set (for example by {@link #setData}).
+ *
+ * <p>The MIME type is normalized using
+ * {@link #normalizeMimeType} before it is set,
+ * so really this is just a convenience method for
+ * <pre>
+ * setType(Intent.normalizeMimeType(type))
+ * </pre>
+ *
+ * @param type The MIME type of the data being handled by this intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getType
+ * @see #setData
+ * @see #normalizeMimeType
+ */
+ public @NonNull Intent setTypeAndNormalize(@Nullable String type) {
+ return setType(normalizeMimeType(type));
+ }
+
+ /**
+ * (Usually optional) Set the data for the intent along with an explicit
+ * MIME data type. This method should very rarely be used -- it allows you
+ * to override the MIME type that would ordinarily be inferred from the
+ * data with your own type given here.
+ *
+ * <p><em>Note: MIME type and Uri scheme matching in the
+ * Android framework is case-sensitive, unlike the formal RFC definitions.
+ * As a result, you should always write these elements with lower case letters,
+ * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalizeScheme} or
+ * {@link #setDataAndTypeAndNormalize}
+ * to ensure that they are converted to lower case.</em>
+ *
+ * @param data The Uri of the data this intent is now targeting.
+ * @param type The MIME type of the data being handled by this intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setType
+ * @see #setData
+ * @see #normalizeMimeType
+ * @see android.net.Uri#normalizeScheme
+ * @see #setDataAndTypeAndNormalize
+ */
+ public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
+ mData = data;
+ mType = type;
+ return this;
+ }
+
+ /**
+ * (Usually optional) Normalize and set both the data Uri and an explicit
+ * MIME data type. This method should very rarely be used -- it allows you
+ * to override the MIME type that would ordinarily be inferred from the
+ * data with your own type given here.
+ *
+ * <p>The data Uri and the MIME type are normalize using
+ * {@link android.net.Uri#normalizeScheme} and {@link #normalizeMimeType}
+ * before they are set, so really this is just a convenience method for
+ * <pre>
+ * setDataAndType(data.normalize(), Intent.normalizeMimeType(type))
+ * </pre>
+ *
+ * @param data The Uri of the data this intent is now targeting.
+ * @param type The MIME type of the data being handled by this intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setType
+ * @see #setData
+ * @see #setDataAndType
+ * @see #normalizeMimeType
+ * @see android.net.Uri#normalizeScheme
+ */
+ public @NonNull Intent setDataAndTypeAndNormalize(@NonNull Uri data, @Nullable String type) {
+ return setDataAndType(data.normalizeScheme(), normalizeMimeType(type));
+ }
+
+ /**
+ * Set an identifier for this Intent. If set, this provides a unique identity for this Intent,
+ * allowing it to be unique from other Intents that would otherwise look the same. In
+ * particular, this will be used by {@link #filterEquals(Intent)} to determine if two
+ * Intents are the same as with other fields like {@link #setAction}. However, unlike those
+ * fields, the identifier is <em>never</em> used for matching against an {@link IntentFilter};
+ * it is as if the identifier has not been set on the Intent.
+ *
+ * <p>This can be used, for example, to make this Intent unique from other Intents that
+ * are otherwise the same, for use in creating a {@link android.app.PendingIntent}. (Be aware
+ * however that the receiver of the PendingIntent will see whatever you put in here.) The
+ * structure of this string is completely undefined by the platform, however if you are going
+ * to be exposing identifier strings across different applications you may need to define
+ * your own structure if there is no central party defining the contents of this field.</p>
+ *
+ * @param identifier The identifier for this Intent. The contents of the string have no
+ * meaning to the system, except whether they are exactly the same as
+ * another identifier.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getIdentifier
+ */
+ public @NonNull Intent setIdentifier(@Nullable String identifier) {
+ mIdentifier = identifier;
+ return this;
+ }
+
+ /**
+ * Add a new category to the intent. Categories provide additional detail
+ * about the action the intent performs. When resolving an intent, only
+ * activities that provide <em>all</em> of the requested categories will be
+ * used.
+ *
+ * @param category The desired category. This can be either one of the
+ * predefined Intent categories, or a custom category in your own
+ * namespace.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #hasCategory
+ * @see #removeCategory
+ */
+ public @NonNull Intent addCategory(String category) {
+ if (mCategories == null) {
+ mCategories = new ArraySet<String>();
+ }
+ mCategories.add(category.intern());
+ return this;
+ }
+
+ /**
+ * Remove a category from an intent.
+ *
+ * @param category The category to remove.
+ *
+ * @see #addCategory
+ */
+ public void removeCategory(String category) {
+ if (mCategories != null) {
+ mCategories.remove(category);
+ if (mCategories.size() == 0) {
+ mCategories = null;
+ }
+ }
+ }
+
+ /**
+ * Set a selector for this Intent. This is a modification to the kinds of
+ * things the Intent will match. If the selector is set, it will be used
+ * when trying to find entities that can handle the Intent, instead of the
+ * main contents of the Intent. This allows you build an Intent containing
+ * a generic protocol while targeting it more specifically.
+ *
+ * <p>An example of where this may be used is with things like
+ * {@link #CATEGORY_APP_BROWSER}. This category allows you to build an
+ * Intent that will launch the Browser application. However, the correct
+ * main entry point of an application is actually {@link #ACTION_MAIN}
+ * {@link #CATEGORY_LAUNCHER} with {@link #setComponent(ComponentName)}
+ * used to specify the actual Activity to launch. If you launch the browser
+ * with something different, undesired behavior may happen if the user has
+ * previously or later launches it the normal way, since they do not match.
+ * Instead, you can build an Intent with the MAIN action (but no ComponentName
+ * yet specified) and set a selector with {@link #ACTION_MAIN} and
+ * {@link #CATEGORY_APP_BROWSER} to point it specifically to the browser activity.
+ *
+ * <p>Setting a selector does not impact the behavior of
+ * {@link #filterEquals(Intent)} and {@link #filterHashCode()}. This is part of the
+ * desired behavior of a selector -- it does not impact the base meaning
+ * of the Intent, just what kinds of things will be matched against it
+ * when determining who can handle it.</p>
+ *
+ * <p>You can not use both a selector and {@link #setPackage(String)} on
+ * the same base Intent.</p>
+ *
+ * @param selector The desired selector Intent; set to null to not use
+ * a special selector.
+ */
+ public void setSelector(@Nullable Intent selector) {
+ if (selector == this) {
+ throw new IllegalArgumentException(
+ "Intent being set as a selector of itself");
+ }
+ if (selector != null && mPackage != null) {
+ throw new IllegalArgumentException(
+ "Can't set selector when package name is already set");
+ }
+ mSelector = selector;
+ }
+
+ /**
+ * Set a {@link ClipData} associated with this Intent. This replaces any
+ * previously set ClipData.
+ *
+ * <p>The ClipData in an intent is not used for Intent matching or other
+ * such operations. Semantically it is like extras, used to transmit
+ * additional data with the Intent. The main feature of using this over
+ * the extras for data is that {@link #FLAG_GRANT_READ_URI_PERMISSION}
+ * and {@link #FLAG_GRANT_WRITE_URI_PERMISSION} will operate on any URI
+ * items included in the clip data. This is useful, in particular, if
+ * you want to transmit an Intent containing multiple <code>content:</code>
+ * URIs for which the recipient may not have global permission to access the
+ * content provider.
+ *
+ * <p>If the ClipData contains items that are themselves Intents, any
+ * grant flags in those Intents will be ignored. Only the top-level flags
+ * of the main Intent are respected, and will be applied to all Uri or
+ * Intent items in the clip (or sub-items of the clip).
+ *
+ * <p>The MIME type, label, and icon in the ClipData object are not
+ * directly used by Intent. Applications should generally rely on the
+ * MIME type of the Intent itself, not what it may find in the ClipData.
+ * A common practice is to construct a ClipData for use with an Intent
+ * with a MIME type of "*/*".
+ *
+ * @param clip The new clip to set. May be null to clear the current clip.
+ */
+ public void setClipData(@Nullable ClipData clip) {
+ mClipData = clip;
+ }
+
+ /**
+ * This is NOT a secure mechanism to identify the user who sent the intent.
+ * When the intent is sent to a different user, it is used to fix uris by adding the userId
+ * who sent the intent.
+ * @hide
+ */
+ public void prepareToLeaveUser(int userId) {
+ // If mContentUserHint is not UserHandle.USER_CURRENT, the intent has already left a user.
+ // We want mContentUserHint to refer to the original user, so don't do anything.
+ if (mContentUserHint == UserHandle.USER_CURRENT) {
+ mContentUserHint = userId;
+ }
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The boolean data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getBooleanExtra(String, boolean)
+ */
+ public @NonNull Intent putExtra(String name, boolean value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putBoolean(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The byte data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getByteExtra(String, byte)
+ */
+ public @NonNull Intent putExtra(String name, byte value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putByte(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The char data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharExtra(String, char)
+ */
+ public @NonNull Intent putExtra(String name, char value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putChar(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The short data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getShortExtra(String, short)
+ */
+ public @NonNull Intent putExtra(String name, short value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putShort(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The integer data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getIntExtra(String, int)
+ */
+ public @NonNull Intent putExtra(String name, int value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putInt(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The long data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getLongExtra(String, long)
+ */
+ public @NonNull Intent putExtra(String name, long value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putLong(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The float data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getFloatExtra(String, float)
+ */
+ public @NonNull Intent putExtra(String name, float value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putFloat(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The double data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getDoubleExtra(String, double)
+ */
+ public @NonNull Intent putExtra(String name, double value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putDouble(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The String data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getStringExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable String value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putString(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The CharSequence data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharSequenceExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable CharSequence value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putCharSequence(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The Parcelable data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getParcelableExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable Parcelable value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putParcelable(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The Parcelable[] data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getParcelableArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable Parcelable[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putParcelableArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The ArrayList<Parcelable> data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getParcelableArrayListExtra(String)
+ */
+ public @NonNull Intent putParcelableArrayListExtra(String name,
+ @Nullable ArrayList<? extends Parcelable> value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putParcelableArrayList(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The ArrayList<Integer> data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getIntegerArrayListExtra(String)
+ */
+ public @NonNull Intent putIntegerArrayListExtra(String name,
+ @Nullable ArrayList<Integer> value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putIntegerArrayList(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The ArrayList<String> data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getStringArrayListExtra(String)
+ */
+ public @NonNull Intent putStringArrayListExtra(String name, @Nullable ArrayList<String> value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putStringArrayList(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The ArrayList<CharSequence> data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharSequenceArrayListExtra(String)
+ */
+ public @NonNull Intent putCharSequenceArrayListExtra(String name,
+ @Nullable ArrayList<CharSequence> value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putCharSequenceArrayList(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The Serializable data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getSerializableExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable Serializable value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putSerializable(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The boolean array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getBooleanArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable boolean[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putBooleanArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The byte array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getByteArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable byte[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putByteArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The short array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getShortArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable short[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putShortArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The char array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable char[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putCharArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The int array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getIntArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable int[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putIntArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The byte array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getLongArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable long[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putLongArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The float array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getFloatArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable float[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putFloatArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The double array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getDoubleArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable double[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putDoubleArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The String array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getStringArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable String[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putStringArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The CharSequence array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharSequenceArrayExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable CharSequence[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putCharSequenceArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The Bundle data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getBundleExtra(String)
+ */
+ public @NonNull Intent putExtra(String name, @Nullable Bundle value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putBundle(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
+ * @param value The IBinder data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getIBinderExtra(String)
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public @NonNull Intent putExtra(String name, IBinder value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putIBinder(name, value);
+ return this;
+ }
+
+ /**
+ * Copy all extras in 'src' in to this intent.
+ *
+ * @param src Contains the extras to copy.
+ *
+ * @see #putExtra
+ */
+ public @NonNull Intent putExtras(@NonNull Intent src) {
+ if (src.mExtras != null) {
+ if (mExtras == null) {
+ mExtras = new Bundle(src.mExtras);
+ } else {
+ mExtras.putAll(src.mExtras);
+ }
+ }
+ // If the provided Intent was unparceled and this is not an Intent delivered to a protected
+ // component then mark the extras as unfiltered. An Intent delivered to a protected
+ // component had to come from a trusted component, and if unfiltered data was copied to the
+ // delivered Intent then it would have been reported when that Intent left the sending
+ // process.
+ if ((src.mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0
+ && (src.mLocalFlags & LOCAL_FLAG_FROM_PROTECTED_COMPONENT) == 0) {
+ mLocalFlags |= LOCAL_FLAG_UNFILTERED_EXTRAS;
+ }
+ return this;
+ }
+
+ /**
+ * Add a set of extended data to the intent. The keys must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param extras The Bundle of extras to add to this intent.
+ *
+ * @see #putExtra
+ * @see #removeExtra
+ */
+ public @NonNull Intent putExtras(@NonNull Bundle extras) {
+ // If the provided Bundle has not yet been unparceled then treat this as unfiltered extras.
+ if (extras.isParcelled()) {
+ mLocalFlags |= LOCAL_FLAG_UNFILTERED_EXTRAS;
+ }
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putAll(extras);
+ return this;
+ }
+
+ /**
+ * Completely replace the extras in the Intent with the extras in the
+ * given Intent.
+ *
+ * @param src The exact extras contained in this Intent are copied
+ * into the target intent, replacing any that were previously there.
+ */
+ public @NonNull Intent replaceExtras(@NonNull Intent src) {
+ mExtras = src.mExtras != null ? new Bundle(src.mExtras) : null;
+ return this;
+ }
+
+ /**
+ * Completely replace the extras in the Intent with the given Bundle of
+ * extras.
+ *
+ * @param extras The new set of extras in the Intent, or null to erase
+ * all extras.
+ */
+ public @NonNull Intent replaceExtras(@Nullable Bundle extras) {
+ mExtras = extras != null ? new Bundle(extras) : null;
+ return this;
+ }
+
+ /**
+ * Remove extended data from the intent.
+ *
+ * @see #putExtra
+ */
+ public void removeExtra(String name) {
+ if (mExtras != null) {
+ mExtras.remove(name);
+ if (mExtras.size() == 0) {
+ mExtras = null;
+ }
+ }
+ }
+
+ /**
+ * Set special flags controlling how this intent is handled. Most values
+ * here depend on the type of component being executed by the Intent,
+ * specifically the FLAG_ACTIVITY_* flags are all for use with
+ * {@link Context#startActivity Context.startActivity()} and the
+ * FLAG_RECEIVER_* flags are all for use with
+ * {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
+ *
+ * <p>See the
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+ * Stack</a> documentation for important information on how some of these options impact
+ * the behavior of your application.
+ *
+ * @param flags The desired flags.
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ * @see #getFlags
+ * @see #addFlags
+ * @see #removeFlags
+ */
+ public @NonNull Intent setFlags(@Flags int flags) {
+ mFlags = flags;
+ return this;
+ }
+
+ /**
+ * Add additional flags to the intent (or with existing flags value).
+ *
+ * @param flags The new flags to set.
+ * @return Returns the same Intent object, for chaining multiple calls into
+ * a single statement.
+ * @see #setFlags
+ * @see #getFlags
+ * @see #removeFlags
+ */
+ public @NonNull Intent addFlags(@Flags int flags) {
+ mFlags |= flags;
+ return this;
+ }
+
+ /**
+ * Remove these flags from the intent.
+ *
+ * @param flags The flags to remove.
+ * @see #setFlags
+ * @see #getFlags
+ * @see #addFlags
+ */
+ public void removeFlags(@Flags int flags) {
+ mFlags &= ~flags;
+ }
+
+ /**
+ * (Usually optional) Set an explicit application package name that limits
+ * the components this Intent will resolve to. If left to the default
+ * value of null, all components in all applications will considered.
+ * If non-null, the Intent can only match the components in the given
+ * application package.
+ *
+ * @param packageName The name of the application package to handle the
+ * intent, or null to allow any application package.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getPackage
+ * @see #resolveActivity
+ */
+ public @NonNull Intent setPackage(@Nullable String packageName) {
+ if (packageName != null && mSelector != null) {
+ throw new IllegalArgumentException(
+ "Can't set package name when selector is already set");
+ }
+ mPackage = packageName;
+ return this;
+ }
+
+ /**
+ * (Usually optional) Explicitly set the component to handle the intent.
+ * If left with the default value of null, the system will determine the
+ * appropriate class to use based on the other fields (action, data,
+ * type, categories) in the Intent. If this class is defined, the
+ * specified class will always be used regardless of the other fields. You
+ * should only set this value when you know you absolutely want a specific
+ * class to be used; otherwise it is better to let the system find the
+ * appropriate class so that you will respect the installed applications
+ * and user preferences.
+ *
+ * @param component The name of the application component to handle the
+ * intent, or null to let the system find one for you.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setClass
+ * @see #setClassName(Context, String)
+ * @see #setClassName(String, String)
+ * @see #getComponent
+ * @see #resolveActivity
+ */
+ public @NonNull Intent setComponent(@Nullable ComponentName component) {
+ mComponent = component;
+ return this;
+ }
+
+ /**
+ * Convenience for calling {@link #setComponent} with an
+ * explicit class name.
+ *
+ * @param packageContext A Context of the application package implementing
+ * this class.
+ * @param className The name of a class inside of the application package
+ * that will be used as the component for this Intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setComponent
+ * @see #setClass
+ */
+ public @NonNull Intent setClassName(@NonNull Context packageContext,
+ @NonNull String className) {
+ mComponent = new ComponentName(packageContext, className);
+ return this;
+ }
+
+ /**
+ * Convenience for calling {@link #setComponent} with an
+ * explicit application package name and class name.
+ *
+ * @param packageName The name of the package implementing the desired
+ * component.
+ * @param className The name of a class inside of the application package
+ * that will be used as the component for this Intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setComponent
+ * @see #setClass
+ */
+ public @NonNull Intent setClassName(@NonNull String packageName, @NonNull String className) {
+ mComponent = new ComponentName(packageName, className);
+ return this;
+ }
+
+ /**
+ * Convenience for calling {@link #setComponent(ComponentName)} with the
+ * name returned by a {@link Class} object.
+ *
+ * @param packageContext A Context of the application package implementing
+ * this class.
+ * @param cls The class name to set, equivalent to
+ * <code>setClassName(context, cls.getName())</code>.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setComponent
+ */
+ public @NonNull Intent setClass(@NonNull Context packageContext, @NonNull Class<?> cls) {
+ mComponent = new ComponentName(packageContext, cls);
+ return this;
+ }
+
+ /**
+ * Set the bounds of the sender of this intent, in screen coordinates. This can be
+ * used as a hint to the receiver for animations and the like. Null means that there
+ * is no source bounds.
+ */
+ public void setSourceBounds(@Nullable Rect r) {
+ if (r != null) {
+ mSourceBounds = new Rect(r);
+ } else {
+ mSourceBounds = null;
+ }
+ }
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FILL_IN_" }, value = {
+ FILL_IN_ACTION,
+ FILL_IN_DATA,
+ FILL_IN_CATEGORIES,
+ FILL_IN_COMPONENT,
+ FILL_IN_PACKAGE,
+ FILL_IN_SOURCE_BOUNDS,
+ FILL_IN_SELECTOR,
+ FILL_IN_CLIP_DATA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FillInFlags {}
+
+ /**
+ * Use with {@link #fillIn} to allow the current action value to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_ACTION = 1<<0;
+
+ /**
+ * Use with {@link #fillIn} to allow the current data or type value
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_DATA = 1<<1;
+
+ /**
+ * Use with {@link #fillIn} to allow the current categories to be
+ * overwritten, even if they are already set.
+ */
+ public static final int FILL_IN_CATEGORIES = 1<<2;
+
+ /**
+ * Use with {@link #fillIn} to allow the current component value to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_COMPONENT = 1<<3;
+
+ /**
+ * Use with {@link #fillIn} to allow the current package value to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_PACKAGE = 1<<4;
+
+ /**
+ * Use with {@link #fillIn} to allow the current bounds rectangle to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_SOURCE_BOUNDS = 1<<5;
+
+ /**
+ * Use with {@link #fillIn} to allow the current selector to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_SELECTOR = 1<<6;
+
+ /**
+ * Use with {@link #fillIn} to allow the current ClipData to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_CLIP_DATA = 1<<7;
+
+ /**
+ * Use with {@link #fillIn} to allow the current identifier value to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_IDENTIFIER = 1<<8;
+
+ /**
+ * Copy the contents of <var>other</var> in to this object, but only
+ * where fields are not defined by this object. For purposes of a field
+ * being defined, the following pieces of data in the Intent are
+ * considered to be separate fields:
+ *
+ * <ul>
+ * <li> action, as set by {@link #setAction}.
+ * <li> data Uri and MIME type, as set by {@link #setData(Uri)},
+ * {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}.
+ * <li> identifier, as set by {@link #setIdentifier}.
+ * <li> categories, as set by {@link #addCategory}.
+ * <li> package, as set by {@link #setPackage}.
+ * <li> component, as set by {@link #setComponent(ComponentName)} or
+ * related methods.
+ * <li> source bounds, as set by {@link #setSourceBounds}.
+ * <li> selector, as set by {@link #setSelector(Intent)}.
+ * <li> clip data, as set by {@link #setClipData(ClipData)}.
+ * <li> each top-level name in the associated extras.
+ * </ul>
+ *
+ * <p>In addition, you can use the {@link #FILL_IN_ACTION},
+ * {@link #FILL_IN_DATA}, {@link #FILL_IN_IDENTIFIER}, {@link #FILL_IN_CATEGORIES},
+ * {@link #FILL_IN_PACKAGE}, {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS},
+ * {@link #FILL_IN_SELECTOR}, and {@link #FILL_IN_CLIP_DATA} to override
+ * the restriction where the corresponding field will not be replaced if
+ * it is already set.
+ *
+ * <p>Note: The component field will only be copied if {@link #FILL_IN_COMPONENT}
+ * is explicitly specified. The selector will only be copied if
+ * {@link #FILL_IN_SELECTOR} is explicitly specified.
+ *
+ * <p>For example, consider Intent A with {data="foo", categories="bar"}
+ * and Intent B with {action="gotit", data-type="some/thing",
+ * categories="one","two"}.
+ *
+ * <p>Calling A.fillIn(B, Intent.FILL_IN_DATA) will result in A now
+ * containing: {action="gotit", data-type="some/thing",
+ * categories="bar"}.
+ *
+ * @param other Another Intent whose values are to be used to fill in
+ * the current one.
+ * @param flags Options to control which fields can be filled in.
+ *
+ * @return Returns a bit mask of {@link #FILL_IN_ACTION},
+ * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+ * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS},
+ * {@link #FILL_IN_SELECTOR} and {@link #FILL_IN_CLIP_DATA} indicating which fields were
+ * changed.
+ */
+ @FillInFlags
+ public int fillIn(@NonNull Intent other, @FillInFlags int flags) {
+ int changes = 0;
+ boolean mayHaveCopiedUris = false;
+ if (other.mAction != null
+ && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
+ mAction = other.mAction;
+ changes |= FILL_IN_ACTION;
+ }
+ if ((other.mData != null || other.mType != null)
+ && ((mData == null && mType == null)
+ || (flags&FILL_IN_DATA) != 0)) {
+ mData = other.mData;
+ mType = other.mType;
+ changes |= FILL_IN_DATA;
+ mayHaveCopiedUris = true;
+ }
+ if (other.mIdentifier != null
+ && (mIdentifier == null || (flags&FILL_IN_IDENTIFIER) != 0)) {
+ mIdentifier = other.mIdentifier;
+ changes |= FILL_IN_IDENTIFIER;
+ }
+ if (other.mCategories != null
+ && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
+ if (other.mCategories != null) {
+ mCategories = new ArraySet<String>(other.mCategories);
+ }
+ changes |= FILL_IN_CATEGORIES;
+ }
+ if (other.mPackage != null
+ && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
+ // Only do this if mSelector is not set.
+ if (mSelector == null) {
+ mPackage = other.mPackage;
+ changes |= FILL_IN_PACKAGE;
+ }
+ }
+ // Selector is special: it can only be set if explicitly allowed,
+ // for the same reason as the component name.
+ if (other.mSelector != null && (flags&FILL_IN_SELECTOR) != 0) {
+ if (mPackage == null) {
+ mSelector = new Intent(other.mSelector);
+ mPackage = null;
+ changes |= FILL_IN_SELECTOR;
+ }
+ }
+ if (other.mClipData != null
+ && (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) {
+ mClipData = other.mClipData;
+ changes |= FILL_IN_CLIP_DATA;
+ mayHaveCopiedUris = true;
+ }
+ // Component is special: it can -only- be set if explicitly allowed,
+ // since otherwise the sender could force the intent somewhere the
+ // originator didn't intend.
+ if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
+ mComponent = other.mComponent;
+ changes |= FILL_IN_COMPONENT;
+ }
+ mFlags |= other.mFlags;
+ if (other.mSourceBounds != null
+ && (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
+ mSourceBounds = new Rect(other.mSourceBounds);
+ changes |= FILL_IN_SOURCE_BOUNDS;
+ }
+ if (mExtras == null) {
+ if (other.mExtras != null) {
+ mExtras = new Bundle(other.mExtras);
+ mayHaveCopiedUris = true;
+ }
+ } else if (other.mExtras != null) {
+ try {
+ Bundle newb = new Bundle(other.mExtras);
+ newb.putAll(mExtras);
+ mExtras = newb;
+ mayHaveCopiedUris = true;
+ } catch (RuntimeException e) {
+ // Modifying the extras can cause us to unparcel the contents
+ // of the bundle, and if we do this in the system process that
+ // may fail. We really should handle this (i.e., the Bundle
+ // impl shouldn't be on top of a plain map), but for now just
+ // ignore it and keep the original contents. :(
+ Log.w(TAG, "Failure filling in extras", e);
+ }
+ }
+ if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
+ && other.mContentUserHint != UserHandle.USER_CURRENT) {
+ mContentUserHint = other.mContentUserHint;
+ }
+ return changes;
+ }
+
+ /**
+ * Wrapper class holding an Intent and implementing comparisons on it for
+ * the purpose of filtering. The class implements its
+ * {@link #equals equals()} and {@link #hashCode hashCode()} methods as
+ * simple calls to {@link Intent#filterEquals(Intent)} filterEquals()} and
+ * {@link android.content.Intent#filterHashCode()} filterHashCode()}
+ * on the wrapped Intent.
+ */
+ public static final class FilterComparison {
+ private final Intent mIntent;
+ private final int mHashCode;
+
+ public FilterComparison(Intent intent) {
+ mIntent = intent;
+ mHashCode = intent.filterHashCode();
+ }
+
+ /**
+ * Return the Intent that this FilterComparison represents.
+ * @return Returns the Intent held by the FilterComparison. Do
+ * not modify!
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof FilterComparison) {
+ Intent other = ((FilterComparison)obj).mIntent;
+ return mIntent.filterEquals(other);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+ }
+
+ /**
+ * Determine if two intents are the same for the purposes of intent
+ * resolution (filtering). That is, if their action, data, type, identity,
+ * class, and categories are the same. This does <em>not</em> compare
+ * any extra data included in the intents. Note that technically when actually
+ * matching against an {@link IntentFilter} the identifier is ignored, while here
+ * it is directly compared for equality like the other fields.
+ *
+ * @param other The other Intent to compare against.
+ *
+ * @return Returns true if action, data, type, class, and categories
+ * are the same.
+ */
+ public boolean filterEquals(Intent other) {
+ if (other == null) {
+ return false;
+ }
+ if (!Objects.equals(this.mAction, other.mAction)) return false;
+ if (!Objects.equals(this.mData, other.mData)) return false;
+ if (!Objects.equals(this.mType, other.mType)) return false;
+ if (!Objects.equals(this.mIdentifier, other.mIdentifier)) return false;
+ if (!(this.hasPackageEquivalentComponent() && other.hasPackageEquivalentComponent())
+ && !Objects.equals(this.mPackage, other.mPackage)) {
+ return false;
+ }
+ if (!Objects.equals(this.mComponent, other.mComponent)) return false;
+ if (!Objects.equals(this.mCategories, other.mCategories)) return false;
+
+ return true;
+ }
+
+ /**
+ * Return {@code true} if the component name is not null and is in the same package that this
+ * intent limited to. otherwise return {@code false}.
+ */
+ private boolean hasPackageEquivalentComponent() {
+ return mComponent != null
+ && (mPackage == null || mPackage.equals(mComponent.getPackageName()));
+ }
+
+ /**
+ * Generate hash code that matches semantics of filterEquals().
+ *
+ * @return Returns the hash value of the action, data, type, class, and
+ * categories.
+ *
+ * @see #filterEquals
+ */
+ public int filterHashCode() {
+ int code = 0;
+ if (mAction != null) {
+ code += mAction.hashCode();
+ }
+ if (mData != null) {
+ code += mData.hashCode();
+ }
+ if (mType != null) {
+ code += mType.hashCode();
+ }
+ if (mIdentifier != null) {
+ code += mIdentifier.hashCode();
+ }
+ if (mPackage != null) {
+ code += mPackage.hashCode();
+ }
+ if (mComponent != null) {
+ code += mComponent.hashCode();
+ }
+ if (mCategories != null) {
+ code += mCategories.hashCode();
+ }
+ return code;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("Intent { ");
+ toShortString(b, true, true, true, false);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String toInsecureString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("Intent { ");
+ toShortString(b, false, true, true, false);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /** @hide */
+ public String toShortString(boolean secure, boolean comp, boolean extras, boolean clip) {
+ StringBuilder b = new StringBuilder(128);
+ toShortString(b, secure, comp, extras, clip);
+ return b.toString();
+ }
+
+ /** @hide */
+ public void toShortString(StringBuilder b, boolean secure, boolean comp, boolean extras,
+ boolean clip) {
+ boolean first = true;
+ if (mAction != null) {
+ b.append("act=").append(mAction);
+ first = false;
+ }
+ if (mCategories != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("cat=[");
+ for (int i=0; i<mCategories.size(); i++) {
+ if (i > 0) b.append(',');
+ b.append(mCategories.valueAt(i));
+ }
+ b.append("]");
+ }
+ if (mData != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("dat=");
+ if (secure) {
+ b.append(mData.toSafeString());
+ } else {
+ b.append(mData);
+ }
+ }
+ if (mType != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("typ=").append(mType);
+ }
+ if (mIdentifier != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("id=").append(mIdentifier);
+ }
+ if (mFlags != 0) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("flg=0x").append(Integer.toHexString(mFlags));
+ }
+ if (mPackage != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("pkg=").append(mPackage);
+ }
+ if (comp && mComponent != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("cmp=").append(mComponent.flattenToShortString());
+ }
+ if (mSourceBounds != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("bnds=").append(mSourceBounds.toShortString());
+ }
+ if (mClipData != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ b.append("clip={");
+ mClipData.toShortString(b, !clip || secure);
+ first = false;
+ b.append('}');
+ }
+ if (extras && mExtras != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("(has extras)");
+ }
+ if (mContentUserHint != UserHandle.USER_CURRENT) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("u=").append(mContentUserHint);
+ }
+ if (mSelector != null) {
+ b.append(" sel=");
+ mSelector.toShortString(b, secure, comp, extras, clip);
+ b.append("}");
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ // Same input parameters that toString() gives to toShortString().
+ dumpDebug(proto, fieldId, true, true, true, false);
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto) {
+ // Same input parameters that toString() gives to toShortString().
+ dumpDebugWithoutFieldId(proto, true, true, true, false);
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
+ boolean extras, boolean clip) {
+ long token = proto.start(fieldId);
+ dumpDebugWithoutFieldId(proto, secure, comp, extras, clip);
+ proto.end(token);
+ }
+
+ private void dumpDebugWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp,
+ boolean extras, boolean clip) {
+ if (mAction != null) {
+ proto.write(IntentProto.ACTION, mAction);
+ }
+ if (mCategories != null) {
+ for (String category : mCategories) {
+ proto.write(IntentProto.CATEGORIES, category);
+ }
+ }
+ if (mData != null) {
+ proto.write(IntentProto.DATA, secure ? mData.toSafeString() : mData.toString());
+ }
+ if (mType != null) {
+ proto.write(IntentProto.TYPE, mType);
+ }
+ if (mIdentifier != null) {
+ proto.write(IntentProto.IDENTIFIER, mIdentifier);
+ }
+ if (mFlags != 0) {
+ proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
+ }
+ if (mPackage != null) {
+ proto.write(IntentProto.PACKAGE, mPackage);
+ }
+ if (comp && mComponent != null) {
+ mComponent.dumpDebug(proto, IntentProto.COMPONENT);
+ }
+ if (mSourceBounds != null) {
+ proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString());
+ }
+ if (mClipData != null) {
+ StringBuilder b = new StringBuilder();
+ mClipData.toShortString(b, !clip || secure);
+ proto.write(IntentProto.CLIP_DATA, b.toString());
+ }
+ if (extras && mExtras != null) {
+ proto.write(IntentProto.EXTRAS, mExtras.toShortString());
+ }
+ if (mContentUserHint != 0) {
+ proto.write(IntentProto.CONTENT_USER_HINT, mContentUserHint);
+ }
+ if (mSelector != null) {
+ proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
+ }
+ }
+
+ /**
+ * Call {@link #toUri} with 0 flags.
+ * @deprecated Use {@link #toUri} instead.
+ */
+ @Deprecated
+ public String toURI() {
+ return toUri(0);
+ }
+
+ /**
+ * Convert this Intent into a String holding a URI representation of it.
+ * The returned URI string has been properly URI encoded, so it can be
+ * used with {@link Uri#parse Uri.parse(String)}. The URI contains the
+ * Intent's data as the base URI, with an additional fragment describing
+ * the action, categories, type, flags, package, component, and extras.
+ *
+ * <p>You can convert the returned string back to an Intent with
+ * {@link #getIntent}.
+ *
+ * @param flags Additional operating flags.
+ *
+ * @return Returns a URI encoding URI string describing the entire contents
+ * of the Intent.
+ */
+ public String toUri(@UriFlags int flags) {
+ StringBuilder uri = new StringBuilder(128);
+ if ((flags&URI_ANDROID_APP_SCHEME) != 0) {
+ if (mPackage == null) {
+ throw new IllegalArgumentException(
+ "Intent must include an explicit package name to build an android-app: "
+ + this);
+ }
+ uri.append("android-app://");
+ uri.append(mPackage);
+ String scheme = null;
+ if (mData != null) {
+ scheme = mData.getScheme();
+ if (scheme != null) {
+ uri.append('/');
+ uri.append(scheme);
+ String authority = mData.getEncodedAuthority();
+ if (authority != null) {
+ uri.append('/');
+ uri.append(authority);
+ String path = mData.getEncodedPath();
+ if (path != null) {
+ uri.append(path);
+ }
+ String queryParams = mData.getEncodedQuery();
+ if (queryParams != null) {
+ uri.append('?');
+ uri.append(queryParams);
+ }
+ String fragment = mData.getEncodedFragment();
+ if (fragment != null) {
+ uri.append('#');
+ uri.append(fragment);
+ }
+ }
+ }
+ }
+ toUriFragment(uri, null, scheme == null ? Intent.ACTION_MAIN : Intent.ACTION_VIEW,
+ mPackage, flags);
+ return uri.toString();
+ }
+ String scheme = null;
+ if (mData != null) {
+ String data = mData.toString();
+ if ((flags&URI_INTENT_SCHEME) != 0) {
+ final int N = data.length();
+ for (int i=0; i<N; i++) {
+ char c = data.charAt(i);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+') {
+ continue;
+ }
+ if (c == ':' && i > 0) {
+ // Valid scheme.
+ scheme = data.substring(0, i);
+ uri.append("intent:");
+ data = data.substring(i+1);
+ break;
+ }
+
+ // No scheme.
+ break;
+ }
+ }
+ uri.append(data);
+
+ } else if ((flags&URI_INTENT_SCHEME) != 0) {
+ uri.append("intent:");
+ }
+
+ toUriFragment(uri, scheme, Intent.ACTION_VIEW, null, flags);
+
+ return uri.toString();
+ }
+
+ private void toUriFragment(StringBuilder uri, String scheme, String defAction,
+ String defPackage, int flags) {
+ StringBuilder frag = new StringBuilder(128);
+
+ toUriInner(frag, scheme, defAction, defPackage, flags);
+ if (mSelector != null) {
+ frag.append("SEL;");
+ // Note that for now we are not going to try to handle the
+ // data part; not clear how to represent this as a URI, and
+ // not much utility in it.
+ mSelector.toUriInner(frag, mSelector.mData != null ? mSelector.mData.getScheme() : null,
+ null, null, flags);
+ }
+
+ if (frag.length() > 0) {
+ uri.append("#Intent;");
+ uri.append(frag);
+ uri.append("end");
+ }
+ }
+
+ private void toUriInner(StringBuilder uri, String scheme, String defAction,
+ String defPackage, int flags) {
+ if (scheme != null) {
+ uri.append("scheme=").append(scheme).append(';');
+ }
+ if (mAction != null && !mAction.equals(defAction)) {
+ uri.append("action=").append(Uri.encode(mAction)).append(';');
+ }
+ if (mCategories != null) {
+ for (int i=0; i<mCategories.size(); i++) {
+ uri.append("category=").append(Uri.encode(mCategories.valueAt(i))).append(';');
+ }
+ }
+ if (mType != null) {
+ uri.append("type=").append(Uri.encode(mType, "/")).append(';');
+ }
+ if (mIdentifier != null) {
+ uri.append("identifier=").append(Uri.encode(mIdentifier, "/")).append(';');
+ }
+ if (mFlags != 0) {
+ uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
+ }
+ if (mPackage != null && !mPackage.equals(defPackage)) {
+ uri.append("package=").append(Uri.encode(mPackage)).append(';');
+ }
+ if (mComponent != null) {
+ uri.append("component=").append(Uri.encode(
+ mComponent.flattenToShortString(), "/")).append(';');
+ }
+ if (mSourceBounds != null) {
+ uri.append("sourceBounds=")
+ .append(Uri.encode(mSourceBounds.flattenToString()))
+ .append(';');
+ }
+ if (mExtras != null) {
+ for (String key : mExtras.keySet()) {
+ final Object value = mExtras.get(key);
+ char entryType =
+ value instanceof String ? 'S' :
+ value instanceof Boolean ? 'B' :
+ value instanceof Byte ? 'b' :
+ value instanceof Character ? 'c' :
+ value instanceof Double ? 'd' :
+ value instanceof Float ? 'f' :
+ value instanceof Integer ? 'i' :
+ value instanceof Long ? 'l' :
+ value instanceof Short ? 's' :
+ '\0';
+
+ if (entryType != '\0') {
+ uri.append(entryType);
+ uri.append('.');
+ uri.append(Uri.encode(key));
+ uri.append('=');
+ uri.append(Uri.encode(value.toString()));
+ uri.append(';');
+ }
+ }
+ }
+ }
+
+ public int describeContents() {
+ return (mExtras != null) ? mExtras.describeContents() : 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString8(mAction);
+ Uri.writeToParcel(out, mData);
+ out.writeString8(mType);
+ out.writeString8(mIdentifier);
+ out.writeInt(mFlags);
+ out.writeString8(mPackage);
+ ComponentName.writeToParcel(mComponent, out);
+
+ if (mSourceBounds != null) {
+ out.writeInt(1);
+ mSourceBounds.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
+
+ if (mCategories != null) {
+ final int N = mCategories.size();
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ out.writeString8(mCategories.valueAt(i));
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ if (mSelector != null) {
+ out.writeInt(1);
+ mSelector.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
+
+ if (mClipData != null) {
+ out.writeInt(1);
+ mClipData.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(mContentUserHint);
+ out.writeBundle(mExtras);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<Intent> CREATOR
+ = new Parcelable.Creator<Intent>() {
+ public Intent createFromParcel(Parcel in) {
+ return new Intent(in);
+ }
+ public Intent[] newArray(int size) {
+ return new Intent[size];
+ }
+ };
+
+ /** @hide */
+ protected Intent(Parcel in) {
+ // Remember that we came from a remote process to help detect security
+ // issues caused by later unsafe launches
+ mLocalFlags = LOCAL_FLAG_FROM_PARCEL;
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ setAction(in.readString8());
+ mData = Uri.CREATOR.createFromParcel(in);
+ mType = in.readString8();
+ mIdentifier = in.readString8();
+ mFlags = in.readInt();
+ mPackage = in.readString8();
+ mComponent = ComponentName.readFromParcel(in);
+
+ if (in.readInt() != 0) {
+ mSourceBounds = Rect.CREATOR.createFromParcel(in);
+ }
+
+ int N = in.readInt();
+ if (N > 0) {
+ mCategories = new ArraySet<String>();
+ int i;
+ for (i=0; i<N; i++) {
+ mCategories.add(in.readString8().intern());
+ }
+ } else {
+ mCategories = null;
+ }
+
+ if (in.readInt() != 0) {
+ mSelector = new Intent(in);
+ }
+
+ if (in.readInt() != 0) {
+ mClipData = new ClipData(in);
+ }
+ mContentUserHint = in.readInt();
+ mExtras = in.readBundle();
+ }
+
+ /**
+ * Parses the "intent" element (and its children) from XML and instantiates
+ * an Intent object. The given XML parser should be located at the tag
+ * where parsing should start (often named "intent"), from which the
+ * basic action, data, type, and package and class name will be
+ * retrieved. The function will then parse in to any child elements,
+ * looking for <category android:name="xxx"> tags to add categories and
+ * <extra android:name="xxx" android:value="yyy"> to attach extra data
+ * to the intent.
+ *
+ * @param resources The Resources to use when inflating resources.
+ * @param parser The XML parser pointing at an "intent" tag.
+ * @param attrs The AttributeSet interface for retrieving extended
+ * attribute data at the current <var>parser</var> location.
+ * @return An Intent object matching the XML data.
+ * @throws XmlPullParserException If there was an XML parsing error.
+ * @throws IOException If there was an I/O error.
+ */
+ public static @NonNull Intent parseIntent(@NonNull Resources resources,
+ @NonNull XmlPullParser parser, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ Intent intent = new Intent();
+
+ TypedArray sa = resources.obtainAttributes(attrs,
+ com.android.internal.R.styleable.Intent);
+
+ intent.setAction(sa.getString(com.android.internal.R.styleable.Intent_action));
+
+ String data = sa.getString(com.android.internal.R.styleable.Intent_data);
+ String mimeType = sa.getString(com.android.internal.R.styleable.Intent_mimeType);
+ intent.setDataAndType(data != null ? Uri.parse(data) : null, mimeType);
+
+ intent.setIdentifier(sa.getString(com.android.internal.R.styleable.Intent_identifier));
+
+ String packageName = sa.getString(com.android.internal.R.styleable.Intent_targetPackage);
+ String className = sa.getString(com.android.internal.R.styleable.Intent_targetClass);
+ if (packageName != null && className != null) {
+ intent.setComponent(new ComponentName(packageName, className));
+ }
+
+ sa.recycle();
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String nodeName = parser.getName();
+ if (nodeName.equals(TAG_CATEGORIES)) {
+ sa = resources.obtainAttributes(attrs,
+ com.android.internal.R.styleable.IntentCategory);
+ String cat = sa.getString(com.android.internal.R.styleable.IntentCategory_name);
+ sa.recycle();
+
+ if (cat != null) {
+ intent.addCategory(cat);
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (nodeName.equals(TAG_EXTRA)) {
+ if (intent.mExtras == null) {
+ intent.mExtras = new Bundle();
+ }
+ resources.parseBundleExtra(TAG_EXTRA, attrs, intent.mExtras);
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ return intent;
+ }
+
+ /** @hide */
+ public void saveToXml(XmlSerializer out) throws IOException {
+ if (mAction != null) {
+ out.attribute(null, ATTR_ACTION, mAction);
+ }
+ if (mData != null) {
+ out.attribute(null, ATTR_DATA, mData.toString());
+ }
+ if (mType != null) {
+ out.attribute(null, ATTR_TYPE, mType);
+ }
+ if (mIdentifier != null) {
+ out.attribute(null, ATTR_IDENTIFIER, mIdentifier);
+ }
+ if (mComponent != null) {
+ out.attribute(null, ATTR_COMPONENT, mComponent.flattenToShortString());
+ }
+ out.attribute(null, ATTR_FLAGS, Integer.toHexString(getFlags()));
+
+ if (mCategories != null) {
+ out.startTag(null, TAG_CATEGORIES);
+ for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) {
+ out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx));
+ }
+ out.endTag(null, TAG_CATEGORIES);
+ }
+ }
+
+ /** @hide */
+ public static Intent restoreFromXml(XmlPullParser in) throws IOException,
+ XmlPullParserException {
+ Intent intent = new Intent();
+ final int outerDepth = in.getDepth();
+
+ int attrCount = in.getAttributeCount();
+ for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) {
+ final String attrName = in.getAttributeName(attrNdx);
+ final String attrValue = in.getAttributeValue(attrNdx);
+ if (ATTR_ACTION.equals(attrName)) {
+ intent.setAction(attrValue);
+ } else if (ATTR_DATA.equals(attrName)) {
+ intent.setData(Uri.parse(attrValue));
+ } else if (ATTR_TYPE.equals(attrName)) {
+ intent.setType(attrValue);
+ } else if (ATTR_IDENTIFIER.equals(attrName)) {
+ intent.setIdentifier(attrValue);
+ } else if (ATTR_COMPONENT.equals(attrName)) {
+ intent.setComponent(ComponentName.unflattenFromString(attrValue));
+ } else if (ATTR_FLAGS.equals(attrName)) {
+ intent.setFlags(Integer.parseInt(attrValue, 16));
+ } else {
+ Log.e(TAG, "restoreFromXml: unknown attribute=" + attrName);
+ }
+ }
+
+ int event;
+ String name;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ name = in.getName();
+ if (TAG_CATEGORIES.equals(name)) {
+ attrCount = in.getAttributeCount();
+ for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) {
+ intent.addCategory(in.getAttributeValue(attrNdx));
+ }
+ } else {
+ Log.w(TAG, "restoreFromXml: unknown name=" + name);
+ XmlUtils.skipCurrentTag(in);
+ }
+ }
+ }
+
+ return intent;
+ }
+
+ /**
+ * Normalize a MIME data type.
+ *
+ * <p>A normalized MIME type has white-space trimmed,
+ * content-type parameters removed, and is lower-case.
+ * This aligns the type with Android best practices for
+ * intent filtering.
+ *
+ * <p>For example, "text/plain; charset=utf-8" becomes "text/plain".
+ * "text/x-vCard" becomes "text/x-vcard".
+ *
+ * <p>All MIME types received from outside Android (such as user input,
+ * or external sources like Bluetooth, NFC, or the Internet) should
+ * be normalized before they are used to create an Intent.
+ *
+ * @param type MIME data type to normalize
+ * @return normalized MIME data type, or null if the input was null
+ * @see #setType
+ * @see #setTypeAndNormalize
+ */
+ public static @Nullable String normalizeMimeType(@Nullable String type) {
+ if (type == null) {
+ return null;
+ }
+
+ type = type.trim().toLowerCase(Locale.ROOT);
+
+ final int semicolonIndex = type.indexOf(';');
+ if (semicolonIndex != -1) {
+ type = type.substring(0, semicolonIndex);
+ }
+ return type;
+ }
+
+ /**
+ * Prepare this {@link Intent} to leave an app process.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void prepareToLeaveProcess(Context context) {
+ final boolean leavingPackage = (mComponent == null)
+ || !Objects.equals(mComponent.getPackageName(), context.getPackageName());
+ prepareToLeaveProcess(leavingPackage);
+ }
+
+ /**
+ * Prepare this {@link Intent} to leave an app process.
+ *
+ * @hide
+ */
+ public void prepareToLeaveProcess(boolean leavingPackage) {
+ setAllowFds(false);
+
+ if (mSelector != null) {
+ mSelector.prepareToLeaveProcess(leavingPackage);
+ }
+ if (mClipData != null) {
+ mClipData.prepareToLeaveProcess(leavingPackage, getFlags());
+ }
+
+ if (mExtras != null && !mExtras.isParcelled()) {
+ final Object intent = mExtras.get(Intent.EXTRA_INTENT);
+ if (intent instanceof Intent) {
+ ((Intent) intent).prepareToLeaveProcess(leavingPackage);
+ }
+ }
+
+ if (mAction != null && mData != null && StrictMode.vmFileUriExposureEnabled()
+ && leavingPackage) {
+ switch (mAction) {
+ case ACTION_MEDIA_REMOVED:
+ case ACTION_MEDIA_UNMOUNTED:
+ case ACTION_MEDIA_CHECKING:
+ case ACTION_MEDIA_NOFS:
+ case ACTION_MEDIA_MOUNTED:
+ case ACTION_MEDIA_SHARED:
+ case ACTION_MEDIA_UNSHARED:
+ case ACTION_MEDIA_BAD_REMOVAL:
+ case ACTION_MEDIA_UNMOUNTABLE:
+ case ACTION_MEDIA_EJECT:
+ case ACTION_MEDIA_SCANNER_STARTED:
+ case ACTION_MEDIA_SCANNER_FINISHED:
+ case ACTION_MEDIA_SCANNER_SCAN_FILE:
+ case ACTION_PACKAGE_NEEDS_VERIFICATION:
+ case ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION:
+ case ACTION_PACKAGE_VERIFIED:
+ case ACTION_PACKAGE_ENABLE_ROLLBACK:
+ // Ignore legacy actions
+ break;
+ default:
+ mData.checkFileUriExposed("Intent.getData()");
+ }
+ }
+
+ if (mAction != null && mData != null && StrictMode.vmContentUriWithoutPermissionEnabled()
+ && leavingPackage) {
+ switch (mAction) {
+ case ACTION_PROVIDER_CHANGED:
+ case QuickContact.ACTION_QUICK_CONTACT:
+ // Ignore actions that don't need to grant
+ break;
+ default:
+ mData.checkContentUriWithoutPermission("Intent.getData()", getFlags());
+ }
+ }
+
+ // Translate raw filesystem paths out of storage sandbox
+ if (ACTION_MEDIA_SCANNER_SCAN_FILE.equals(mAction) && mData != null
+ && ContentResolver.SCHEME_FILE.equals(mData.getScheme()) && leavingPackage) {
+ final StorageManager sm = AppGlobals.getInitialApplication()
+ .getSystemService(StorageManager.class);
+ final File before = new File(mData.getPath());
+ final File after = sm.translateAppToSystem(before,
+ android.os.Process.myPid(), android.os.Process.myUid());
+ if (!Objects.equals(before, after)) {
+ Log.v(TAG, "Translated " + before + " to " + after);
+ mData = Uri.fromFile(after);
+ }
+ }
+
+ // Detect cases where we're about to launch a potentially unsafe intent
+ if (StrictMode.vmUnsafeIntentLaunchEnabled()) {
+ if ((mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0
+ && (mLocalFlags & LOCAL_FLAG_FROM_PROTECTED_COMPONENT) == 0) {
+ StrictMode.onUnsafeIntentLaunch(this);
+ } else if ((mLocalFlags & LOCAL_FLAG_UNFILTERED_EXTRAS) != 0) {
+ StrictMode.onUnsafeIntentLaunch(this);
+ } else if ((mLocalFlags & LOCAL_FLAG_FROM_URI) != 0
+ && !(mCategories != null && mCategories.contains(CATEGORY_BROWSABLE)
+ && mComponent == null)) {
+ // Since the docs for #URI_ALLOW_UNSAFE recommend setting the category to browsable
+ // for an implicit Intent parsed from a URI a violation should be reported if these
+ // conditions are not met.
+ StrictMode.onUnsafeIntentLaunch(this);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void prepareToEnterProcess(boolean fromProtectedComponent, AttributionSource source) {
+ // We just entered destination process, so we should be able to read all
+ // parcelables inside.
+ setDefusable(true);
+
+ if (mSelector != null) {
+ // We can't recursively claim that this data is from a protected
+ // component, since it may have been filled in by a malicious app
+ mSelector.prepareToEnterProcess(false, source);
+ }
+ if (mClipData != null) {
+ mClipData.prepareToEnterProcess(source);
+ }
+
+ if (mContentUserHint != UserHandle.USER_CURRENT) {
+ if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+ fixUris(mContentUserHint);
+ mContentUserHint = UserHandle.USER_CURRENT;
+ }
+ }
+
+ if (fromProtectedComponent) {
+ mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT;
+ }
+
+ // Special attribution fix-up logic for any BluetoothDevice extras
+ // passed via Bluetooth intents
+ if (mAction != null && mAction.startsWith("android.bluetooth.")
+ && hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
+ final BluetoothDevice device = getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device != null) {
+ device.prepareToEnterProcess(source);
+ }
+ }
+ }
+
+ /** @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();
+ }
+
+ private boolean isImageCaptureIntent() {
+ return (MediaStore.ACTION_IMAGE_CAPTURE.equals(mAction)
+ || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(mAction)
+ || MediaStore.ACTION_VIDEO_CAPTURE.equals(mAction));
+ }
+
+ /** @hide */
+ public boolean isImplicitImageCaptureIntent() {
+ return mPackage == null && mComponent == null && isImageCaptureIntent();
+ }
+
+ /**
+ * @hide
+ */
+ public void fixUris(int contentUserHint) {
+ Uri data = getData();
+ if (data != null) {
+ mData = maybeAddUserId(data, contentUserHint);
+ }
+ if (mClipData != null) {
+ mClipData.fixUris(contentUserHint);
+ }
+ String action = getAction();
+ if (ACTION_SEND.equals(action)) {
+ final Uri stream = getParcelableExtra(EXTRA_STREAM);
+ if (stream != null) {
+ putExtra(EXTRA_STREAM, maybeAddUserId(stream, contentUserHint));
+ }
+ } else if (ACTION_SEND_MULTIPLE.equals(action)) {
+ final ArrayList<Uri> streams = getParcelableArrayListExtra(EXTRA_STREAM);
+ if (streams != null) {
+ ArrayList<Uri> newStreams = new ArrayList<Uri>();
+ for (int i = 0; i < streams.size(); i++) {
+ newStreams.add(maybeAddUserId(streams.get(i), contentUserHint));
+ }
+ putParcelableArrayListExtra(EXTRA_STREAM, newStreams);
+ }
+ } else if (isImageCaptureIntent()) {
+ final Uri output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
+ if (output != null) {
+ putExtra(MediaStore.EXTRA_OUTPUT, maybeAddUserId(output, contentUserHint));
+ }
+ }
+ }
+
+ /**
+ * Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and
+ * {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. Also inspects nested
+ * intents in {@link #ACTION_CHOOSER}.
+ *
+ * @return Whether any contents were migrated.
+ * @hide
+ */
+ public boolean migrateExtraStreamToClipData() {
+ return migrateExtraStreamToClipData(AppGlobals.getInitialApplication());
+ }
+
+ /**
+ * Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and
+ * {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. Also inspects nested
+ * intents in {@link #ACTION_CHOOSER}.
+ *
+ * @param context app context
+ * @return Whether any contents were migrated.
+ * @hide
+ */
+ public boolean migrateExtraStreamToClipData(Context context) {
+ // Refuse to touch if extras already parcelled
+ if (mExtras != null && mExtras.isParcelled()) return false;
+
+ // Bail when someone already gave us ClipData
+ if (getClipData() != null) return false;
+
+ final String action = getAction();
+ if (ACTION_CHOOSER.equals(action)) {
+ // Inspect contained intents to see if we need to migrate extras. We
+ // don't promote ClipData to the parent, since ChooserActivity will
+ // already start the picked item as the caller, and we can't combine
+ // the flags in a safe way.
+
+ boolean migrated = false;
+ try {
+ final Intent intent = getParcelableExtra(EXTRA_INTENT);
+ if (intent != null) {
+ migrated |= intent.migrateExtraStreamToClipData(context);
+ }
+ } catch (ClassCastException e) {
+ }
+ try {
+ final Parcelable[] intents = getParcelableArrayExtra(EXTRA_INITIAL_INTENTS);
+ if (intents != null) {
+ for (int i = 0; i < intents.length; i++) {
+ final Intent intent = (Intent) intents[i];
+ if (intent != null) {
+ migrated |= intent.migrateExtraStreamToClipData(context);
+ }
+ }
+ }
+ } catch (ClassCastException e) {
+ }
+ return migrated;
+
+ } else if (ACTION_SEND.equals(action)) {
+ try {
+ final Uri stream = getParcelableExtra(EXTRA_STREAM);
+ final CharSequence text = getCharSequenceExtra(EXTRA_TEXT);
+ final String htmlText = getStringExtra(EXTRA_HTML_TEXT);
+ if (stream != null || text != null || htmlText != null) {
+ final ClipData clipData = new ClipData(
+ null, new String[] { getType() },
+ new ClipData.Item(text, htmlText, null, stream));
+ setClipData(clipData);
+ addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ return true;
+ }
+ } catch (ClassCastException e) {
+ }
+
+ } else if (ACTION_SEND_MULTIPLE.equals(action)) {
+ try {
+ final ArrayList<Uri> streams = getParcelableArrayListExtra(EXTRA_STREAM);
+ final ArrayList<CharSequence> texts = getCharSequenceArrayListExtra(EXTRA_TEXT);
+ final ArrayList<String> htmlTexts = getStringArrayListExtra(EXTRA_HTML_TEXT);
+ int num = -1;
+ if (streams != null) {
+ num = streams.size();
+ }
+ if (texts != null) {
+ if (num >= 0 && num != texts.size()) {
+ // Wha...! F- you.
+ return false;
+ }
+ num = texts.size();
+ }
+ if (htmlTexts != null) {
+ if (num >= 0 && num != htmlTexts.size()) {
+ // Wha...! F- you.
+ return false;
+ }
+ num = htmlTexts.size();
+ }
+ if (num > 0) {
+ final ClipData clipData = new ClipData(
+ null, new String[] { getType() },
+ makeClipItem(streams, texts, htmlTexts, 0));
+
+ for (int i = 1; i < num; i++) {
+ clipData.addItem(makeClipItem(streams, texts, htmlTexts, i));
+ }
+
+ setClipData(clipData);
+ addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ return true;
+ }
+ } catch (ClassCastException e) {
+ }
+ } else if (isImageCaptureIntent()) {
+ Uri output;
+ try {
+ output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
+ } catch (ClassCastException e) {
+ return false;
+ }
+
+ if (output != null) {
+ output = maybeConvertFileToContentUri(context, output);
+ putExtra(MediaStore.EXTRA_OUTPUT, output);
+
+ setClipData(ClipData.newRawUri("", output));
+ addFlags(FLAG_GRANT_WRITE_URI_PERMISSION|FLAG_GRANT_READ_URI_PERMISSION);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private Uri maybeConvertFileToContentUri(Context context, Uri uri) {
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())
+ && context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.R) {
+ File file = new File(uri.getPath());
+ try {
+ if (!file.exists()) file.createNewFile();
+ uri = MediaStore.scanFile(context.getContentResolver(), new File(uri.getPath()));
+ if (uri != null) {
+ return uri;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Ignoring failure to create file " + file, e);
+ }
+ }
+ return uri;
+ }
+
+ /**
+ * Convert the dock state to a human readable format.
+ * @hide
+ */
+ public static String dockStateToString(int dock) {
+ switch (dock) {
+ case EXTRA_DOCK_STATE_HE_DESK:
+ return "EXTRA_DOCK_STATE_HE_DESK";
+ case EXTRA_DOCK_STATE_LE_DESK:
+ return "EXTRA_DOCK_STATE_LE_DESK";
+ case EXTRA_DOCK_STATE_CAR:
+ return "EXTRA_DOCK_STATE_CAR";
+ case EXTRA_DOCK_STATE_DESK:
+ return "EXTRA_DOCK_STATE_DESK";
+ case EXTRA_DOCK_STATE_UNDOCKED:
+ return "EXTRA_DOCK_STATE_UNDOCKED";
+ default:
+ return Integer.toString(dock);
+ }
+ }
+
+ private static ClipData.Item makeClipItem(ArrayList<Uri> streams, ArrayList<CharSequence> texts,
+ ArrayList<String> htmlTexts, int which) {
+ Uri uri = streams != null ? streams.get(which) : null;
+ CharSequence text = texts != null ? texts.get(which) : null;
+ String htmlText = htmlTexts != null ? htmlTexts.get(which) : null;
+ return new ClipData.Item(text, htmlText, null, uri);
+ }
+
+ /** @hide */
+ public boolean isDocument() {
+ return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
+ }
+}
diff --git a/android/content/IntentFilter.java b/android/content/IntentFilter.java
new file mode 100644
index 0000000..c5badb9
--- /dev/null
+++ b/android/content/IntentFilter.java
@@ -0,0 +1,2496 @@
+/*
+ * Copyright (C) 2006 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.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.text.TextUtils;
+import android.util.AndroidException;
+import android.util.Log;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+/**
+ * Structured description of Intent values to be matched. An IntentFilter can
+ * match against actions, categories, and data (either via its type, scheme,
+ * and/or path) in an Intent. It also includes a "priority" value which is
+ * used to order multiple matching filters.
+ *
+ * <p>IntentFilter objects are often created in XML as part of a package's
+ * {@link android.R.styleable#AndroidManifest AndroidManifest.xml} file,
+ * using {@link android.R.styleable#AndroidManifestIntentFilter intent-filter}
+ * tags.
+ *
+ * <p>There are three Intent characteristics you can filter on: the
+ * <em>action</em>, <em>data</em>, and <em>categories</em>. For each of these
+ * characteristics you can provide
+ * multiple possible matching values (via {@link #addAction},
+ * {@link #addDataType}, {@link #addDataScheme}, {@link #addDataSchemeSpecificPart},
+ * {@link #addDataAuthority}, {@link #addDataPath}, and {@link #addCategory}, respectively).
+ * For actions, if no data characteristics are specified, then the filter will
+ * only match intents that contain no data.
+ *
+ * <p>The data characteristic is
+ * itself divided into three attributes: type, scheme, authority, and path.
+ * Any that are
+ * specified must match the contents of the Intent. If you specify a scheme
+ * but no type, only Intent that does not have a type (such as mailto:) will
+ * match; a content: URI will never match because they always have a MIME type
+ * that is supplied by their content provider. Specifying a type with no scheme
+ * has somewhat special meaning: it will match either an Intent with no URI
+ * field, or an Intent with a content: or file: URI. If you specify neither,
+ * then only an Intent with no data or type will match. To specify an authority,
+ * you must also specify one or more schemes that it is associated with.
+ * To specify a path, you also must specify both one or more authorities and
+ * one or more schemes it is associated with.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to create and resolve intents, read the
+ * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <h3>Filter Rules</h3>
+ * <p>A match is based on the following rules. Note that
+ * for an IntentFilter to match an Intent, three conditions must hold:
+ * the <strong>action</strong> and <strong>category</strong> must match, and
+ * the data (both the <strong>data type</strong> and
+ * <strong>data scheme+authority+path</strong> if specified) must match
+ * (see {@link #match(ContentResolver, Intent, boolean, String)} for more details
+ * on how the data fields match).
+ *
+ * <p><strong>Action</strong> matches if any of the given values match the
+ * Intent action; if the filter specifies no actions, then it will only match
+ * Intents that do not contain an action.
+ *
+ * <p><strong>Data Type</strong> matches if any of the given values match the
+ * Intent type. The Intent
+ * type is determined by calling {@link Intent#resolveType}. A wildcard can be
+ * used for the MIME sub-type, in both the Intent and IntentFilter, so that the
+ * type "audio/*" will match "audio/mpeg", "audio/aiff", "audio/*", etc.
+ * <em>Note that MIME type matching here is <b>case sensitive</b>, unlike
+ * formal RFC MIME types!</em> You should thus always use lower case letters
+ * for your MIME types.
+ *
+ * <p><strong>Data Scheme</strong> matches if any of the given values match the
+ * Intent data's scheme.
+ * The Intent scheme is determined by calling {@link Intent#getData}
+ * and {@link android.net.Uri#getScheme} on that URI.
+ * <em>Note that scheme matching here is <b>case sensitive</b>, unlike
+ * formal RFC schemes!</em> You should thus always use lower case letters
+ * for your schemes.
+ *
+ * <p><strong>Data Scheme Specific Part</strong> matches if any of the given values match
+ * the Intent's data scheme specific part <em>and</em> one of the data schemes in the filter
+ * has matched the Intent, <em>or</em> no scheme specific parts were supplied in the filter.
+ * The Intent scheme specific part is determined by calling
+ * {@link Intent#getData} and {@link android.net.Uri#getSchemeSpecificPart} on that URI.
+ * <em>Note that scheme specific part matching is <b>case sensitive</b>.</em>
+ *
+ * <p><strong>Data Authority</strong> matches if any of the given values match
+ * the Intent's data authority <em>and</em> one of the data schemes in the filter
+ * has matched the Intent, <em>or</em> no authorities were supplied in the filter.
+ * The Intent authority is determined by calling
+ * {@link Intent#getData} and {@link android.net.Uri#getAuthority} on that URI.
+ * <em>Note that authority matching here is <b>case sensitive</b>, unlike
+ * formal RFC host names!</em> You should thus always use lower case letters
+ * for your authority.
+ *
+ * <p><strong>Data Path</strong> matches if any of the given values match the
+ * Intent's data path <em>and</em> both a scheme and authority in the filter
+ * has matched against the Intent, <em>or</em> no paths were supplied in the
+ * filter. The Intent authority is determined by calling
+ * {@link Intent#getData} and {@link android.net.Uri#getPath} on that URI.
+ *
+ * <p><strong>Categories</strong> match if <em>all</em> of the categories in
+ * the Intent match categories given in the filter. Extra categories in the
+ * filter that are not in the Intent will not cause the match to fail. Note
+ * that unlike the action, an IntentFilter with no categories
+ * will only match an Intent that does not have any categories.
+ */
+public class IntentFilter implements Parcelable {
+ private static final String AGLOB_STR = "aglob";
+ private static final String SGLOB_STR = "sglob";
+ private static final String PREFIX_STR = "prefix";
+ private static final String SUFFIX_STR = "suffix";
+ private static final String LITERAL_STR = "literal";
+ private static final String PATH_STR = "path";
+ private static final String PORT_STR = "port";
+ private static final String HOST_STR = "host";
+ private static final String AUTH_STR = "auth";
+ private static final String SSP_STR = "ssp";
+ private static final String SCHEME_STR = "scheme";
+ private static final String STATIC_TYPE_STR = "staticType";
+ private static final String TYPE_STR = "type";
+ private static final String GROUP_STR = "group";
+ private static final String CAT_STR = "cat";
+ private static final String NAME_STR = "name";
+ private static final String ACTION_STR = "action";
+ private static final String AUTO_VERIFY_STR = "autoVerify";
+
+ /**
+ * The filter {@link #setPriority} value at which system high-priority
+ * receivers are placed; that is, receivers that should execute before
+ * application code. Applications should never use filters with this or
+ * higher priorities.
+ *
+ * @see #setPriority
+ */
+ public static final int SYSTEM_HIGH_PRIORITY = 1000;
+
+ /**
+ * The filter {@link #setPriority} value at which system low-priority
+ * receivers are placed; that is, receivers that should execute after
+ * application code. Applications should never use filters with this or
+ * lower priorities.
+ *
+ * @see #setPriority
+ */
+ public static final int SYSTEM_LOW_PRIORITY = -1000;
+
+ /**
+ * The part of a match constant that describes the category of match
+ * that occurred. May be either {@link #MATCH_CATEGORY_EMPTY},
+ * {@link #MATCH_CATEGORY_SCHEME}, {@link #MATCH_CATEGORY_SCHEME_SPECIFIC_PART},
+ * {@link #MATCH_CATEGORY_HOST}, {@link #MATCH_CATEGORY_PORT},
+ * {@link #MATCH_CATEGORY_PATH}, or {@link #MATCH_CATEGORY_TYPE}. Higher
+ * values indicate a better match.
+ */
+ public static final int MATCH_CATEGORY_MASK = 0xfff0000;
+
+ /**
+ * The part of a match constant that applies a quality adjustment to the
+ * basic category of match. The value {@link #MATCH_ADJUSTMENT_NORMAL}
+ * is no adjustment; higher numbers than that improve the quality, while
+ * lower numbers reduce it.
+ */
+ public static final int MATCH_ADJUSTMENT_MASK = 0x000ffff;
+
+ /**
+ * Quality adjustment applied to the category of match that signifies
+ * the default, base value; higher numbers improve the quality while
+ * lower numbers reduce it.
+ */
+ public static final int MATCH_ADJUSTMENT_NORMAL = 0x8000;
+
+ /**
+ * The filter matched an intent that had no data specified.
+ */
+ public static final int MATCH_CATEGORY_EMPTY = 0x0100000;
+ /**
+ * The filter matched an intent with the same data URI scheme.
+ */
+ public static final int MATCH_CATEGORY_SCHEME = 0x0200000;
+ /**
+ * The filter matched an intent with the same data URI scheme and
+ * authority host.
+ */
+ public static final int MATCH_CATEGORY_HOST = 0x0300000;
+ /**
+ * The filter matched an intent with the same data URI scheme and
+ * authority host and port.
+ */
+ public static final int MATCH_CATEGORY_PORT = 0x0400000;
+ /**
+ * The filter matched an intent with the same data URI scheme,
+ * authority, and path.
+ */
+ public static final int MATCH_CATEGORY_PATH = 0x0500000;
+ /**
+ * The filter matched an intent with the same data URI scheme and
+ * scheme specific part.
+ */
+ public static final int MATCH_CATEGORY_SCHEME_SPECIFIC_PART = 0x0580000;
+ /**
+ * The filter matched an intent with the same data MIME type.
+ */
+ public static final int MATCH_CATEGORY_TYPE = 0x0600000;
+
+ /**
+ * The filter didn't match due to different MIME types.
+ */
+ public static final int NO_MATCH_TYPE = -1;
+ /**
+ * The filter didn't match due to different data URIs.
+ */
+ public static final int NO_MATCH_DATA = -2;
+ /**
+ * The filter didn't match due to different actions.
+ */
+ public static final int NO_MATCH_ACTION = -3;
+ /**
+ * The filter didn't match because it required one or more categories
+ * that were not in the Intent.
+ */
+ public static final int NO_MATCH_CATEGORY = -4;
+
+ /**
+ * HTTP scheme.
+ *
+ * @see #addDataScheme(String)
+ * @hide
+ */
+ public static final String SCHEME_HTTP = "http";
+ /**
+ * HTTPS scheme.
+ *
+ * @see #addDataScheme(String)
+ * @hide
+ */
+ public static final String SCHEME_HTTPS = "https";
+
+ /**
+ * Package scheme
+ *
+ * @see #addDataScheme(String)
+ * @hide
+ */
+ public static final String SCHEME_PACKAGE = "package";
+
+ /**
+ * The value to indicate a wildcard for incoming match arguments.
+ * @hide
+ */
+ public static final String WILDCARD = "*";
+ /** @hide */
+ public static final String WILDCARD_PATH = "/" + WILDCARD;
+
+ private int mPriority;
+ @UnsupportedAppUsage
+ private int mOrder;
+ @UnsupportedAppUsage
+ private final ArrayList<String> mActions;
+ private ArrayList<String> mCategories = null;
+ private ArrayList<String> mDataSchemes = null;
+ private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
+ private ArrayList<AuthorityEntry> mDataAuthorities = null;
+ private ArrayList<PatternMatcher> mDataPaths = null;
+ private ArrayList<String> mStaticDataTypes = null;
+ private ArrayList<String> mDataTypes = null;
+ private ArrayList<String> mMimeGroups = null;
+ private boolean mHasStaticPartialTypes = false;
+ private boolean mHasDynamicPartialTypes = false;
+
+ private static final int STATE_VERIFY_AUTO = 0x00000001;
+ private static final int STATE_NEED_VERIFY = 0x00000010;
+ private static final int STATE_NEED_VERIFY_CHECKED = 0x00000100;
+ private static final int STATE_VERIFIED = 0x00001000;
+
+ private int mVerifyState;
+ /** @hide */
+ public static final int VISIBILITY_NONE = 0;
+ /** @hide */
+ public static final int VISIBILITY_EXPLICIT = 1;
+ /** @hide */
+ public static final int VISIBILITY_IMPLICIT = 2;
+ /** @hide */
+ @IntDef(prefix = { "VISIBILITY_" }, value = {
+ VISIBILITY_NONE,
+ VISIBILITY_EXPLICIT,
+ VISIBILITY_IMPLICIT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstantAppVisibility {}
+ /** Whether or not the intent filter is visible to instant apps. */
+ private @InstantAppVisibility int mInstantAppVisibility;
+ // These functions are the start of more optimized code for managing
+ // the string sets... not yet implemented.
+
+ private static int findStringInSet(String[] set, String string,
+ int[] lengths, int lenPos) {
+ if (set == null) return -1;
+ final int N = lengths[lenPos];
+ for (int i=0; i<N; i++) {
+ if (set[i].equals(string)) return i;
+ }
+ return -1;
+ }
+
+ private static String[] addStringToSet(String[] set, String string,
+ int[] lengths, int lenPos) {
+ if (findStringInSet(set, string, lengths, lenPos) >= 0) return set;
+ if (set == null) {
+ set = new String[2];
+ set[0] = string;
+ lengths[lenPos] = 1;
+ return set;
+ }
+ final int N = lengths[lenPos];
+ if (N < set.length) {
+ set[N] = string;
+ lengths[lenPos] = N+1;
+ return set;
+ }
+
+ String[] newSet = new String[(N*3)/2 + 2];
+ System.arraycopy(set, 0, newSet, 0, N);
+ set = newSet;
+ set[N] = string;
+ lengths[lenPos] = N+1;
+ return set;
+ }
+
+ private static String[] removeStringFromSet(String[] set, String string,
+ int[] lengths, int lenPos) {
+ int pos = findStringInSet(set, string, lengths, lenPos);
+ if (pos < 0) return set;
+ final int N = lengths[lenPos];
+ if (N > (set.length/4)) {
+ int copyLen = N-(pos+1);
+ if (copyLen > 0) {
+ System.arraycopy(set, pos+1, set, pos, copyLen);
+ }
+ set[N-1] = null;
+ lengths[lenPos] = N-1;
+ return set;
+ }
+
+ String[] newSet = new String[set.length/3];
+ if (pos > 0) System.arraycopy(set, 0, newSet, 0, pos);
+ if ((pos+1) < N) System.arraycopy(set, pos+1, newSet, pos, N-(pos+1));
+ return newSet;
+ }
+
+ /**
+ * This exception is thrown when a given MIME type does not have a valid
+ * syntax.
+ */
+ public static class MalformedMimeTypeException extends AndroidException {
+ public MalformedMimeTypeException() {
+ }
+
+ public MalformedMimeTypeException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Create a new IntentFilter instance with a specified action and MIME
+ * type, where you know the MIME type is correctly formatted. This catches
+ * the {@link MalformedMimeTypeException} exception that the constructor
+ * can call and turns it into a runtime exception.
+ *
+ * @param action The action to match, such as Intent.ACTION_VIEW.
+ * @param dataType The type to match, such as "vnd.android.cursor.dir/person".
+ *
+ * @return A new IntentFilter for the given action and type.
+ *
+ * @see #IntentFilter(String, String)
+ */
+ public static IntentFilter create(String action, String dataType) {
+ try {
+ return new IntentFilter(action, dataType);
+ } catch (MalformedMimeTypeException e) {
+ throw new RuntimeException("Bad MIME type", e);
+ }
+ }
+
+ /**
+ * New empty IntentFilter.
+ */
+ public IntentFilter() {
+ mPriority = 0;
+ mActions = new ArrayList<String>();
+ }
+
+ /**
+ * New IntentFilter that matches a single action with no data. If
+ * no data characteristics are subsequently specified, then the
+ * filter will only match intents that contain no data.
+ *
+ * @param action The action to match, such as Intent.ACTION_MAIN.
+ */
+ public IntentFilter(String action) {
+ mPriority = 0;
+ mActions = new ArrayList<String>();
+ addAction(action);
+ }
+
+ /**
+ * New IntentFilter that matches a single action and data type.
+ *
+ * <p><em>Note: MIME type matching in the Android framework is
+ * case-sensitive, unlike formal RFC MIME types. As a result,
+ * you should always write your MIME types with lower case letters,
+ * and any MIME types you receive from outside of Android should be
+ * converted to lower case before supplying them here.</em></p>
+ *
+ * <p>Throws {@link MalformedMimeTypeException} if the given MIME type is
+ * not syntactically correct.
+ *
+ * @param action The action to match, such as Intent.ACTION_VIEW.
+ * @param dataType The type to match, such as "vnd.android.cursor.dir/person".
+ *
+ */
+ public IntentFilter(String action, String dataType)
+ throws MalformedMimeTypeException {
+ mPriority = 0;
+ mActions = new ArrayList<String>();
+ addAction(action);
+ addDataType(dataType);
+ }
+
+ /**
+ * New IntentFilter containing a copy of an existing filter.
+ *
+ * @param o The original filter to copy.
+ */
+ public IntentFilter(IntentFilter o) {
+ mPriority = o.mPriority;
+ mOrder = o.mOrder;
+ mActions = new ArrayList<String>(o.mActions);
+ if (o.mCategories != null) {
+ mCategories = new ArrayList<String>(o.mCategories);
+ }
+ if (o.mStaticDataTypes != null) {
+ mStaticDataTypes = new ArrayList<String>(o.mStaticDataTypes);
+ }
+ if (o.mDataTypes != null) {
+ mDataTypes = new ArrayList<String>(o.mDataTypes);
+ }
+ if (o.mDataSchemes != null) {
+ mDataSchemes = new ArrayList<String>(o.mDataSchemes);
+ }
+ if (o.mDataSchemeSpecificParts != null) {
+ mDataSchemeSpecificParts = new ArrayList<PatternMatcher>(o.mDataSchemeSpecificParts);
+ }
+ if (o.mDataAuthorities != null) {
+ mDataAuthorities = new ArrayList<AuthorityEntry>(o.mDataAuthorities);
+ }
+ if (o.mDataPaths != null) {
+ mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
+ }
+ if (o.mMimeGroups != null) {
+ mMimeGroups = new ArrayList<String>(o.mMimeGroups);
+ }
+ mHasStaticPartialTypes = o.mHasStaticPartialTypes;
+ mHasDynamicPartialTypes = o.mHasDynamicPartialTypes;
+ mVerifyState = o.mVerifyState;
+ mInstantAppVisibility = o.mInstantAppVisibility;
+ }
+
+ /**
+ * Modify priority of this filter. This only affects receiver filters.
+ * The priority of activity filters are set in XML and cannot be changed
+ * programmatically. The default priority is 0. Positive values will be
+ * before the default, lower values will be after it. Applications should
+ * use a value that is larger than {@link #SYSTEM_LOW_PRIORITY} and
+ * smaller than {@link #SYSTEM_HIGH_PRIORITY} .
+ *
+ * @param priority The new priority value.
+ *
+ * @see #getPriority
+ * @see #SYSTEM_LOW_PRIORITY
+ * @see #SYSTEM_HIGH_PRIORITY
+ */
+ public final void setPriority(int priority) {
+ mPriority = priority;
+ }
+
+ /**
+ * Return the priority of this filter.
+ *
+ * @return The priority of the filter.
+ *
+ * @see #setPriority
+ */
+ public final int getPriority() {
+ return mPriority;
+ }
+
+ /** @hide */
+ @SystemApi
+ public final void setOrder(int order) {
+ mOrder = order;
+ }
+
+ /** @hide */
+ @SystemApi
+ public final int getOrder() {
+ return mOrder;
+ }
+
+ /**
+ * Set whether this filter will needs to be automatically verified against its data URIs or not.
+ * The default is false.
+ *
+ * The verification would need to happen only and only if the Intent action is
+ * {@link android.content.Intent#ACTION_VIEW} and the Intent category is
+ * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme
+ * is "http" or "https".
+ *
+ * True means that the filter will need to use its data URIs to be verified.
+ *
+ * @param autoVerify The new autoVerify value.
+ *
+ * @see #getAutoVerify()
+ * @see #addAction(String)
+ * @see #getAction(int)
+ * @see #addCategory(String)
+ * @see #getCategory(int)
+ * @see #addDataScheme(String)
+ * @see #getDataScheme(int)
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final void setAutoVerify(boolean autoVerify) {
+ mVerifyState &= ~STATE_VERIFY_AUTO;
+ if (autoVerify) mVerifyState |= STATE_VERIFY_AUTO;
+ }
+
+ /**
+ * Return if this filter will needs to be automatically verified again its data URIs or not.
+ *
+ * @return True if the filter will needs to be automatically verified. False otherwise.
+ *
+ * @see #setAutoVerify(boolean)
+ *
+ * @hide
+ */
+ public final boolean getAutoVerify() {
+ return ((mVerifyState & STATE_VERIFY_AUTO) == STATE_VERIFY_AUTO);
+ }
+
+ /**
+ * Return if this filter handle all HTTP or HTTPS data URI or not. This is the
+ * core check for whether a given activity qualifies as a "browser".
+ *
+ * @return True if the filter handle all HTTP or HTTPS data URI. False otherwise.
+ *
+ * This will check if:
+ *
+ * - either the Intent category is {@link android.content.Intent#CATEGORY_APP_BROWSER}
+ * - either the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https" and that there is no specific host defined.
+ *
+ * @hide
+ */
+ public final boolean handleAllWebDataURI() {
+ return hasCategory(Intent.CATEGORY_APP_BROWSER) ||
+ (handlesWebUris(false) && countDataAuthorities() == 0);
+ }
+
+ /**
+ * Return if this filter handles HTTP or HTTPS data URIs.
+ *
+ * @return True if the filter handles ACTION_VIEW/CATEGORY_BROWSABLE,
+ * has at least one HTTP or HTTPS data URI pattern defined, and optionally
+ * does not define any non-http/https data URI patterns.
+ *
+ * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https".
+ *
+ * @param onlyWebSchemes When true, requires that the intent filter declare
+ * that it handles *only* http: or https: schemes. This is a requirement for
+ * the intent filter's domain linkage being verifiable.
+ * @hide
+ */
+ public final boolean handlesWebUris(boolean onlyWebSchemes) {
+ // Require ACTION_VIEW, CATEGORY_BROWSEABLE, and at least one scheme
+ if (!hasAction(Intent.ACTION_VIEW)
+ || !hasCategory(Intent.CATEGORY_BROWSABLE)
+ || mDataSchemes == null
+ || mDataSchemes.size() == 0) {
+ return false;
+ }
+
+ // Now allow only the schemes "http" and "https"
+ final int N = mDataSchemes.size();
+ for (int i = 0; i < N; i++) {
+ final String scheme = mDataSchemes.get(i);
+ final boolean isWebScheme =
+ SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme);
+ if (onlyWebSchemes) {
+ // If we're specifically trying to ensure that there are no non-web schemes
+ // declared in this filter, then if we ever see a non-http/https scheme then
+ // we know it's a failure.
+ if (!isWebScheme) {
+ return false;
+ }
+ } else {
+ // If we see any http/https scheme declaration in this case then the
+ // filter matches what we're looking for.
+ if (isWebScheme) {
+ return true;
+ }
+ }
+ }
+
+ // We get here if:
+ // 1) onlyWebSchemes and no non-web schemes were found, i.e success; or
+ // 2) !onlyWebSchemes and no http/https schemes were found, i.e. failure.
+ return onlyWebSchemes;
+ }
+
+ /**
+ * Return if this filter needs to be automatically verified again its data URIs or not.
+ *
+ * @return True if the filter needs to be automatically verified. False otherwise.
+ *
+ * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https".
+ *
+ * @see #setAutoVerify(boolean)
+ *
+ * @hide
+ */
+ public final boolean needsVerification() {
+ return getAutoVerify() && handlesWebUris(true);
+ }
+
+ /**
+ * Return if this filter has been verified
+ *
+ * @return true if the filter has been verified or if autoVerify is false.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final boolean isVerified() {
+ if ((mVerifyState & STATE_NEED_VERIFY_CHECKED) == STATE_NEED_VERIFY_CHECKED) {
+ return ((mVerifyState & STATE_NEED_VERIFY) == STATE_NEED_VERIFY);
+ }
+ return false;
+ }
+
+ /**
+ * Set if this filter has been verified
+ *
+ * @param verified true if this filter has been verified. False otherwise.
+ *
+ * @hide
+ */
+ public void setVerified(boolean verified) {
+ mVerifyState |= STATE_NEED_VERIFY_CHECKED;
+ mVerifyState &= ~STATE_VERIFIED;
+ if (verified) mVerifyState |= STATE_VERIFIED;
+ }
+
+ /** @hide */
+ public void setVisibilityToInstantApp(@InstantAppVisibility int visibility) {
+ mInstantAppVisibility = visibility;
+ }
+ /** @hide */
+ public @InstantAppVisibility int getVisibilityToInstantApp() {
+ return mInstantAppVisibility;
+ }
+ /** @hide */
+ public boolean isVisibleToInstantApp() {
+ return mInstantAppVisibility != VISIBILITY_NONE;
+ }
+ /** @hide */
+ public boolean isExplicitlyVisibleToInstantApp() {
+ return mInstantAppVisibility == VISIBILITY_EXPLICIT;
+ }
+ /** @hide */
+ public boolean isImplicitlyVisibleToInstantApp() {
+ return mInstantAppVisibility == VISIBILITY_IMPLICIT;
+ }
+
+ /**
+ * Add a new Intent action to match against. If any actions are included
+ * in the filter, then an Intent's action must be one of those values for
+ * it to match. If no actions are included, the Intent action is ignored.
+ *
+ * @param action Name of the action to match, such as Intent.ACTION_VIEW.
+ */
+ public final void addAction(String action) {
+ if (!mActions.contains(action)) {
+ mActions.add(action.intern());
+ }
+ }
+
+ /**
+ * Return the number of actions in the filter.
+ */
+ public final int countActions() {
+ return mActions.size();
+ }
+
+ /**
+ * Return an action in the filter.
+ */
+ public final String getAction(int index) {
+ return mActions.get(index);
+ }
+
+ /**
+ * Is the given action included in the filter? Note that if the filter
+ * does not include any actions, false will <em>always</em> be returned.
+ *
+ * @param action The action to look for.
+ *
+ * @return True if the action is explicitly mentioned in the filter.
+ */
+ public final boolean hasAction(String action) {
+ return action != null && mActions.contains(action);
+ }
+
+ /**
+ * Match this filter against an Intent's action. If the filter does not
+ * specify any actions, the match will always fail.
+ *
+ * @param action The desired action to look for.
+ *
+ * @return True if the action is listed in the filter.
+ */
+ public final boolean matchAction(String action) {
+ return matchAction(action, false /*wildcardSupported*/, null /*ignoreActions*/);
+ }
+
+ /**
+ * Variant of {@link #matchAction(String)} that allows a wildcard for the provided action.
+ * @param wildcardSupported if true, will allow action to use wildcards
+ * @param ignoreActions if not null, the set of actions to should not be considered valid while
+ * calculating the match
+ */
+ private boolean matchAction(String action, boolean wildcardSupported,
+ @Nullable Collection<String> ignoreActions) {
+ if (wildcardSupported && WILDCARD.equals(action)) {
+ if (ignoreActions == null) {
+ return !mActions.isEmpty();
+ }
+ for (int i = mActions.size() - 1; i >= 0; i--) {
+ if (!ignoreActions.contains(mActions.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (ignoreActions != null && ignoreActions.contains(action)) {
+ return false;
+ }
+ return hasAction(action);
+ }
+
+ /**
+ * Return an iterator over the filter's actions. If there are no actions,
+ * returns null.
+ */
+ public final Iterator<String> actionsIterator() {
+ return mActions != null ? mActions.iterator() : null;
+ }
+
+ /**
+ * Add a new Intent data type to match against. If any types are
+ * included in the filter, then an Intent's data must be <em>either</em>
+ * one of these types <em>or</em> a matching scheme. If no data types
+ * are included, then an Intent will only match if it specifies no data.
+ *
+ * <p><em>Note: MIME type matching in the Android framework is
+ * case-sensitive, unlike formal RFC MIME types. As a result,
+ * you should always write your MIME types with lower case letters,
+ * and any MIME types you receive from outside of Android should be
+ * converted to lower case before supplying them here.</em></p>
+ *
+ * <p>Throws {@link MalformedMimeTypeException} if the given MIME type is
+ * not syntactically correct.
+ *
+ * @param type Name of the data type to match, such as "vnd.android.cursor.dir/person".
+ *
+ * @see #matchData
+ */
+ public final void addDataType(String type)
+ throws MalformedMimeTypeException {
+ processMimeType(type, (internalType, isPartial) -> {
+ if (mDataTypes == null) {
+ mDataTypes = new ArrayList<>();
+ }
+ if (mStaticDataTypes == null) {
+ mStaticDataTypes = new ArrayList<>();
+ }
+
+ if (mDataTypes.contains(internalType)) {
+ return;
+ }
+
+ mDataTypes.add(internalType.intern());
+ mStaticDataTypes.add(internalType.intern());
+ mHasStaticPartialTypes = mHasStaticPartialTypes || isPartial;
+ });
+ }
+
+ /**
+ * Add a new Intent data type <em>from MIME group</em> to match against. If any types are
+ * included in the filter, then an Intent's data must be <em>either</em>
+ * one of these types <em>or</em> a matching scheme. If no data types
+ * are included, then an Intent will only match if it specifies no data.
+ *
+ * <p><em>Note: MIME type matching in the Android framework is
+ * case-sensitive, unlike formal RFC MIME types. As a result,
+ * you should always write your MIME types with lower case letters,
+ * and any MIME types you receive from outside of Android should be
+ * converted to lower case before supplying them here.</em></p>
+ *
+ * <p>Throws {@link MalformedMimeTypeException} if the given MIME type is
+ * not syntactically correct.
+ *
+ * @param type Name of the data type to match, such as "vnd.android.cursor.dir/person".
+ *
+ * @see #clearDynamicDataTypes()
+ * @hide
+ */
+ public final void addDynamicDataType(String type)
+ throws MalformedMimeTypeException {
+ processMimeType(type, (internalType, isPartial) -> {
+ if (mDataTypes == null) {
+ mDataTypes = new ArrayList<>();
+ }
+
+ if (!mDataTypes.contains(internalType)) {
+ mDataTypes.add(internalType.intern());
+
+ mHasDynamicPartialTypes = mHasDynamicPartialTypes || isPartial;
+ }
+ });
+ }
+
+ /**
+ * Process mime type - convert to representation used internally and check if type is partial,
+ * and then call provided action
+ */
+ private void processMimeType(String type, BiConsumer<String, Boolean> action)
+ throws MalformedMimeTypeException {
+ final int slashpos = type.indexOf('/');
+ final int typelen = type.length();
+ if (slashpos <= 0 || typelen < slashpos + 2) {
+ throw new MalformedMimeTypeException(type);
+ }
+
+ String internalType = type;
+ boolean isPartialType = false;
+ if (typelen == slashpos + 2 && type.charAt(slashpos + 1) == '*') {
+ internalType = type.substring(0, slashpos);
+ isPartialType = true;
+ }
+
+ action.accept(internalType, isPartialType);
+ }
+
+ /**
+ * Remove all previously added Intent data types from IntentFilter.
+ *
+ * @see #addDynamicDataType(String)
+ * @hide
+ */
+ public final void clearDynamicDataTypes() {
+ if (mDataTypes == null) {
+ return;
+ }
+
+ if (mStaticDataTypes != null) {
+ mDataTypes.clear();
+ mDataTypes.addAll(mStaticDataTypes);
+ } else {
+ mDataTypes = null;
+ }
+
+ mHasDynamicPartialTypes = false;
+ }
+
+ /**
+ * Return the number of static data types in the filter.
+ * @hide
+ */
+ public int countStaticDataTypes() {
+ return mStaticDataTypes != null ? mStaticDataTypes.size() : 0;
+ }
+
+ /**
+ * Is the given data type included in the filter? Note that if the filter
+ * does not include any type, false will <em>always</em> be returned.
+ *
+ * @param type The data type to look for.
+ *
+ * @return True if the type is explicitly mentioned in the filter.
+ */
+ public final boolean hasDataType(String type) {
+ return mDataTypes != null && findMimeType(type);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final boolean hasExactDataType(String type) {
+ return mDataTypes != null && mDataTypes.contains(type);
+ }
+
+ /** @hide */
+ public final boolean hasExactDynamicDataType(String type) {
+ return hasExactDataType(type) && !hasExactStaticDataType(type);
+ }
+
+ /** @hide */
+ public final boolean hasExactStaticDataType(String type) {
+ return mStaticDataTypes != null && mStaticDataTypes.contains(type);
+ }
+
+ /**
+ * Return the number of data types in the filter.
+ */
+ public final int countDataTypes() {
+ return mDataTypes != null ? mDataTypes.size() : 0;
+ }
+
+ /**
+ * Return a data type in the filter.
+ */
+ public final String getDataType(int index) {
+ return mDataTypes.get(index);
+ }
+
+ /**
+ * Return an iterator over the filter's data types.
+ */
+ public final Iterator<String> typesIterator() {
+ return mDataTypes != null ? mDataTypes.iterator() : null;
+ }
+
+ /**
+ * Return copy of filter's data types.
+ * @hide
+ */
+ public final List<String> dataTypes() {
+ return mDataTypes != null ? new ArrayList<>(mDataTypes) : null;
+ }
+
+ /** @hide */
+ public final void addMimeGroup(String name) {
+ if (mMimeGroups == null) {
+ mMimeGroups = new ArrayList<>();
+ }
+ if (!mMimeGroups.contains(name)) {
+ mMimeGroups.add(name);
+ }
+ }
+
+ /** @hide */
+ public final boolean hasMimeGroup(String name) {
+ return mMimeGroups != null && mMimeGroups.contains(name);
+ }
+
+ /** @hide */
+ public final String getMimeGroup(int index) {
+ return mMimeGroups.get(index);
+ }
+
+ /** @hide */
+ public final int countMimeGroups() {
+ return mMimeGroups != null ? mMimeGroups.size() : 0;
+ }
+
+ /** @hide */
+ public final Iterator<String> mimeGroupsIterator() {
+ return mMimeGroups != null ? mMimeGroups.iterator() : null;
+ }
+
+ /**
+ * Add a new Intent data scheme to match against. If any schemes are
+ * included in the filter, then an Intent's data must be <em>either</em>
+ * one of these schemes <em>or</em> a matching data type. If no schemes
+ * are included, then an Intent will match only if it includes no data.
+ *
+ * <p><em>Note: scheme matching in the Android framework is
+ * case-sensitive, unlike formal RFC schemes. As a result,
+ * you should always write your schemes with lower case letters,
+ * and any schemes you receive from outside of Android should be
+ * converted to lower case before supplying them here.</em></p>
+ *
+ * @param scheme Name of the scheme to match, such as "http".
+ *
+ * @see #matchData
+ */
+ public final void addDataScheme(String scheme) {
+ if (mDataSchemes == null) mDataSchemes = new ArrayList<String>();
+ if (!mDataSchemes.contains(scheme)) {
+ mDataSchemes.add(scheme.intern());
+ }
+ }
+
+ /**
+ * Return the number of data schemes in the filter.
+ */
+ public final int countDataSchemes() {
+ return mDataSchemes != null ? mDataSchemes.size() : 0;
+ }
+
+ /**
+ * Return a data scheme in the filter.
+ */
+ public final String getDataScheme(int index) {
+ return mDataSchemes.get(index);
+ }
+
+ /**
+ * Is the given data scheme included in the filter? Note that if the
+ * filter does not include any scheme, false will <em>always</em> be
+ * returned.
+ *
+ * @param scheme The data scheme to look for.
+ *
+ * @return True if the scheme is explicitly mentioned in the filter.
+ */
+ public final boolean hasDataScheme(String scheme) {
+ return mDataSchemes != null && mDataSchemes.contains(scheme);
+ }
+
+ /**
+ * Return an iterator over the filter's data schemes.
+ */
+ public final Iterator<String> schemesIterator() {
+ return mDataSchemes != null ? mDataSchemes.iterator() : null;
+ }
+
+ /**
+ * This is an entry for a single authority in the Iterator returned by
+ * {@link #authoritiesIterator()}.
+ */
+ public final static class AuthorityEntry {
+ private final String mOrigHost;
+ private final String mHost;
+ private final boolean mWild;
+ private final int mPort;
+
+ public AuthorityEntry(String host, String port) {
+ mOrigHost = host;
+ mWild = host.length() > 0 && host.charAt(0) == '*';
+ mHost = mWild ? host.substring(1).intern() : host;
+ mPort = port != null ? Integer.parseInt(port) : -1;
+ }
+
+ AuthorityEntry(Parcel src) {
+ mOrigHost = src.readString();
+ mHost = src.readString();
+ mWild = src.readInt() != 0;
+ mPort = src.readInt();
+ }
+
+ void writeToParcel(Parcel dest) {
+ dest.writeString(mOrigHost);
+ dest.writeString(mHost);
+ dest.writeInt(mWild ? 1 : 0);
+ dest.writeInt(mPort);
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ // The original host information is already contained in host and wild, no output now.
+ proto.write(AuthorityEntryProto.HOST, mHost);
+ proto.write(AuthorityEntryProto.WILD, mWild);
+ proto.write(AuthorityEntryProto.PORT, mPort);
+ proto.end(token);
+ }
+
+ public String getHost() {
+ return mOrigHost;
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+ /** @hide */
+ public boolean match(AuthorityEntry other) {
+ if (mWild != other.mWild) {
+ return false;
+ }
+ if (!mHost.equals(other.mHost)) {
+ return false;
+ }
+ if (mPort != other.mPort) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof AuthorityEntry) {
+ final AuthorityEntry other = (AuthorityEntry)obj;
+ return match(other);
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether this AuthorityEntry matches the given data Uri.
+ * <em>Note that this comparison is case-sensitive, unlike formal
+ * RFC host names. You thus should always normalize to lower-case.</em>
+ *
+ * @param data The Uri to match.
+ * @return Returns either {@link IntentFilter#NO_MATCH_DATA},
+ * {@link IntentFilter#MATCH_CATEGORY_PORT}, or
+ * {@link IntentFilter#MATCH_CATEGORY_HOST}.
+ */
+ public int match(Uri data) {
+ return match(data, false);
+ }
+
+ /**
+ * Variant of {@link #match(Uri)} that supports wildcards on the scheme, host and
+ * path of the provided {@link Uri}
+ *
+ * @param wildcardSupported if true, will allow parameters to use wildcards
+ * @hide
+ */
+ public int match(Uri data, boolean wildcardSupported) {
+ String host = data.getHost();
+ if (host == null) {
+ if (wildcardSupported && mWild) {
+ // special case, if no host is provided, but the Authority is wildcard, match
+ return MATCH_CATEGORY_HOST;
+ } else {
+ return NO_MATCH_DATA;
+ }
+ }
+ if (false) Log.v("IntentFilter",
+ "Match host " + host + ": " + mHost);
+ if (!wildcardSupported || !WILDCARD.equals(host)) {
+ if (mWild) {
+ if (host.length() < mHost.length()) {
+ return NO_MATCH_DATA;
+ }
+ host = host.substring(host.length() - mHost.length());
+ }
+ if (host.compareToIgnoreCase(mHost) != 0) {
+ return NO_MATCH_DATA;
+ }
+ }
+ // if we're dealing with wildcard support, we ignore ports
+ if (!wildcardSupported && mPort >= 0) {
+ if (mPort != data.getPort()) {
+ return NO_MATCH_DATA;
+ }
+ return MATCH_CATEGORY_PORT;
+ }
+ return MATCH_CATEGORY_HOST;
+ }
+ }
+
+ /**
+ * Add a new Intent data "scheme specific part" to match against. The filter must
+ * include one or more schemes (via {@link #addDataScheme}) for the
+ * scheme specific part to be considered. If any scheme specific parts are
+ * included in the filter, then an Intent's data must match one of
+ * them. If no scheme specific parts are included, then only the scheme must match.
+ *
+ * <p>The "scheme specific part" that this matches against is the string returned
+ * by {@link android.net.Uri#getSchemeSpecificPart() Uri.getSchemeSpecificPart}.
+ * For Uris that contain a path, this kind of matching is not generally of interest,
+ * since {@link #addDataAuthority(String, String)} and
+ * {@link #addDataPath(String, int)} can provide a better mechanism for matching
+ * them. However, for Uris that do not contain a path, the authority and path
+ * are empty, so this is the only way to match against the non-scheme part.</p>
+ *
+ * @param ssp Either a raw string that must exactly match the scheme specific part
+ * path, or a simple pattern, depending on <var>type</var>.
+ * @param type Determines how <var>ssp</var> will be compared to
+ * determine a match: either {@link PatternMatcher#PATTERN_LITERAL},
+ * {@link PatternMatcher#PATTERN_PREFIX},
+ * {@link PatternMatcher#PATTERN_SUFFIX}, or
+ * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}.
+ *
+ * @see #matchData
+ * @see #addDataScheme
+ */
+ public final void addDataSchemeSpecificPart(String ssp, int type) {
+ addDataSchemeSpecificPart(new PatternMatcher(ssp, type));
+ }
+
+ /** @hide */
+ public final void addDataSchemeSpecificPart(PatternMatcher ssp) {
+ if (mDataSchemeSpecificParts == null) {
+ mDataSchemeSpecificParts = new ArrayList<PatternMatcher>();
+ }
+ mDataSchemeSpecificParts.add(ssp);
+ }
+
+ /**
+ * Return the number of data scheme specific parts in the filter.
+ */
+ public final int countDataSchemeSpecificParts() {
+ return mDataSchemeSpecificParts != null ? mDataSchemeSpecificParts.size() : 0;
+ }
+
+ /**
+ * Return a data scheme specific part in the filter.
+ */
+ public final PatternMatcher getDataSchemeSpecificPart(int index) {
+ return mDataSchemeSpecificParts.get(index);
+ }
+
+ /**
+ * Is the given data scheme specific part included in the filter? Note that if the
+ * filter does not include any scheme specific parts, false will <em>always</em> be
+ * returned.
+ *
+ * @param data The scheme specific part that is being looked for.
+ *
+ * @return Returns true if the data string matches a scheme specific part listed in the
+ * filter.
+ */
+ public final boolean hasDataSchemeSpecificPart(String data) {
+ return hasDataSchemeSpecificPart(data, false);
+ }
+
+ /**
+ * Variant of {@link #hasDataSchemeSpecificPart(String)} that supports wildcards on the provided
+ * ssp.
+ * @param supportWildcards if true, will allow parameters to use wildcards
+ */
+ private boolean hasDataSchemeSpecificPart(String data, boolean supportWildcards) {
+ if (mDataSchemeSpecificParts == null) {
+ return false;
+ }
+ if (supportWildcards && WILDCARD.equals(data) && mDataSchemeSpecificParts.size() > 0) {
+ return true;
+ }
+ final int numDataSchemeSpecificParts = mDataSchemeSpecificParts.size();
+ for (int i = 0; i < numDataSchemeSpecificParts; i++) {
+ final PatternMatcher pe = mDataSchemeSpecificParts.get(i);
+ if (pe.match(data)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final boolean hasDataSchemeSpecificPart(PatternMatcher ssp) {
+ if (mDataSchemeSpecificParts == null) {
+ return false;
+ }
+ final int numDataSchemeSpecificParts = mDataSchemeSpecificParts.size();
+ for (int i = 0; i < numDataSchemeSpecificParts; i++) {
+ final PatternMatcher pe = mDataSchemeSpecificParts.get(i);
+ if (pe.getType() == ssp.getType() && pe.getPath().equals(ssp.getPath())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return an iterator over the filter's data scheme specific parts.
+ */
+ public final Iterator<PatternMatcher> schemeSpecificPartsIterator() {
+ return mDataSchemeSpecificParts != null ? mDataSchemeSpecificParts.iterator() : null;
+ }
+
+ /**
+ * Add a new Intent data authority to match against. The filter must
+ * include one or more schemes (via {@link #addDataScheme}) for the
+ * authority to be considered. If any authorities are
+ * included in the filter, then an Intent's data must match one of
+ * them. If no authorities are included, then only the scheme must match.
+ *
+ * <p><em>Note: host name in the Android framework is
+ * case-sensitive, unlike formal RFC host names. As a result,
+ * you should always write your host names with lower case letters,
+ * and any host names you receive from outside of Android should be
+ * converted to lower case before supplying them here.</em></p>
+ *
+ * @param host The host part of the authority to match. May start with a
+ * single '*' to wildcard the front of the host name.
+ * @param port Optional port part of the authority to match. If null, any
+ * port is allowed.
+ *
+ * @see #matchData
+ * @see #addDataScheme
+ */
+ public final void addDataAuthority(String host, String port) {
+ if (port != null) port = port.intern();
+ addDataAuthority(new AuthorityEntry(host.intern(), port));
+ }
+
+ /** @hide */
+ public final void addDataAuthority(AuthorityEntry ent) {
+ if (mDataAuthorities == null) mDataAuthorities =
+ new ArrayList<AuthorityEntry>();
+ mDataAuthorities.add(ent);
+ }
+
+ /**
+ * Return the number of data authorities in the filter.
+ */
+ public final int countDataAuthorities() {
+ return mDataAuthorities != null ? mDataAuthorities.size() : 0;
+ }
+
+ /**
+ * Return a data authority in the filter.
+ */
+ public final AuthorityEntry getDataAuthority(int index) {
+ return mDataAuthorities.get(index);
+ }
+
+ /**
+ * Is the given data authority included in the filter? Note that if the
+ * filter does not include any authorities, false will <em>always</em> be
+ * returned.
+ *
+ * @param data The data whose authority is being looked for.
+ *
+ * @return Returns true if the data string matches an authority listed in the
+ * filter.
+ */
+ public final boolean hasDataAuthority(Uri data) {
+ return matchDataAuthority(data) >= 0;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final boolean hasDataAuthority(AuthorityEntry auth) {
+ if (mDataAuthorities == null) {
+ return false;
+ }
+ final int numDataAuthorities = mDataAuthorities.size();
+ for (int i = 0; i < numDataAuthorities; i++) {
+ if (mDataAuthorities.get(i).match(auth)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return an iterator over the filter's data authorities.
+ */
+ public final Iterator<AuthorityEntry> authoritiesIterator() {
+ return mDataAuthorities != null ? mDataAuthorities.iterator() : null;
+ }
+
+ /**
+ * Add a new Intent data path to match against. The filter must
+ * include one or more schemes (via {@link #addDataScheme}) <em>and</em>
+ * one or more authorities (via {@link #addDataAuthority}) for the
+ * path to be considered. If any paths are
+ * included in the filter, then an Intent's data must match one of
+ * them. If no paths are included, then only the scheme/authority must
+ * match.
+ *
+ * <p>The path given here can either be a literal that must directly
+ * match or match against a prefix, or it can be a simple globbing pattern.
+ * If the latter, you can use '*' anywhere in the pattern to match zero
+ * or more instances of the previous character, '.' as a wildcard to match
+ * any character, and '\' to escape the next character.
+ *
+ * @param path Either a raw string that must exactly match the file
+ * path, or a simple pattern, depending on <var>type</var>.
+ * @param type Determines how <var>path</var> will be compared to
+ * determine a match: either {@link PatternMatcher#PATTERN_LITERAL},
+ * {@link PatternMatcher#PATTERN_PREFIX},
+ * {@link PatternMatcher#PATTERN_SUFFIX}, or
+ * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}.
+ *
+ * @see #matchData
+ * @see #addDataScheme
+ * @see #addDataAuthority
+ */
+ public final void addDataPath(String path, int type) {
+ addDataPath(new PatternMatcher(path.intern(), type));
+ }
+
+ /** @hide */
+ public final void addDataPath(PatternMatcher path) {
+ if (mDataPaths == null) mDataPaths = new ArrayList<PatternMatcher>();
+ mDataPaths.add(path);
+ }
+
+ /**
+ * Return the number of data paths in the filter.
+ */
+ public final int countDataPaths() {
+ return mDataPaths != null ? mDataPaths.size() : 0;
+ }
+
+ /**
+ * Return a data path in the filter.
+ */
+ public final PatternMatcher getDataPath(int index) {
+ return mDataPaths.get(index);
+ }
+
+ /**
+ * Is the given data path included in the filter? Note that if the
+ * filter does not include any paths, false will <em>always</em> be
+ * returned.
+ *
+ * @param data The data path to look for. This is without the scheme
+ * prefix.
+ *
+ * @return True if the data string matches a path listed in the
+ * filter.
+ */
+ public final boolean hasDataPath(String data) {
+ return hasDataPath(data, false);
+ }
+
+ /**
+ * Variant of {@link #hasDataPath(String)} that supports wildcards on the provided scheme, host,
+ * and path.
+ * @param wildcardSupported if true, will allow parameters to use wildcards
+ */
+ private boolean hasDataPath(String data, boolean wildcardSupported) {
+ if (mDataPaths == null) {
+ return false;
+ }
+ if (wildcardSupported && WILDCARD_PATH.equals(data)) {
+ return true;
+ }
+ final int numDataPaths = mDataPaths.size();
+ for (int i = 0; i < numDataPaths; i++) {
+ final PatternMatcher pe = mDataPaths.get(i);
+ if (pe.match(data)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final boolean hasDataPath(PatternMatcher path) {
+ if (mDataPaths == null) {
+ return false;
+ }
+ final int numDataPaths = mDataPaths.size();
+ for (int i = 0; i < numDataPaths; i++) {
+ final PatternMatcher pe = mDataPaths.get(i);
+ if (pe.getType() == path.getType() && pe.getPath().equals(path.getPath())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return an iterator over the filter's data paths.
+ */
+ public final Iterator<PatternMatcher> pathsIterator() {
+ return mDataPaths != null ? mDataPaths.iterator() : null;
+ }
+
+ /**
+ * Match this intent filter against the given Intent data. This ignores
+ * the data scheme -- unlike {@link #matchData}, the authority will match
+ * regardless of whether there is a matching scheme.
+ *
+ * @param data The data whose authority is being looked for.
+ *
+ * @return Returns either {@link #MATCH_CATEGORY_HOST},
+ * {@link #MATCH_CATEGORY_PORT}, {@link #NO_MATCH_DATA}.
+ */
+ public final int matchDataAuthority(Uri data) {
+ return matchDataAuthority(data, false);
+ }
+
+ /**
+ * Variant of {@link #matchDataAuthority(Uri)} that allows wildcard matching of the provided
+ * authority.
+ *
+ * @param wildcardSupported if true, will allow parameters to use wildcards
+ * @hide
+ */
+ public final int matchDataAuthority(Uri data, boolean wildcardSupported) {
+ if (data == null || mDataAuthorities == null) {
+ return NO_MATCH_DATA;
+ }
+ final int numDataAuthorities = mDataAuthorities.size();
+ for (int i = 0; i < numDataAuthorities; i++) {
+ final AuthorityEntry ae = mDataAuthorities.get(i);
+ int match = ae.match(data, wildcardSupported);
+ if (match >= 0) {
+ return match;
+ }
+ }
+ return NO_MATCH_DATA;
+ }
+
+ /**
+ * Match this filter against an Intent's data (type, scheme and path). If
+ * the filter does not specify any types and does not specify any
+ * schemes/paths, the match will only succeed if the intent does not
+ * also specify a type or data. If the filter does not specify any schemes,
+ * it will implicitly match intents with no scheme, or the schemes "content:"
+ * or "file:" (basically performing a MIME-type only match). If the filter
+ * does not specify any MIME types, the Intent also must not specify a MIME
+ * type.
+ *
+ * <p>Be aware that to match against an authority, you must also specify a base
+ * scheme the authority is in. To match against a data path, both a scheme
+ * and authority must be specified. If the filter does not specify any
+ * types or schemes that it matches against, it is considered to be empty
+ * (any authority or data path given is ignored, as if it were empty as
+ * well).
+ *
+ * <p><em>Note: MIME type, Uri scheme, and host name matching in the
+ * Android framework is case-sensitive, unlike the formal RFC definitions.
+ * As a result, you should always write these elements with lower case letters,
+ * and normalize any MIME types or Uris you receive from
+ * outside of Android to ensure these elements are lower case before
+ * supplying them here.</em></p>
+ *
+ * @param type The desired data type to look for, as returned by
+ * Intent.resolveType().
+ * @param scheme The desired data scheme to look for, as returned by
+ * Intent.getScheme().
+ * @param data The full data string to match against, as supplied in
+ * Intent.data.
+ *
+ * @return Returns either a valid match constant (a combination of
+ * {@link #MATCH_CATEGORY_MASK} and {@link #MATCH_ADJUSTMENT_MASK}),
+ * or one of the error codes {@link #NO_MATCH_TYPE} if the type didn't match
+ * or {@link #NO_MATCH_DATA} if the scheme/path didn't match.
+ *
+ * @see #match
+ */
+ public final int matchData(String type, String scheme, Uri data) {
+ return matchData(type, scheme, data, false);
+ }
+
+ /**
+ * Variant of {@link #matchData(String, String, Uri)} that allows wildcard matching
+ * of the provided type, scheme, host and path parameters.
+ * @param wildcardSupported if true, will allow parameters to use wildcards
+ */
+ private int matchData(String type, String scheme, Uri data, boolean wildcardSupported) {
+ final boolean wildcardWithMimegroups = wildcardSupported && countMimeGroups() != 0;
+ final List<String> types = mDataTypes;
+ final ArrayList<String> schemes = mDataSchemes;
+
+ int match = MATCH_CATEGORY_EMPTY;
+
+ if (!wildcardWithMimegroups && types == null && schemes == null) {
+ return ((type == null && data == null)
+ ? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);
+ }
+
+ if (schemes != null) {
+ if (schemes.contains(scheme != null ? scheme : "")
+ || wildcardSupported && WILDCARD.equals(scheme)) {
+ match = MATCH_CATEGORY_SCHEME;
+ } else {
+ return NO_MATCH_DATA;
+ }
+
+ final ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;
+ if (schemeSpecificParts != null && data != null) {
+ match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart(), wildcardSupported)
+ ? MATCH_CATEGORY_SCHEME_SPECIFIC_PART : NO_MATCH_DATA;
+ }
+ if (match != MATCH_CATEGORY_SCHEME_SPECIFIC_PART) {
+ // If there isn't any matching ssp, we need to match an authority.
+ final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
+ if (authorities != null) {
+ int authMatch = matchDataAuthority(data, wildcardSupported);
+ if (authMatch >= 0) {
+ final ArrayList<PatternMatcher> paths = mDataPaths;
+ if (paths == null) {
+ match = authMatch;
+ } else if (hasDataPath(data.getPath(), wildcardSupported)) {
+ match = MATCH_CATEGORY_PATH;
+ } else {
+ return NO_MATCH_DATA;
+ }
+ } else {
+ return NO_MATCH_DATA;
+ }
+ }
+ }
+ // If neither an ssp nor an authority matched, we're done.
+ if (match == NO_MATCH_DATA) {
+ return NO_MATCH_DATA;
+ }
+ } else {
+ // Special case: match either an Intent with no data URI,
+ // or with a scheme: URI. This is to give a convenience for
+ // the common case where you want to deal with data in a
+ // content provider, which is done by type, and we don't want
+ // to force everyone to say they handle content: or file: URIs.
+ if (scheme != null && !"".equals(scheme)
+ && !"content".equals(scheme)
+ && !"file".equals(scheme)
+ && !(wildcardSupported && WILDCARD.equals(scheme))) {
+ return NO_MATCH_DATA;
+ }
+ }
+
+ if (wildcardWithMimegroups) {
+ return MATCH_CATEGORY_TYPE;
+ } else if (types != null) {
+ if (findMimeType(type)) {
+ match = MATCH_CATEGORY_TYPE;
+ } else {
+ return NO_MATCH_TYPE;
+ }
+ } else {
+ // If no MIME types are specified, then we will only match against
+ // an Intent that does not have a MIME type.
+ if (type != null) {
+ return NO_MATCH_TYPE;
+ }
+ }
+
+ return match + MATCH_ADJUSTMENT_NORMAL;
+ }
+
+ /**
+ * Add a new Intent category to match against. The semantics of
+ * categories is the opposite of actions -- an Intent includes the
+ * categories that it requires, all of which must be included in the
+ * filter in order to match. In other words, adding a category to the
+ * filter has no impact on matching unless that category is specified in
+ * the intent.
+ *
+ * @param category Name of category to match, such as Intent.CATEGORY_EMBED.
+ */
+ public final void addCategory(String category) {
+ if (mCategories == null) mCategories = new ArrayList<String>();
+ if (!mCategories.contains(category)) {
+ mCategories.add(category.intern());
+ }
+ }
+
+ /**
+ * Return the number of categories in the filter.
+ */
+ public final int countCategories() {
+ return mCategories != null ? mCategories.size() : 0;
+ }
+
+ /**
+ * Return a category in the filter.
+ */
+ public final String getCategory(int index) {
+ return mCategories.get(index);
+ }
+
+ /**
+ * Is the given category included in the filter?
+ *
+ * @param category The category that the filter supports.
+ *
+ * @return True if the category is explicitly mentioned in the filter.
+ */
+ public final boolean hasCategory(String category) {
+ return mCategories != null && mCategories.contains(category);
+ }
+
+ /**
+ * Return an iterator over the filter's categories.
+ *
+ * @return Iterator if this filter has categories or {@code null} if none.
+ */
+ public final Iterator<String> categoriesIterator() {
+ return mCategories != null ? mCategories.iterator() : null;
+ }
+
+ /**
+ * Match this filter against an Intent's categories. Each category in
+ * the Intent must be specified by the filter; if any are not in the
+ * filter, the match fails.
+ *
+ * @param categories The categories included in the intent, as returned by
+ * Intent.getCategories().
+ *
+ * @return If all categories match (success), null; else the name of the
+ * first category that didn't match.
+ */
+ public final String matchCategories(Set<String> categories) {
+ if (categories == null) {
+ return null;
+ }
+
+ Iterator<String> it = categories.iterator();
+
+ if (mCategories == null) {
+ return it.hasNext() ? it.next() : null;
+ }
+
+ while (it.hasNext()) {
+ final String category = it.next();
+ if (!mCategories.contains(category)) {
+ return category;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Test whether this filter matches the given <var>intent</var>.
+ *
+ * @param intent The Intent to compare against.
+ * @param resolve If true, the intent's type will be resolved by calling
+ * Intent.resolveType(); otherwise a simple match against
+ * Intent.type will be performed.
+ * @param logTag Tag to use in debugging messages.
+ *
+ * @return Returns either a valid match constant (a combination of
+ * {@link #MATCH_CATEGORY_MASK} and {@link #MATCH_ADJUSTMENT_MASK}),
+ * or one of the error codes {@link #NO_MATCH_TYPE} if the type didn't match,
+ * {@link #NO_MATCH_DATA} if the scheme/path didn't match,
+ * {@link #NO_MATCH_ACTION} if the action didn't match, or
+ * {@link #NO_MATCH_CATEGORY} if one or more categories didn't match.
+ *
+ * @see #match(String, String, String, android.net.Uri , Set, String)
+ */
+ public final int match(ContentResolver resolver, Intent intent,
+ boolean resolve, String logTag) {
+ String type = resolve ? intent.resolveType(resolver) : intent.getType();
+ return match(intent.getAction(), type, intent.getScheme(),
+ intent.getData(), intent.getCategories(), logTag);
+ }
+
+ /**
+ * Test whether this filter matches the given intent data. A match is
+ * only successful if the actions and categories in the Intent match
+ * against the filter, as described in {@link IntentFilter}; in that case,
+ * the match result returned will be as per {@link #matchData}.
+ *
+ * @param action The intent action to match against (Intent.getAction).
+ * @param type The intent type to match against (Intent.resolveType()).
+ * @param scheme The data scheme to match against (Intent.getScheme()).
+ * @param data The data URI to match against (Intent.getData()).
+ * @param categories The categories to match against
+ * (Intent.getCategories()).
+ * @param logTag Tag to use in debugging messages.
+ *
+ * @return Returns either a valid match constant (a combination of
+ * {@link #MATCH_CATEGORY_MASK} and {@link #MATCH_ADJUSTMENT_MASK}),
+ * or one of the error codes {@link #NO_MATCH_TYPE} if the type didn't match,
+ * {@link #NO_MATCH_DATA} if the scheme/path didn't match,
+ * {@link #NO_MATCH_ACTION} if the action didn't match, or
+ * {@link #NO_MATCH_CATEGORY} if one or more categories didn't match.
+ *
+ * @see #matchData
+ * @see Intent#getAction
+ * @see Intent#resolveType
+ * @see Intent#getScheme
+ * @see Intent#getData
+ * @see Intent#getCategories
+ */
+ public final int match(String action, String type, String scheme,
+ Uri data, Set<String> categories, String logTag) {
+ return match(action, type, scheme, data, categories, logTag, false /*supportWildcards*/,
+ null /*ignoreActions*/);
+ }
+
+ /**
+ * Variant of {@link #match(ContentResolver, Intent, boolean, String)} that supports wildcards
+ * in the action, type, scheme, host and path.
+ * @param supportWildcards if true, will allow supported parameters to use wildcards to match
+ * this filter
+ * @param ignoreActions a collection of actions that, if not null should be ignored and not used
+ * if provided as the matching action or the set of actions defined by this
+ * filter
+ * @hide
+ */
+ public final int match(String action, String type, String scheme,
+ Uri data, Set<String> categories, String logTag, boolean supportWildcards,
+ @Nullable Collection<String> ignoreActions) {
+ if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
+ if (false) Log.v(
+ logTag, "No matching action " + action + " for " + this);
+ return NO_MATCH_ACTION;
+ }
+
+ int dataMatch = matchData(type, scheme, data, supportWildcards);
+ if (dataMatch < 0) {
+ if (false) {
+ if (dataMatch == NO_MATCH_TYPE) {
+ Log.v(logTag, "No matching type " + type
+ + " for " + this);
+ }
+ if (dataMatch == NO_MATCH_DATA) {
+ Log.v(logTag, "No matching scheme/path " + data
+ + " for " + this);
+ }
+ }
+ return dataMatch;
+ }
+
+ String categoryMismatch = matchCategories(categories);
+ if (categoryMismatch != null) {
+ if (false) {
+ Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
+ }
+ return NO_MATCH_CATEGORY;
+ }
+
+ // It would be nice to treat container activities as more
+ // important than ones that can be embedded, but this is not the way...
+ if (false) {
+ if (categories != null) {
+ dataMatch -= mCategories.size() - categories.size();
+ }
+ }
+
+ return dataMatch;
+ }
+
+ /**
+ * Write the contents of the IntentFilter as an XML stream.
+ */
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+
+ if (getAutoVerify()) {
+ serializer.attribute(null, AUTO_VERIFY_STR, Boolean.toString(true));
+ }
+
+ int N = countActions();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, ACTION_STR);
+ serializer.attribute(null, NAME_STR, mActions.get(i));
+ serializer.endTag(null, ACTION_STR);
+ }
+ N = countCategories();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, CAT_STR);
+ serializer.attribute(null, NAME_STR, mCategories.get(i));
+ serializer.endTag(null, CAT_STR);
+ }
+ writeDataTypesToXml(serializer);
+ N = countMimeGroups();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, GROUP_STR);
+ serializer.attribute(null, NAME_STR, mMimeGroups.get(i));
+ serializer.endTag(null, GROUP_STR);
+ }
+ N = countDataSchemes();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, SCHEME_STR);
+ serializer.attribute(null, NAME_STR, mDataSchemes.get(i));
+ serializer.endTag(null, SCHEME_STR);
+ }
+ N = countDataSchemeSpecificParts();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, SSP_STR);
+ PatternMatcher pe = mDataSchemeSpecificParts.get(i);
+ switch (pe.getType()) {
+ case PatternMatcher.PATTERN_LITERAL:
+ serializer.attribute(null, LITERAL_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_PREFIX:
+ serializer.attribute(null, PREFIX_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_SIMPLE_GLOB:
+ serializer.attribute(null, SGLOB_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_ADVANCED_GLOB:
+ serializer.attribute(null, AGLOB_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_SUFFIX:
+ serializer.attribute(null, SUFFIX_STR, pe.getPath());
+ break;
+ }
+ serializer.endTag(null, SSP_STR);
+ }
+ N = countDataAuthorities();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, AUTH_STR);
+ AuthorityEntry ae = mDataAuthorities.get(i);
+ serializer.attribute(null, HOST_STR, ae.getHost());
+ if (ae.getPort() >= 0) {
+ serializer.attribute(null, PORT_STR, Integer.toString(ae.getPort()));
+ }
+ serializer.endTag(null, AUTH_STR);
+ }
+ N = countDataPaths();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, PATH_STR);
+ PatternMatcher pe = mDataPaths.get(i);
+ switch (pe.getType()) {
+ case PatternMatcher.PATTERN_LITERAL:
+ serializer.attribute(null, LITERAL_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_PREFIX:
+ serializer.attribute(null, PREFIX_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_SIMPLE_GLOB:
+ serializer.attribute(null, SGLOB_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_ADVANCED_GLOB:
+ serializer.attribute(null, AGLOB_STR, pe.getPath());
+ break;
+ case PatternMatcher.PATTERN_SUFFIX:
+ serializer.attribute(null, SUFFIX_STR, pe.getPath());
+ break;
+ }
+ serializer.endTag(null, PATH_STR);
+ }
+ }
+
+ /**
+ * Write data types (both static and dynamic) to XML.
+ * In implementation we rely on two facts:
+ * - {@link #mStaticDataTypes} is subsequence of {@link #mDataTypes}
+ * - both {@link #mStaticDataTypes} and {@link #mDataTypes} does not contain duplicates
+ */
+ private void writeDataTypesToXml(XmlSerializer serializer) throws IOException {
+ if (mStaticDataTypes == null) {
+ return;
+ }
+
+ int i = 0;
+ for (String staticType: mStaticDataTypes) {
+ while (!mDataTypes.get(i).equals(staticType)) {
+ writeDataTypeToXml(serializer, mDataTypes.get(i), TYPE_STR);
+ i++;
+ }
+
+ writeDataTypeToXml(serializer, staticType, STATIC_TYPE_STR);
+ i++;
+ }
+
+ while (i < mDataTypes.size()) {
+ writeDataTypeToXml(serializer, mDataTypes.get(i), TYPE_STR);
+ i++;
+ }
+ }
+
+ private void writeDataTypeToXml(XmlSerializer serializer, String type, String tag)
+ throws IOException {
+ serializer.startTag(null, tag);
+
+ if (type.indexOf('/') < 0) {
+ type = type + "/*";
+ }
+
+ serializer.attribute(null, NAME_STR, type);
+ serializer.endTag(null, tag);
+ }
+
+ public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ String autoVerify = parser.getAttributeValue(null, AUTO_VERIFY_STR);
+ setAutoVerify(TextUtils.isEmpty(autoVerify) ? false : Boolean.getBoolean(autoVerify));
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(ACTION_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ addAction(name);
+ }
+ } else if (tagName.equals(CAT_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ addCategory(name);
+ }
+ } else if (tagName.equals(STATIC_TYPE_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ try {
+ addDataType(name);
+ } catch (MalformedMimeTypeException e) {
+ }
+ }
+ } else if (tagName.equals(TYPE_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ try {
+ addDynamicDataType(name);
+ } catch (MalformedMimeTypeException e) {
+ }
+ }
+ } else if (tagName.equals(GROUP_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ addMimeGroup(name);
+ }
+ } else if (tagName.equals(SCHEME_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ addDataScheme(name);
+ }
+ } else if (tagName.equals(SSP_STR)) {
+ String ssp = parser.getAttributeValue(null, LITERAL_STR);
+ if (ssp != null) {
+ addDataSchemeSpecificPart(ssp, PatternMatcher.PATTERN_LITERAL);
+ } else if ((ssp=parser.getAttributeValue(null, PREFIX_STR)) != null) {
+ addDataSchemeSpecificPart(ssp, PatternMatcher.PATTERN_PREFIX);
+ } else if ((ssp=parser.getAttributeValue(null, SGLOB_STR)) != null) {
+ addDataSchemeSpecificPart(ssp, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ } else if ((ssp=parser.getAttributeValue(null, AGLOB_STR)) != null) {
+ addDataSchemeSpecificPart(ssp, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ } else if ((ssp=parser.getAttributeValue(null, SUFFIX_STR)) != null) {
+ addDataSchemeSpecificPart(ssp, PatternMatcher.PATTERN_SUFFIX);
+ }
+ } else if (tagName.equals(AUTH_STR)) {
+ String host = parser.getAttributeValue(null, HOST_STR);
+ String port = parser.getAttributeValue(null, PORT_STR);
+ if (host != null) {
+ addDataAuthority(host, port);
+ }
+ } else if (tagName.equals(PATH_STR)) {
+ String path = parser.getAttributeValue(null, LITERAL_STR);
+ if (path != null) {
+ addDataPath(path, PatternMatcher.PATTERN_LITERAL);
+ } else if ((path=parser.getAttributeValue(null, PREFIX_STR)) != null) {
+ addDataPath(path, PatternMatcher.PATTERN_PREFIX);
+ } else if ((path=parser.getAttributeValue(null, SGLOB_STR)) != null) {
+ addDataPath(path, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ } else if ((path=parser.getAttributeValue(null, AGLOB_STR)) != null) {
+ addDataPath(path, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ } else if ((path=parser.getAttributeValue(null, SUFFIX_STR)) != null) {
+ addDataPath(path, PatternMatcher.PATTERN_SUFFIX);
+ }
+ } else {
+ Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (mActions.size() > 0) {
+ Iterator<String> it = mActions.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.ACTIONS, it.next());
+ }
+ }
+ if (mCategories != null) {
+ Iterator<String> it = mCategories.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.CATEGORIES, it.next());
+ }
+ }
+ if (mDataSchemes != null) {
+ Iterator<String> it = mDataSchemes.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.DATA_SCHEMES, it.next());
+ }
+ }
+ if (mDataSchemeSpecificParts != null) {
+ Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator();
+ while (it.hasNext()) {
+ it.next().dumpDebug(proto, IntentFilterProto.DATA_SCHEME_SPECS);
+ }
+ }
+ if (mDataAuthorities != null) {
+ Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+ while (it.hasNext()) {
+ it.next().dumpDebug(proto, IntentFilterProto.DATA_AUTHORITIES);
+ }
+ }
+ if (mDataPaths != null) {
+ Iterator<PatternMatcher> it = mDataPaths.iterator();
+ while (it.hasNext()) {
+ it.next().dumpDebug(proto, IntentFilterProto.DATA_PATHS);
+ }
+ }
+ if (mDataTypes != null) {
+ Iterator<String> it = mDataTypes.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.DATA_TYPES, it.next());
+ }
+ }
+ if (mMimeGroups != null) {
+ Iterator<String> it = mMimeGroups.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.MIME_GROUPS, it.next());
+ }
+ }
+ if (mPriority != 0 || hasPartialTypes()) {
+ proto.write(IntentFilterProto.PRIORITY, mPriority);
+ proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, hasPartialTypes());
+ }
+ proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+ proto.end(token);
+ }
+
+ public void dump(Printer du, String prefix) {
+ StringBuilder sb = new StringBuilder(256);
+ if (mActions.size() > 0) {
+ Iterator<String> it = mActions.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Action: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mCategories != null) {
+ Iterator<String> it = mCategories.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Category: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mDataSchemes != null) {
+ Iterator<String> it = mDataSchemes.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Scheme: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mDataSchemeSpecificParts != null) {
+ Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator();
+ while (it.hasNext()) {
+ PatternMatcher pe = it.next();
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Ssp: \"");
+ sb.append(pe); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mDataAuthorities != null) {
+ Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+ while (it.hasNext()) {
+ AuthorityEntry ae = it.next();
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Authority: \"");
+ sb.append(ae.mHost); sb.append("\": ");
+ sb.append(ae.mPort);
+ if (ae.mWild) sb.append(" WILD");
+ du.println(sb.toString());
+ }
+ }
+ if (mDataPaths != null) {
+ Iterator<PatternMatcher> it = mDataPaths.iterator();
+ while (it.hasNext()) {
+ PatternMatcher pe = it.next();
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Path: \"");
+ sb.append(pe); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mStaticDataTypes != null) {
+ Iterator<String> it = mStaticDataTypes.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("StaticType: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mDataTypes != null) {
+ Iterator<String> it = mDataTypes.iterator();
+ while (it.hasNext()) {
+ String dataType = it.next();
+ if (hasExactStaticDataType(dataType)) {
+ continue;
+ }
+
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Type: \"");
+ sb.append(dataType); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mMimeGroups != null) {
+ Iterator<String> it = mMimeGroups.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("MimeGroup: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mPriority != 0 || mOrder != 0 || hasPartialTypes()) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mPriority="); sb.append(mPriority);
+ sb.append(", mOrder="); sb.append(mOrder);
+ sb.append(", mHasStaticPartialTypes="); sb.append(mHasStaticPartialTypes);
+ sb.append(", mHasDynamicPartialTypes="); sb.append(mHasDynamicPartialTypes);
+ du.println(sb.toString());
+ }
+ if (getAutoVerify()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
+ du.println(sb.toString());
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IntentFilter> CREATOR
+ = new Parcelable.Creator<IntentFilter>() {
+ public IntentFilter createFromParcel(Parcel source) {
+ return new IntentFilter(source);
+ }
+
+ public IntentFilter[] newArray(int size) {
+ return new IntentFilter[size];
+ }
+ };
+
+ public final int describeContents() {
+ return 0;
+ }
+
+ public final void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(mActions);
+ if (mCategories != null) {
+ dest.writeInt(1);
+ dest.writeStringList(mCategories);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mDataSchemes != null) {
+ dest.writeInt(1);
+ dest.writeStringList(mDataSchemes);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mStaticDataTypes != null) {
+ dest.writeInt(1);
+ dest.writeStringList(mStaticDataTypes);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mDataTypes != null) {
+ dest.writeInt(1);
+ dest.writeStringList(mDataTypes);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mMimeGroups != null) {
+ dest.writeInt(1);
+ dest.writeStringList(mMimeGroups);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mDataSchemeSpecificParts != null) {
+ final int N = mDataSchemeSpecificParts.size();
+ dest.writeInt(N);
+ for (int i=0; i<N; i++) {
+ mDataSchemeSpecificParts.get(i).writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ if (mDataAuthorities != null) {
+ final int N = mDataAuthorities.size();
+ dest.writeInt(N);
+ for (int i=0; i<N; i++) {
+ mDataAuthorities.get(i).writeToParcel(dest);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ if (mDataPaths != null) {
+ final int N = mDataPaths.size();
+ dest.writeInt(N);
+ for (int i=0; i<N; i++) {
+ mDataPaths.get(i).writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mPriority);
+ dest.writeInt(mHasStaticPartialTypes ? 1 : 0);
+ dest.writeInt(mHasDynamicPartialTypes ? 1 : 0);
+ dest.writeInt(getAutoVerify() ? 1 : 0);
+ dest.writeInt(mInstantAppVisibility);
+ dest.writeInt(mOrder);
+ }
+
+ /**
+ * For debugging -- perform a check on the filter, return true if it passed
+ * or false if it failed.
+ *
+ * {@hide}
+ */
+ public boolean debugCheck() {
+ return true;
+
+ // This code looks for intent filters that do not specify data.
+ /*
+ if (mActions != null && mActions.size() == 1
+ && mActions.contains(Intent.ACTION_MAIN)) {
+ return true;
+ }
+
+ if (mDataTypes == null && mDataSchemes == null) {
+ Log.w("IntentFilter", "QUESTIONABLE INTENT FILTER:");
+ dump(Log.WARN, "IntentFilter", " ");
+ return false;
+ }
+
+ return true;
+ */
+ }
+
+ /** @hide */
+ public IntentFilter(Parcel source) {
+ mActions = new ArrayList<String>();
+ source.readStringList(mActions);
+ if (source.readInt() != 0) {
+ mCategories = new ArrayList<String>();
+ source.readStringList(mCategories);
+ }
+ if (source.readInt() != 0) {
+ mDataSchemes = new ArrayList<String>();
+ source.readStringList(mDataSchemes);
+ }
+ if (source.readInt() != 0) {
+ mStaticDataTypes = new ArrayList<String>();
+ source.readStringList(mStaticDataTypes);
+ }
+ if (source.readInt() != 0) {
+ mDataTypes = new ArrayList<String>();
+ source.readStringList(mDataTypes);
+ }
+ if (source.readInt() != 0) {
+ mMimeGroups = new ArrayList<String>();
+ source.readStringList(mMimeGroups);
+ }
+ int N = source.readInt();
+ if (N > 0) {
+ mDataSchemeSpecificParts = new ArrayList<PatternMatcher>(N);
+ for (int i=0; i<N; i++) {
+ mDataSchemeSpecificParts.add(new PatternMatcher(source));
+ }
+ }
+ N = source.readInt();
+ if (N > 0) {
+ mDataAuthorities = new ArrayList<AuthorityEntry>(N);
+ for (int i=0; i<N; i++) {
+ mDataAuthorities.add(new AuthorityEntry(source));
+ }
+ }
+ N = source.readInt();
+ if (N > 0) {
+ mDataPaths = new ArrayList<PatternMatcher>(N);
+ for (int i=0; i<N; i++) {
+ mDataPaths.add(new PatternMatcher(source));
+ }
+ }
+ mPriority = source.readInt();
+ mHasStaticPartialTypes = source.readInt() > 0;
+ mHasDynamicPartialTypes = source.readInt() > 0;
+ setAutoVerify(source.readInt() > 0);
+ setVisibilityToInstantApp(source.readInt());
+ mOrder = source.readInt();
+ }
+
+ private boolean hasPartialTypes() {
+ return mHasStaticPartialTypes || mHasDynamicPartialTypes;
+ }
+
+ private final boolean findMimeType(String type) {
+ final ArrayList<String> t = mDataTypes;
+
+ if (type == null) {
+ return false;
+ }
+
+ if (t.contains(type)) {
+ return true;
+ }
+
+ // Deal with an Intent wanting to match every type in the IntentFilter.
+ final int typeLength = type.length();
+ if (typeLength == 3 && type.equals("*/*")) {
+ return !t.isEmpty();
+ }
+
+ // Deal with this IntentFilter wanting to match every Intent type.
+ if (hasPartialTypes() && t.contains("*")) {
+ return true;
+ }
+
+ final int slashpos = type.indexOf('/');
+ if (slashpos > 0) {
+ if (hasPartialTypes() && t.contains(type.substring(0, slashpos))) {
+ return true;
+ }
+ if (typeLength == slashpos+2 && type.charAt(slashpos+1) == '*') {
+ // Need to look through all types for one that matches
+ // our base...
+ final int numTypes = t.size();
+ for (int i = 0; i < numTypes; i++) {
+ final String v = t.get(i);
+ if (type.regionMatches(0, v, 0, slashpos+1)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<String> getHostsList() {
+ ArrayList<String> result = new ArrayList<>();
+ Iterator<IntentFilter.AuthorityEntry> it = authoritiesIterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ IntentFilter.AuthorityEntry entry = it.next();
+ result.add(entry.getHost());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public String[] getHosts() {
+ ArrayList<String> list = getHostsList();
+ return list.toArray(new String[list.size()]);
+ }
+}
diff --git a/android/content/IntentSender.java b/android/content/IntentSender.java
new file mode 100644
index 0000000..b1252fd
--- /dev/null
+++ b/android/content/IntentSender.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2006 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.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.PendingIntentInfo;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.AndroidException;
+
+/**
+ * A description of an Intent and target action to perform with it.
+ * The returned object can be
+ * handed to other applications so that they can perform the action you
+ * described on your behalf at a later time.
+ *
+ * <p>By giving a IntentSender to another application,
+ * you are granting it the right to perform the operation you have specified
+ * as if the other application was yourself (with the same permissions and
+ * identity). As such, you should be careful about how you build the IntentSender:
+ * often, for example, the base Intent you supply will have the component
+ * name explicitly set to one of your own components, to ensure it is ultimately
+ * sent there and nowhere else.
+ *
+ * <p>A IntentSender itself is simply a reference to a token maintained by
+ * the system describing the original data used to retrieve it. This means
+ * that, even if its owning application's process is killed, the
+ * IntentSender itself will remain usable from other processes that
+ * have been given it. If the creating application later re-retrieves the
+ * same kind of IntentSender (same operation, same Intent action, data,
+ * categories, and components, and same flags), it will receive a IntentSender
+ * representing the same token if that is still valid.
+ *
+ * <p>Instances of this class can not be made directly, but rather must be
+ * created from an existing {@link android.app.PendingIntent} with
+ * {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
+ */
+public class IntentSender implements Parcelable {
+ @UnsupportedAppUsage
+ private final IIntentSender mTarget;
+ IBinder mWhitelistToken;
+
+ // cached pending intent information
+ private @Nullable PendingIntentInfo mCachedInfo;
+
+ /**
+ * Exception thrown when trying to send through a PendingIntent that
+ * has been canceled or is otherwise no longer able to execute the request.
+ */
+ public static class SendIntentException extends AndroidException {
+ public SendIntentException() {
+ }
+
+ public SendIntentException(String name) {
+ super(name);
+ }
+
+ public SendIntentException(Exception cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Callback interface for discovering when a send operation has
+ * completed. Primarily for use with a IntentSender that is
+ * performing a broadcast, this provides the same information as
+ * calling {@link Context#sendOrderedBroadcast(Intent, String,
+ * android.content.BroadcastReceiver, Handler, int, String, Bundle)
+ * Context.sendBroadcast()} with a final BroadcastReceiver.
+ */
+ public interface OnFinished {
+ /**
+ * Called when a send operation as completed.
+ *
+ * @param IntentSender The IntentSender this operation was sent through.
+ * @param intent The original Intent that was sent.
+ * @param resultCode The final result code determined by the send.
+ * @param resultData The final data collected by a broadcast.
+ * @param resultExtras The final extras collected by a broadcast.
+ */
+ void onSendFinished(IntentSender IntentSender, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras);
+ }
+
+ private static class FinishedDispatcher extends IIntentReceiver.Stub
+ implements Runnable {
+ private final IntentSender mIntentSender;
+ private final OnFinished mWho;
+ private final Handler mHandler;
+ private Intent mIntent;
+ private int mResultCode;
+ private String mResultData;
+ private Bundle mResultExtras;
+ FinishedDispatcher(IntentSender pi, OnFinished who, Handler handler) {
+ mIntentSender = pi;
+ mWho = who;
+ mHandler = handler;
+ }
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean serialized, boolean sticky, int sendingUser) {
+ mIntent = intent;
+ mResultCode = resultCode;
+ mResultData = data;
+ mResultExtras = extras;
+ if (mHandler == null) {
+ run();
+ } else {
+ mHandler.post(this);
+ }
+ }
+ public void run() {
+ mWho.onSendFinished(mIntentSender, mIntent, mResultCode,
+ mResultData, mResultExtras);
+ }
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be null if
+ * <var>intent</var> is also null.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use null to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or null for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If null, the callback will happen from the thread
+ * pool of the process.
+ *
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ */
+ public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler) throws SendIntentException {
+ sendIntent(context, code, intent, onFinished, handler, null);
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be null if
+ * <var>intent</var> is also null.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use null to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or null for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If null, the callback will happen from the thread
+ * pool of the process.
+ * @param requiredPermission Name of permission that a recipient of the PendingIntent
+ * is required to hold. This is only valid for broadcast intents, and
+ * corresponds to the permission argument in
+ * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
+ * If null, no permission is required.
+ *
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ */
+ public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler, String requiredPermission)
+ throws SendIntentException {
+ try {
+ String resolvedType = intent != null ?
+ intent.resolveTypeIfNeeded(context.getContentResolver())
+ : null;
+ int res = ActivityManager.getService().sendIntentSender(mTarget, mWhitelistToken,
+ code, intent, resolvedType,
+ onFinished != null
+ ? new FinishedDispatcher(this, onFinished, handler)
+ : null,
+ requiredPermission, null);
+ if (res < 0) {
+ throw new SendIntentException();
+ }
+ } catch (RemoteException e) {
+ throw new SendIntentException();
+ }
+ }
+
+ /**
+ * @deprecated Renamed to {@link #getCreatorPackage()}.
+ */
+ @Deprecated
+ public String getTargetPackage() {
+ return getCreatorPackage();
+ }
+
+ /**
+ * Return the package name of the application that created this
+ * IntentSender, that is the identity under which you will actually be
+ * sending the Intent. The returned string is supplied by the system, so
+ * that an application can not spoof its package.
+ *
+ * @return The package name of the PendingIntent, or null if there is
+ * none associated with it.
+ */
+ public String getCreatorPackage() {
+ return getCachedInfo().getCreatorPackage();
+ }
+
+ /**
+ * Return the uid of the application that created this
+ * PendingIntent, that is the identity under which you will actually be
+ * sending the Intent. The returned integer is supplied by the system, so
+ * that an application can not spoof its uid.
+ *
+ * @return The uid of the PendingIntent, or -1 if there is
+ * none associated with it.
+ */
+ public int getCreatorUid() {
+ return getCachedInfo().getCreatorUid();
+ }
+
+ /**
+ * Return the user handle of the application that created this
+ * PendingIntent, that is the user under which you will actually be
+ * sending the Intent. The returned UserHandle is supplied by the system, so
+ * that an application can not spoof its user. See
+ * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
+ * more explanation of user handles.
+ *
+ * @return The user handle of the PendingIntent, or null if there is
+ * none associated with it.
+ */
+ public UserHandle getCreatorUserHandle() {
+ int uid = getCachedInfo().getCreatorUid();
+ return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null;
+ }
+
+ /**
+ * Comparison operator on two IntentSender objects, such that true
+ * is returned then they both represent the same operation from the
+ * same package.
+ */
+ @Override
+ public boolean equals(@Nullable Object otherObj) {
+ if (otherObj instanceof IntentSender) {
+ return mTarget.asBinder().equals(((IntentSender)otherObj)
+ .mTarget.asBinder());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mTarget.asBinder().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("IntentSender{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(": ");
+ sb.append(mTarget != null ? mTarget.asBinder() : null);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mTarget.asBinder());
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IntentSender> CREATOR
+ = new Parcelable.Creator<IntentSender>() {
+ public IntentSender createFromParcel(Parcel in) {
+ IBinder target = in.readStrongBinder();
+ return target != null ? new IntentSender(target) : null;
+ }
+
+ public IntentSender[] newArray(int size) {
+ return new IntentSender[size];
+ }
+ };
+
+ /**
+ * Convenience function for writing either a IntentSender or null pointer to
+ * a Parcel. You must use this with {@link #readIntentSenderOrNullFromParcel}
+ * for later reading it.
+ *
+ * @param sender The IntentSender to write, or null.
+ * @param out Where to write the IntentSender.
+ */
+ public static void writeIntentSenderOrNullToParcel(IntentSender sender,
+ Parcel out) {
+ out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
+ : null);
+ }
+
+ /**
+ * Convenience function for reading either a Messenger or null pointer from
+ * a Parcel. You must have previously written the Messenger with
+ * {@link #writeIntentSenderOrNullToParcel}.
+ *
+ * @param in The Parcel containing the written Messenger.
+ *
+ * @return Returns the Messenger read from the Parcel, or null if null had
+ * been written.
+ */
+ public static IntentSender readIntentSenderOrNullFromParcel(Parcel in) {
+ IBinder b = in.readStrongBinder();
+ return b != null ? new IntentSender(b) : null;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public IIntentSender getTarget() {
+ return mTarget;
+ }
+
+ /** @hide */
+ public IBinder getWhitelistToken() {
+ return mWhitelistToken;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public IntentSender(IIntentSender target) {
+ mTarget = target;
+ }
+
+ /** @hide */
+ public IntentSender(IIntentSender target, IBinder whitelistToken) {
+ mTarget = target;
+ mWhitelistToken = whitelistToken;
+ }
+
+ /** @hide */
+ public IntentSender(IBinder target) {
+ mTarget = IIntentSender.Stub.asInterface(target);
+ }
+
+ private PendingIntentInfo getCachedInfo() {
+ if (mCachedInfo == null) {
+ try {
+ mCachedInfo = ActivityManager.getService().getInfoForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ return mCachedInfo;
+ }
+}
diff --git a/android/content/Loader.java b/android/content/Loader.java
new file mode 100644
index 0000000..b0555d4
--- /dev/null
+++ b/android/content/Loader.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2010 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.database.ContentObserver;
+import android.os.Handler;
+import android.util.DebugUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A class that performs asynchronous loading of data. While Loaders are active
+ * they should monitor the source of their data and deliver new results when the contents
+ * change. See {@link android.app.LoaderManager} for more detail.
+ *
+ * <p><b>Note on threading:</b> Clients of loaders should as a rule perform
+ * any calls on to a Loader from the main thread of their process (that is,
+ * the thread the Activity callbacks and other things occur on). Subclasses
+ * of Loader (such as {@link AsyncTaskLoader}) will often perform their work
+ * in a separate thread, but when delivering their results this too should
+ * be done on the main thread.</p>
+ *
+ * <p>Subclasses generally must implement at least {@link #onStartLoading()},
+ * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.</p>
+ *
+ * <p>Most implementations should not derive directly from this class, but
+ * instead inherit from {@link AsyncTaskLoader}.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using loaders, read the
+ * <a href="{@docRoot}guide/components/loaders.html">Loaders</a> developer guide.</p>
+ * </div>
+ *
+ * @param <D> The result returned when the load is complete
+ *
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ * {@link android.support.v4.content.Loader}
+ */
+@Deprecated
+public class Loader<D> {
+ int mId;
+ OnLoadCompleteListener<D> mListener;
+ OnLoadCanceledListener<D> mOnLoadCanceledListener;
+ Context mContext;
+ boolean mStarted = false;
+ boolean mAbandoned = false;
+ boolean mReset = true;
+ boolean mContentChanged = false;
+ boolean mProcessingChange = false;
+
+ /**
+ * An implementation of a ContentObserver that takes care of connecting
+ * it to the Loader to have the loader re-load its data when the observer
+ * is told it has changed. You do not normally need to use this yourself;
+ * it is used for you by {@link CursorLoader} to take care of executing
+ * an update when the cursor's backing data changes.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
+ */
+ @Deprecated
+ public final class ForceLoadContentObserver extends ContentObserver {
+ public ForceLoadContentObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onContentChanged();
+ }
+ }
+
+ /**
+ * Interface that is implemented to discover when a Loader has finished
+ * loading its data. You do not normally need to implement this yourself;
+ * it is used in the implementation of {@link android.app.LoaderManager}
+ * to find out when a Loader it is managing has completed so that this can
+ * be reported to its client. This interface should only be used if a
+ * Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
+ */
+ @Deprecated
+ public interface OnLoadCompleteListener<D> {
+ /**
+ * Called on the thread that created the Loader when the load is complete.
+ *
+ * @param loader the loader that completed the load
+ * @param data the result of the load
+ */
+ public void onLoadComplete(Loader<D> loader, D data);
+ }
+
+ /**
+ * Interface that is implemented to discover when a Loader has been canceled
+ * before it finished loading its data. You do not normally need to implement
+ * this yourself; it is used in the implementation of {@link android.app.LoaderManager}
+ * to find out when a Loader it is managing has been canceled so that it
+ * can schedule the next Loader. This interface should only be used if a
+ * Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
+ */
+ @Deprecated
+ public interface OnLoadCanceledListener<D> {
+ /**
+ * Called on the thread that created the Loader when the load is canceled.
+ *
+ * @param loader the loader that canceled the load
+ */
+ public void onLoadCanceled(Loader<D> loader);
+ }
+
+ /**
+ * Stores away the application context associated with context.
+ * Since Loaders can be used across multiple activities it's dangerous to
+ * store the context directly; always use {@link #getContext()} to retrieve
+ * the Loader's Context, don't use the constructor argument directly.
+ * The Context returned by {@link #getContext} is safe to use across
+ * Activity instances.
+ *
+ * @param context used to retrieve the application context.
+ */
+ public Loader(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ /**
+ * Sends the result of the load to the registered listener. Should only be called by subclasses.
+ *
+ * Must be called from the process's main thread.
+ *
+ * @param data the result of the load
+ */
+ public void deliverResult(D data) {
+ if (mListener != null) {
+ mListener.onLoadComplete(this, data);
+ }
+ }
+
+ /**
+ * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled.
+ * Should only be called by subclasses.
+ *
+ * Must be called from the process's main thread.
+ */
+ public void deliverCancellation() {
+ if (mOnLoadCanceledListener != null) {
+ mOnLoadCanceledListener.onLoadCanceled(this);
+ }
+ }
+
+ /**
+ * @return an application context retrieved from the Context passed to the constructor.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * @return the ID of this loader
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Registers a class that will receive callbacks when a load is complete.
+ * The callback will be called on the process's main thread so it's safe to
+ * pass the results to widgets.
+ *
+ * <p>Must be called from the process's main thread.
+ */
+ public void registerListener(int id, OnLoadCompleteListener<D> listener) {
+ if (mListener != null) {
+ throw new IllegalStateException("There is already a listener registered");
+ }
+ mListener = listener;
+ mId = id;
+ }
+
+ /**
+ * Remove a listener that was previously added with {@link #registerListener}.
+ *
+ * Must be called from the process's main thread.
+ */
+ public void unregisterListener(OnLoadCompleteListener<D> listener) {
+ if (mListener == null) {
+ throw new IllegalStateException("No listener register");
+ }
+ if (mListener != listener) {
+ throw new IllegalArgumentException("Attempting to unregister the wrong listener");
+ }
+ mListener = null;
+ }
+
+ /**
+ * Registers a listener that will receive callbacks when a load is canceled.
+ * The callback will be called on the process's main thread so it's safe to
+ * pass the results to widgets.
+ *
+ * Must be called from the process's main thread.
+ *
+ * @param listener The listener to register.
+ */
+ public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+ if (mOnLoadCanceledListener != null) {
+ throw new IllegalStateException("There is already a listener registered");
+ }
+ mOnLoadCanceledListener = listener;
+ }
+
+ /**
+ * Unregisters a listener that was previously added with
+ * {@link #registerOnLoadCanceledListener}.
+ *
+ * Must be called from the process's main thread.
+ *
+ * @param listener The listener to unregister.
+ */
+ public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+ if (mOnLoadCanceledListener == null) {
+ throw new IllegalStateException("No listener register");
+ }
+ if (mOnLoadCanceledListener != listener) {
+ throw new IllegalArgumentException("Attempting to unregister the wrong listener");
+ }
+ mOnLoadCanceledListener = null;
+ }
+
+ /**
+ * Return whether this load has been started. That is, its {@link #startLoading()}
+ * has been called and no calls to {@link #stopLoading()} or
+ * {@link #reset()} have yet been made.
+ */
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ /**
+ * Return whether this loader has been abandoned. In this state, the
+ * loader <em>must not</em> report any new data, and <em>must</em> keep
+ * its last reported data valid until it is finally reset.
+ */
+ public boolean isAbandoned() {
+ return mAbandoned;
+ }
+
+ /**
+ * Return whether this load has been reset. That is, either the loader
+ * has not yet been started for the first time, or its {@link #reset()}
+ * has been called.
+ */
+ public boolean isReset() {
+ return mReset;
+ }
+
+ /**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when the associated fragment/activity
+ * is being started. When using a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
+ * Starts an asynchronous load of the Loader's data. When the result
+ * is ready the callbacks will be called on the process's main thread.
+ * If a previous load has been completed and is still valid
+ * the result may be passed to the callbacks immediately.
+ * The loader will monitor the source of
+ * the data set and may deliver future callbacks if the source changes.
+ * Calling {@link #stopLoading} will stop the delivery of callbacks.
+ *
+ * <p>This updates the Loader's internal state so that
+ * {@link #isStarted()} and {@link #isReset()} will return the correct
+ * values, and then calls the implementation's {@link #onStartLoading()}.
+ *
+ * <p>Must be called from the process's main thread.
+ */
+ public final void startLoading() {
+ mStarted = true;
+ mReset = false;
+ mAbandoned = false;
+ onStartLoading();
+ }
+
+ /**
+ * Subclasses must implement this to take care of loading their data,
+ * as per {@link #startLoading()}. This is not called by clients directly,
+ * but as a result of a call to {@link #startLoading()}.
+ */
+ protected void onStartLoading() {
+ }
+
+ /**
+ * Attempt to cancel the current load task.
+ * Must be called on the main thread of the process.
+ *
+ * <p>Cancellation is not an immediate operation, since the load is performed
+ * in a background thread. If there is currently a load in progress, this
+ * method requests that the load be canceled, and notes this is the case;
+ * once the background thread has completed its work its remaining state
+ * will be cleared. If another load request comes in during this time,
+ * it will be held until the canceled load is complete.
+ *
+ * @return Returns <tt>false</tt> if the task could not be canceled,
+ * typically because it has already completed normally, or
+ * because {@link #startLoading()} hasn't been called; returns
+ * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task
+ * is still running and the {@link OnLoadCanceledListener} will be called
+ * when the task completes.
+ */
+ public boolean cancelLoad() {
+ return onCancelLoad();
+ }
+
+ /**
+ * Subclasses must implement this to take care of requests to {@link #cancelLoad()}.
+ * This will always be called from the process's main thread.
+ *
+ * @return Returns <tt>false</tt> if the task could not be canceled,
+ * typically because it has already completed normally, or
+ * because {@link #startLoading()} hasn't been called; returns
+ * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task
+ * is still running and the {@link OnLoadCanceledListener} will be called
+ * when the task completes.
+ */
+ protected boolean onCancelLoad() {
+ return false;
+ }
+
+ /**
+ * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
+ * loaded data set and load a new one. This simply calls through to the
+ * implementation's {@link #onForceLoad()}. You generally should only call this
+ * when the loader is started -- that is, {@link #isStarted()} returns true.
+ *
+ * <p>Must be called from the process's main thread.
+ */
+ public void forceLoad() {
+ onForceLoad();
+ }
+
+ /**
+ * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
+ * This will always be called from the process's main thread.
+ */
+ protected void onForceLoad() {
+ }
+
+ /**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when the associated fragment/activity
+ * is being stopped. When using a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
+ * <p>Stops delivery of updates until the next time {@link #startLoading()} is called.
+ * Implementations should <em>not</em> invalidate their data at this point --
+ * clients are still free to use the last data the loader reported. They will,
+ * however, typically stop reporting new data if the data changes; they can
+ * still monitor for changes, but must not report them to the client until and
+ * if {@link #startLoading()} is later called.
+ *
+ * <p>This updates the Loader's internal state so that
+ * {@link #isStarted()} will return the correct
+ * value, and then calls the implementation's {@link #onStopLoading()}.
+ *
+ * <p>Must be called from the process's main thread.
+ */
+ public void stopLoading() {
+ mStarted = false;
+ onStopLoading();
+ }
+
+ /**
+ * Subclasses must implement this to take care of stopping their loader,
+ * as per {@link #stopLoading()}. This is not called by clients directly,
+ * but as a result of a call to {@link #stopLoading()}.
+ * This will always be called from the process's main thread.
+ */
+ protected void onStopLoading() {
+ }
+
+ /**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when restarting a Loader. When using
+ * a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
+ * Tell the Loader that it is being abandoned. This is called prior
+ * to {@link #reset} to have it retain its current data but not report
+ * any new data.
+ */
+ public void abandon() {
+ mAbandoned = true;
+ onAbandon();
+ }
+
+ /**
+ * Subclasses implement this to take care of being abandoned. This is
+ * an optional intermediate state prior to {@link #onReset()} -- it means that
+ * the client is no longer interested in any new data from the loader,
+ * so the loader must not report any further updates. However, the
+ * loader <em>must</em> keep its last reported data valid until the final
+ * {@link #onReset()} happens. You can retrieve the current abandoned
+ * state with {@link #isAbandoned}.
+ */
+ protected void onAbandon() {
+ }
+
+ /**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when destroying a Loader. When using
+ * a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
+ * Resets the state of the Loader. The Loader should at this point free
+ * all of its resources, since it may never be called again; however, its
+ * {@link #startLoading()} may later be called at which point it must be
+ * able to start running again.
+ *
+ * <p>This updates the Loader's internal state so that
+ * {@link #isStarted()} and {@link #isReset()} will return the correct
+ * values, and then calls the implementation's {@link #onReset()}.
+ *
+ * <p>Must be called from the process's main thread.
+ */
+ public void reset() {
+ onReset();
+ mReset = true;
+ mStarted = false;
+ mAbandoned = false;
+ mContentChanged = false;
+ mProcessingChange = false;
+ }
+
+ /**
+ * Subclasses must implement this to take care of resetting their loader,
+ * as per {@link #reset()}. This is not called by clients directly,
+ * but as a result of a call to {@link #reset()}.
+ * This will always be called from the process's main thread.
+ */
+ protected void onReset() {
+ }
+
+ /**
+ * Take the current flag indicating whether the loader's content had
+ * changed while it was stopped. If it had, true is returned and the
+ * flag is cleared.
+ */
+ public boolean takeContentChanged() {
+ boolean res = mContentChanged;
+ mContentChanged = false;
+ mProcessingChange |= res;
+ return res;
+ }
+
+ /**
+ * Commit that you have actually fully processed a content change that
+ * was returned by {@link #takeContentChanged}. This is for use with
+ * {@link #rollbackContentChanged()} to handle situations where a load
+ * is cancelled. Call this when you have completely processed a load
+ * without it being cancelled.
+ */
+ public void commitContentChanged() {
+ mProcessingChange = false;
+ }
+
+ /**
+ * Report that you have abandoned the processing of a content change that
+ * was returned by {@link #takeContentChanged()} and would like to rollback
+ * to the state where there is again a pending content change. This is
+ * to handle the case where a data load due to a content change has been
+ * canceled before its data was delivered back to the loader.
+ */
+ public void rollbackContentChanged() {
+ if (mProcessingChange) {
+ onContentChanged();
+ }
+ }
+
+ /**
+ * Called when {@link ForceLoadContentObserver} detects a change. The
+ * default implementation checks to see if the loader is currently started;
+ * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
+ * so that {@link #takeContentChanged()} returns true.
+ *
+ * <p>Must be called from the process's main thread.
+ */
+ public void onContentChanged() {
+ if (mStarted) {
+ forceLoad();
+ } else {
+ // This loader has been stopped, so we don't want to load
+ // new data right now... but keep track of it changing to
+ // refresh later if we start again.
+ mContentChanged = true;
+ }
+ }
+
+ /**
+ * For debugging, converts an instance of the Loader's data class to
+ * a string that can be printed. Must handle a null data.
+ */
+ public String dataToString(D data) {
+ StringBuilder sb = new StringBuilder(64);
+ DebugUtils.buildShortClassTag(data, sb);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ DebugUtils.buildShortClassTag(this, sb);
+ sb.append(" id=");
+ sb.append(mId);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Print the Loader's state into the given stream.
+ *
+ * @param prefix Text to print at the front of each line.
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer A PrintWriter to which the dump is to be set.
+ * @param args Additional arguments to the dump request.
+ */
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.print(prefix); writer.print("mId="); writer.print(mId);
+ writer.print(" mListener="); writer.println(mListener);
+ if (mStarted || mContentChanged || mProcessingChange) {
+ writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
+ writer.print(" mContentChanged="); writer.print(mContentChanged);
+ writer.print(" mProcessingChange="); writer.println(mProcessingChange);
+ }
+ if (mAbandoned || mReset) {
+ writer.print(prefix); writer.print("mAbandoned="); writer.print(mAbandoned);
+ writer.print(" mReset="); writer.println(mReset);
+ }
+ }
+}
diff --git a/android/content/LocusId.java b/android/content/LocusId.java
new file mode 100644
index 0000000..a04fbd1
--- /dev/null
+++ b/android/content/LocusId.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.contentcapture.ContentCaptureManager;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+
+/**
+ * An identifier for an unique state (locus) in the application. Should be stable across reboots and
+ * backup / restore.
+ *
+ * <p>Locus is a new concept introduced on
+ * {@link android.os.Build.VERSION_CODES#Q Android Q} and it lets the intelligence service provided
+ * by the Android System to correlate state between different subsystems such as content capture,
+ * shortcuts, and notifications.
+ *
+ * <p>For example, if your app provides an activity representing a chat between 2 users
+ * (say {@code A} and {@code B}, this chat state could be represented by:
+ *
+ * <pre><code>
+ * LocusId chatId = new LocusId("Chat_A_B");
+ * </code></pre>
+ *
+ * <p>And then you should use that {@code chatId} by:
+ *
+ * <ul>
+ * <li>Setting it in the chat notification (through
+ * {@link android.app.Notification.Builder#setLocusId(LocusId)
+ * Notification.Builder.setLocusId(chatId)}).
+ * <li>Setting it into the {@link android.content.pm.ShortcutInfo} (through
+ * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(LocusId)
+ * ShortcutInfo.Builder.setLocusId(chatId)}), if you provide a launcher shortcut for that chat
+ * conversation.
+ * <li>Associating it with the {@link android.view.contentcapture.ContentCaptureContext} of the
+ * root view of the chat conversation activity (through
+ * {@link android.view.View#getContentCaptureSession()}, then
+ * {@link android.view.contentcapture.ContentCaptureContext.Builder
+ * new ContentCaptureContext.Builder(chatId).build()} and
+ * {@link android.view.contentcapture.ContentCaptureSession#setContentCaptureContext(
+ * android.view.contentcapture.ContentCaptureContext)} - see {@link ContentCaptureManager}
+ * for more info about content capture).
+ * <li>Configuring your app to launch the chat conversation through the
+ * {@link Intent#ACTION_VIEW_LOCUS} intent.
+ * </ul>
+ */
+public final class LocusId implements Parcelable {
+
+ private final String mId;
+
+ /**
+ * Default constructor.
+ *
+ * @throws IllegalArgumentException if {@code id} is empty or {@code null}.
+ */
+ public LocusId(@NonNull String id) {
+ mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
+ }
+
+ /**
+ * Gets the canonical {@code id} associated with the locus.
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ final LocusId other = (LocusId) obj;
+ if (mId == null) {
+ if (other.mId != null) return false;
+ } else {
+ if (!mId.equals(other.mId)) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "LocusId[" + getSanitizedId() + "]";
+ }
+
+ /** @hide */
+ public void dump(@NonNull PrintWriter pw) {
+ pw.print("id:"); pw.println(getSanitizedId());
+ }
+
+ @NonNull
+ private String getSanitizedId() {
+ final int size = mId.length();
+ return size + "_chars";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mId);
+ }
+
+ public static final @NonNull Parcelable.Creator<LocusId> CREATOR =
+ new Parcelable.Creator<LocusId>() {
+
+ @NonNull
+ @Override
+ public LocusId createFromParcel(Parcel parcel) {
+ return new LocusId(parcel.readString());
+ }
+
+ @NonNull
+ @Override
+ public LocusId[] newArray(int size) {
+ return new LocusId[size];
+ }
+ };
+}
diff --git a/android/content/LoggingContentInterface.java b/android/content/LoggingContentInterface.java
new file mode 100644
index 0000000..3bd0832
--- /dev/null
+++ b/android/content/LoggingContentInterface.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2019 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.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Instance of {@link ContentInterface} that logs all inputs and outputs while
+ * delegating to another {@link ContentInterface}.
+ *
+ * @hide
+ */
+public class LoggingContentInterface implements ContentInterface {
+ private final String tag;
+ private final ContentInterface delegate;
+
+ public LoggingContentInterface(String tag, ContentInterface delegate) {
+ this.tag = tag;
+ this.delegate = delegate;
+ }
+
+ private class Logger implements AutoCloseable {
+ private final StringBuilder sb = new StringBuilder();
+
+ public Logger(String method, Object... args) {
+ // First, force-unparcel any bundles so we can log them
+ for (Object arg : args) {
+ if (arg instanceof Bundle) {
+ ((Bundle) arg).size();
+ }
+ }
+
+ sb.append("callingUid=").append(Binder.getCallingUid()).append(' ');
+ sb.append(method);
+ sb.append('(').append(deepToString(args)).append(')');
+ }
+
+ private String deepToString(Object value) {
+ if (value != null && value.getClass().isArray()) {
+ return Arrays.deepToString((Object[]) value);
+ } else {
+ return String.valueOf(value);
+ }
+ }
+
+ public <T> T setResult(T res) {
+ if (res instanceof Cursor) {
+ sb.append('\n');
+ DatabaseUtils.dumpCursor((Cursor) res, sb);
+ } else {
+ sb.append(" = ").append(deepToString(res));
+ }
+ return res;
+ }
+
+ @Override
+ public void close() {
+ Log.v(tag, sb.toString());
+ }
+ }
+
+ @Override
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
+ throws RemoteException {
+ try (Logger l = new Logger("query", uri, projection, queryArgs, cancellationSignal)) {
+ try {
+ return l.setResult(delegate.query(uri, projection, queryArgs, cancellationSignal));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable String getType(@NonNull Uri uri) throws RemoteException {
+ try (Logger l = new Logger("getType", uri)) {
+ try {
+ return l.setResult(delegate.getType(uri));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter)
+ throws RemoteException {
+ try (Logger l = new Logger("getStreamTypes", uri, mimeTypeFilter)) {
+ try {
+ return l.setResult(delegate.getStreamTypes(uri, mimeTypeFilter));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable Uri canonicalize(@NonNull Uri uri) throws RemoteException {
+ try (Logger l = new Logger("canonicalize", uri)) {
+ try {
+ return l.setResult(delegate.canonicalize(uri));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException {
+ try (Logger l = new Logger("uncanonicalize", uri)) {
+ try {
+ return l.setResult(delegate.uncanonicalize(uri));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public boolean refresh(@NonNull Uri uri, @Nullable Bundle args,
+ @Nullable CancellationSignal cancellationSignal) throws RemoteException {
+ try (Logger l = new Logger("refresh", uri, args, cancellationSignal)) {
+ try {
+ return l.setResult(delegate.refresh(uri, args, cancellationSignal));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
+ throws RemoteException {
+ try (Logger l = new Logger("checkUriPermission", uri, uid, modeFlags)) {
+ try {
+ return l.setResult(delegate.checkUriPermission(uri, uid, modeFlags));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues,
+ @Nullable Bundle extras) throws RemoteException {
+ try (Logger l = new Logger("insert", uri, initialValues, extras)) {
+ try {
+ return l.setResult(delegate.insert(uri, initialValues, extras));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] initialValues)
+ throws RemoteException {
+ try (Logger l = new Logger("bulkInsert", uri, initialValues)) {
+ try {
+ return l.setResult(delegate.bulkInsert(uri, initialValues));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable Bundle extras) throws RemoteException {
+ try (Logger l = new Logger("delete", uri, extras)) {
+ try {
+ return l.setResult(delegate.delete(uri, extras));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable Bundle extras)
+ throws RemoteException {
+ try (Logger l = new Logger("update", uri, values, extras)) {
+ try {
+ return l.setResult(delegate.update(uri, values, extras));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ try (Logger l = new Logger("openFile", uri, mode, signal)) {
+ try {
+ return l.setResult(delegate.openFile(uri, mode, signal));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ try (Logger l = new Logger("openAssetFile", uri, mode, signal)) {
+ try {
+ return l.setResult(delegate.openAssetFile(uri, mode, signal));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+ @NonNull String mimeTypeFilter, @Nullable Bundle opts,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ try (Logger l = new Logger("openTypedAssetFile", uri, mimeTypeFilter, opts, signal)) {
+ try {
+ return l.setResult(delegate.openTypedAssetFile(uri, mimeTypeFilter, opts, signal));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ try (Logger l = new Logger("applyBatch", authority, operations)) {
+ try {
+ return l.setResult(delegate.applyBatch(authority, operations));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
+ public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
+ @Nullable String arg, @Nullable Bundle extras) throws RemoteException {
+ try (Logger l = new Logger("call", authority, method, arg, extras)) {
+ try {
+ return l.setResult(delegate.call(authority, method, arg, extras));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+}
diff --git a/android/content/MimeTypeFilter.java b/android/content/MimeTypeFilter.java
new file mode 100644
index 0000000..1c26fd9
--- /dev/null
+++ b/android/content/MimeTypeFilter.java
@@ -0,0 +1,154 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Provides utility methods for matching MIME type filters used in ContentProvider.
+ *
+ * <p>Wildcards are allowed only instead of the entire type or subtype with a tree prefix.
+ * Eg. image\/*, *\/* is a valid filter and will match image/jpeg, but image/j* is invalid and
+ * it will not match image/jpeg. Suffixes and parameters are not supported, and they are treated
+ * as part of the subtype during matching. Neither type nor subtype can be empty.
+ *
+ * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike the formal
+ * RFC definitions. As a result, you should always write these elements with lower case letters,
+ * or use {@link android.content.Intent#normalizeMimeType} to ensure that they are converted to
+ * lower case.</em>
+ *
+ * <p>MIME types can be null or ill-formatted. In such case they won't match anything.
+ *
+ * <p>MIME type filters must be correctly formatted, or an exception will be thrown.
+ * Copied from support library.
+ * {@hide}
+ */
+public final class MimeTypeFilter {
+
+ private MimeTypeFilter() {
+ }
+
+ private static boolean mimeTypeAgainstFilter(
+ @NonNull String[] mimeTypeParts, @NonNull String[] filterParts) {
+ if (filterParts.length != 2) {
+ throw new IllegalArgumentException(
+ "Ill-formatted MIME type filter. Must be type/subtype.");
+ }
+ if (filterParts[0].isEmpty() || filterParts[1].isEmpty()) {
+ throw new IllegalArgumentException(
+ "Ill-formatted MIME type filter. Type or subtype empty.");
+ }
+ if (mimeTypeParts.length != 2) {
+ return false;
+ }
+ if (!"*".equals(filterParts[0])
+ && !filterParts[0].equals(mimeTypeParts[0])) {
+ return false;
+ }
+ if (!"*".equals(filterParts[1])
+ && !filterParts[1].equals(mimeTypeParts[1])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches one nullable MIME type against one MIME type filter.
+ * @return True if the {@code mimeType} matches the {@code filter}.
+ */
+ public static boolean matches(@Nullable String mimeType, @NonNull String filter) {
+ if (mimeType == null) {
+ return false;
+ }
+
+ final String[] mimeTypeParts = mimeType.split("/");
+ final String[] filterParts = filter.split("/");
+
+ return mimeTypeAgainstFilter(mimeTypeParts, filterParts);
+ }
+
+ /**
+ * Matches one nullable MIME type against an array of MIME type filters.
+ * @return The first matching filter, or null if nothing matches.
+ */
+ @Nullable
+ public static String matches(
+ @Nullable String mimeType, @NonNull String[] filters) {
+ if (mimeType == null) {
+ return null;
+ }
+
+ final String[] mimeTypeParts = mimeType.split("/");
+ for (String filter : filters) {
+ final String[] filterParts = filter.split("/");
+ if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+ return filter;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Matches multiple MIME types against an array of MIME type filters.
+ * @return The first matching MIME type, or null if nothing matches.
+ */
+ @Nullable
+ public static String matches(
+ @Nullable String[] mimeTypes, @NonNull String filter) {
+ if (mimeTypes == null) {
+ return null;
+ }
+
+ final String[] filterParts = filter.split("/");
+ for (String mimeType : mimeTypes) {
+ final String[] mimeTypeParts = mimeType.split("/");
+ if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+ return mimeType;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Matches multiple MIME types against an array of MIME type filters.
+ * @return The list of matching MIME types, or empty array if nothing matches.
+ */
+ @NonNull
+ public static String[] matchesMany(
+ @Nullable String[] mimeTypes, @NonNull String filter) {
+ if (mimeTypes == null) {
+ return new String[] {};
+ }
+
+ final ArrayList<String> list = new ArrayList<>();
+ final String[] filterParts = filter.split("/");
+ for (String mimeType : mimeTypes) {
+ final String[] mimeTypeParts = mimeType.split("/");
+ if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+ list.add(mimeType);
+ }
+ }
+
+ return list.toArray(new String[list.size()]);
+ }
+}
diff --git a/android/content/MutableContextWrapper.java b/android/content/MutableContextWrapper.java
new file mode 100644
index 0000000..820479c
--- /dev/null
+++ b/android/content/MutableContextWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Special version of {@link ContextWrapper} that allows the base context to
+ * be modified after it is initially set.
+ */
+public class MutableContextWrapper extends ContextWrapper {
+ public MutableContextWrapper(Context base) {
+ super(base);
+ }
+
+ /**
+ * Change the base context for this ContextWrapper. All calls will then be
+ * delegated to the base context. Unlike ContextWrapper, the base context
+ * can be changed even after one is already set.
+ *
+ * @param base The new base context for this wrapper.
+ */
+ public void setBaseContext(Context base) {
+ mBase = base;
+ }
+}
diff --git a/android/content/OperationApplicationException.java b/android/content/OperationApplicationException.java
new file mode 100644
index 0000000..2fc19bb
--- /dev/null
+++ b/android/content/OperationApplicationException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * Thrown when an application of a {@link ContentProviderOperation} fails due the specified
+ * constraints.
+ */
+public class OperationApplicationException extends Exception {
+ private final int mNumSuccessfulYieldPoints;
+
+ public OperationApplicationException() {
+ super();
+ mNumSuccessfulYieldPoints = 0;
+ }
+ public OperationApplicationException(String message) {
+ super(message);
+ mNumSuccessfulYieldPoints = 0;
+ }
+ public OperationApplicationException(String message, Throwable cause) {
+ super(message, cause);
+ mNumSuccessfulYieldPoints = 0;
+ }
+ public OperationApplicationException(Throwable cause) {
+ super(cause);
+ mNumSuccessfulYieldPoints = 0;
+ }
+ public OperationApplicationException(int numSuccessfulYieldPoints) {
+ super();
+ mNumSuccessfulYieldPoints = numSuccessfulYieldPoints;
+ }
+ public OperationApplicationException(String message, int numSuccessfulYieldPoints) {
+ super(message);
+ mNumSuccessfulYieldPoints = numSuccessfulYieldPoints;
+ }
+
+ public int getNumSuccessfulYieldPoints() {
+ return mNumSuccessfulYieldPoints;
+ }
+}
diff --git a/android/content/PeriodicSync.java b/android/content/PeriodicSync.java
new file mode 100644
index 0000000..432e81b
--- /dev/null
+++ b/android/content/PeriodicSync.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 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.accounts.Account;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Value type that contains information about a periodic sync.
+ */
+public class PeriodicSync implements Parcelable {
+ /** The account to be synced. Can be null. */
+ public final Account account;
+ /** The authority of the sync. Can be null. */
+ public final String authority;
+ /** Any extras that parameters that are to be passed to the sync adapter. */
+ public final Bundle extras;
+ /** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */
+ public final long period;
+ /**
+ * How much flexibility can be taken in scheduling the sync, in seconds.
+ * {@hide}
+ */
+ public final long flexTime;
+
+ /**
+ * Creates a new PeriodicSync, copying the Bundle. This constructor is no longer used.
+ */
+ public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) {
+ this.account = account;
+ this.authority = authority;
+ if (extras == null) {
+ this.extras = new Bundle();
+ } else {
+ this.extras = new Bundle(extras);
+ }
+ this.period = periodInSeconds;
+ // Old API uses default flex time. No-one should be using this ctor anyway.
+ this.flexTime = 0L;
+ }
+
+ /**
+ * Create a copy of a periodic sync.
+ * {@hide}
+ */
+ public PeriodicSync(PeriodicSync other) {
+ this.account = other.account;
+ this.authority = other.authority;
+ this.extras = new Bundle(other.extras);
+ this.period = other.period;
+ this.flexTime = other.flexTime;
+ }
+
+ /**
+ * A PeriodicSync for a sync with a specified provider.
+ * {@hide}
+ */
+ public PeriodicSync(Account account, String authority, Bundle extras,
+ long period, long flexTime) {
+ this.account = account;
+ this.authority = authority;
+ this.extras = new Bundle(extras);
+ this.period = period;
+ this.flexTime = flexTime;
+ }
+
+ private PeriodicSync(Parcel in) {
+ this.account = in.readParcelable(null);
+ this.authority = in.readString();
+ this.extras = in.readBundle();
+ this.period = in.readLong();
+ this.flexTime = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(account, flags);
+ dest.writeString(authority);
+ dest.writeBundle(extras);
+ dest.writeLong(period);
+ dest.writeLong(flexTime);
+ }
+
+ public static final @android.annotation.NonNull Creator<PeriodicSync> CREATOR = new Creator<PeriodicSync>() {
+ @Override
+ public PeriodicSync createFromParcel(Parcel source) {
+ return new PeriodicSync(source);
+ }
+
+ @Override
+ public PeriodicSync[] newArray(int size) {
+ return new PeriodicSync[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof PeriodicSync)) {
+ return false;
+ }
+ final PeriodicSync other = (PeriodicSync) o;
+ return account.equals(other.account)
+ && authority.equals(other.authority)
+ && period == other.period
+ && syncExtrasEquals(extras, other.extras);
+ }
+
+ /**
+ * Periodic sync extra comparison function.
+ * {@hide}
+ */
+ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
+ if (b1.size() != b2.size()) {
+ return false;
+ }
+ if (b1.isEmpty()) {
+ return true;
+ }
+ for (String key : b1.keySet()) {
+ if (!b2.containsKey(key)) {
+ return false;
+ }
+ // Null check. According to ContentResolver#validateSyncExtrasBundle null-valued keys
+ // are allowed in the bundle.
+ if (!Objects.equals(b1.get(key), b2.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "account: " + account +
+ ", authority: " + authority +
+ ". period: " + period + "s " +
+ ", flex: " + flexTime;
+ }
+}
diff --git a/android/content/PermissionChecker.java b/android/content/PermissionChecker.java
new file mode 100644
index 0000000..a167cb3
--- /dev/null
+++ b/android/content/PermissionChecker.java
@@ -0,0 +1,814 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.Binder;
+import android.os.Process;
+import android.permission.IPermissionChecker;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionCheckerManager.PermissionResult;
+
+/**
+ * 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>
+ * <p>
+ * This class has two types of methods and you should be careful which
+ * type to call based on whether permission protected data is being
+ * passed to the app or you are just checking whether the app holds a
+ * permission. The reason is that a permission check requires checking
+ * the runtime permission and if it is granted checking the corresponding
+ * app op as for apps not supporting the runtime mode we never revoke
+ * permissions but disable app ops. Since there are two types of app op
+ * checks, one that does not leave a record an action was performed and
+ * another the does, one needs to call the preflight flavor of the checks
+ * named xxxForPreflight only if no private data is being delivered but
+ * a permission check is what is needed and the xxxForDataDelivery where
+ * the permission check is right before private data delivery.
+ *
+ * @hide
+ */
+public final class PermissionChecker {
+ /**
+ * The permission is granted.
+ *
+ * @hide
+ */
+ public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
+
+ /**
+ * The permission is denied. Applicable only to runtime and app op permissions.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the runtime permission is granted, but the corresponding app op is denied
+ * for runtime permissions.</li>
+ * <li>the app ops is ignored for app op permissions.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int PERMISSION_SOFT_DENIED =
+ PermissionCheckerManager.PERMISSION_SOFT_DENIED;
+
+ /**
+ * The permission is denied.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the permission is denied for non app op permissions.</li>
+ * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
+ * and permission is denied.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int PERMISSION_HARD_DENIED =
+ PermissionCheckerManager.PERMISSION_HARD_DENIED;
+
+ /** Constant when the PID for which we check permissions is unknown. */
+ public static final int PID_UNKNOWN = -1;
+
+ private static volatile IPermissionChecker sService;
+
+ 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.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * <p>For more details how to determine the {@code packageName}, {@code attributionTag}, and
+ * {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @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.
+ * @param attributionTag attribution tag
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ * @param startDataDelivery Whether this is the start of data delivery.
+ *
+ * @see #checkPermissionForPreflight(Context, String, int, int, String)
+ */
+ @PermissionCheckerManager.PermissionResult
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, int pid, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
+ return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
+ packageName, attributionTag), message, startDataDelivery);
+ }
+
+ /**
+ * 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.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * <p>For more details how to determine the {@code packageName}, {@code attributionTag}, and
+ * {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @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.
+ * @param attributionTag attribution tag
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkPermissionForPreflight(Context, String, int, int, String)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, int pid, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message) {
+ return checkPermissionForDataDelivery(context, permission, pid, uid,
+ packageName, attributionTag, message, false /*startDataDelivery*/);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed. Call this method if you are the datasource which would not blame you for
+ * access to the data since you are the data.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param attributionSource the permission identity
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
+ message, false /*startDataDelivery*/, /*fromDatasource*/ true);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String, AttributionSource)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param attributionSource the permission identity
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return checkPermissionForDataDelivery(context, permission, pid, attributionSource,
+ message, false /*startDataDelivery*/);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the required
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String,
+ * AttributionSource)}
+ * to determine if the app has or may have permission and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
+ * is not known.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @param startDataDelivery Whether this is the start of data delivery.
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean startDataDelivery) {
+ return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
+ message, startDataDelivery, /*fromDatasource*/ false);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
+ return context.getSystemService(PermissionCheckerManager.class).checkPermission(permission,
+ attributionSource.asState(), message, true /*forDataDelivery*/, startDataDelivery,
+ fromDatasource, AppOpsManager.OP_NONE);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed. The app ops area also marked as started. This is useful for long running
+ * permissions like camera.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the required
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkPermissionForPreflight(Context, String,
+ * AttributionSource)}
+ * to determine if the app has or may have permission and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForPreflight(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ @SuppressWarnings("ConstantConditions")
+ public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return context.getSystemService(PermissionCheckerManager.class).checkPermission(
+ permission, attributionSource.asState(), message, true /*forDataDelivery*/,
+ /*startDataDelivery*/ true, /*fromDatasource*/ false, AppOpsManager.OP_NONE);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link
+ * AttributionSource} has a given app op allowed and marks the op as started.
+ *
+ * <strong>NOTE:</strong> Use this method only for app op checks at the
+ * point where you will deliver the protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the data
+ * op but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkOpForPreflight(Context, String, AttributionSource, String)}
+ * to determine if the app has or may have op access and this check will not
+ * leave a trace that op protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the op access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param opName THe op to start.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #finishDataDelivery(Context, String, AttributionSource)
+ */
+ @PermissionResult
+ @SuppressWarnings("ConstantConditions")
+ public static int startOpForDataDelivery(@NonNull Context context,
+ @NonNull String opName, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, true /*startDataDelivery*/);
+ }
+
+ /**
+ * Finishes an ongoing op for data access chain described by the given {@link
+ * AttributionSource}.
+ *
+ * @param context Context for accessing resources.
+ * @param op The op to finish.
+ * @param attributionSource The identity for which finish op.
+ *
+ * @see #startOpForDataDelivery(Context, String, AttributionSource, String)
+ * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
+ */
+ @SuppressWarnings("ConstantConditions")
+ public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
+ @NonNull AttributionSource attributionSource) {
+ context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+ AppOpsManager.strOpToOp(op), attributionSource.asState(),
+ /*fromDatasource*/ false);
+ }
+
+ /**
+ * Finishes an ongoing op for data access chain described by the given {@link
+ * AttributionSource}. Call this method if you are the datasource which would
+ * not finish an op for your attribution source as it was not started.
+ *
+ * @param context Context for accessing resources.
+ * @param op The op to finish.
+ * @param attributionSource The identity for which finish op.
+ *
+ * @see #startOpForDataDelivery(Context, String, AttributionSource, String)
+ * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
+ */
+ @SuppressWarnings("ConstantConditions")
+ public static void finishDataDeliveryFromDatasource(@NonNull Context context,
+ @NonNull String op, @NonNull AttributionSource attributionSource) {
+ context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+ AppOpsManager.strOpToOp(op), attributionSource.asState(),
+ /*fromDatasource*/ true);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link
+ * AttributionSource} has a given app op allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for op checks at the
+ * preflight point where you will not deliver the protected data
+ * to clients but schedule a data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a data listener it should have the op
+ * but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have data
+ * access and this check will not leave a trace that protected data
+ * was delivered. When you are about to deliver the data to a registered
+ * listener you should use {@link #checkOpForDataDelivery(Context, String,
+ * AttributionSource, String)} which will evaluate the op access based
+ * on the current fg/bg state of the app and leave a record that the data was
+ * accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param opName The op to check.
+ * @param attributionSource The identity for which to check the permission.
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkOpForDataDelivery(Context, String, AttributionSource, String)
+ */
+ @PermissionResult
+ @SuppressWarnings("ConstantConditions")
+ public static int checkOpForPreflight(@NonNull Context context,
+ @NonNull String opName, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ false /*forDataDelivery*/, false /*startDataDelivery*/);
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has an allowed app op.
+ *
+ * <strong>NOTE:</strong> Use this method only for op checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a data listener it should have the data
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkOpForPreflight(Context, String, AttributionSource, String)}
+ * to determine if the app has or may have data access and this check will not
+ * leave a trace that op protected data was delivered. When you are about to
+ * deliver the data to a registered listener you should use this method which
+ * will evaluate the op access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param opName The op to check.
+ * @param attributionSource The identity for which to check the op.
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkOpForPreflight(Context, String, AttributionSource, String)
+ */
+ @PermissionResult
+ @SuppressWarnings("ConstantConditions")
+ public static int checkOpForDataDelivery(@NonNull Context context,
+ @NonNull String opName, @NonNull AttributionSource attributionSource,
+ @Nullable String message) {
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, false /*startDataDelivery*/);
+ }
+
+ /**
+ * 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.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * preflight point where you will not deliver the permission protected data
+ * to clients but schedule permission data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have location
+ * permission (if app has only foreground location the grant state depends on the app's
+ * fg/gb state) and this check will not leave a trace that permission protected data
+ * was delivered. When you are about to deliver the location data to a registered
+ * listener you should use {@link #checkPermissionForDataDelivery(Context, String,
+ * int, int, String, String, String)} which will evaluate the permission access based
+ * on the currentfg/bg state of the app and leave a record that the data was accessed.
+ *
+ * @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_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String, String)
+ */
+ @PermissionResult
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, int pid, int uid, @Nullable String packageName) {
+ return checkPermissionForPreflight(context, permission, new AttributionSource(
+ uid, packageName, null /*attributionTag*/));
+ }
+
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * preflight point where you will not deliver the permission protected data
+ * to clients but schedule permission data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a data listener it should have the required
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have the
+ * permission and this check will not leave a trace that permission protected data
+ * was delivered. When you are about to deliver the protected data to a registered
+ * listener you should use {@link #checkPermissionForDataDelivery(Context, String,
+ * int, AttributionSource, String, boolean)} which will evaluate the permission access based
+ * on the current fg/bg state of the app and leave a record that the data was accessed.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param attributionSource The identity for which to check the permission.
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkPermissionForDataDelivery(Context, String, int, AttributionSource,
+ * String, boolean)
+ */
+ @PermissionResult
+ @SuppressWarnings("ConstantConditions")
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource) {
+ return context.getSystemService(PermissionCheckerManager.class)
+ .checkPermission(permission, attributionSource.asState(), null /*message*/,
+ false /*forDataDelivery*/, /*startDataDelivery*/ false, /*fromDatasource*/ false,
+ AppOpsManager.OP_NONE);
+ }
+
+ /**
+ * Checks whether your app has a given permission and whether the app op
+ * that corresponds to this permission is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkSelfPermissionForPreflight(Context, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method
+ * which will evaluate the permission access based on the current fg/bg state of the
+ * app and leave a record that the data was accessed.
+ *
+ * <p>This API assumes the the {@link Binder#getCallingUid()} is the same as
+ * {@link Process#myUid()}.
+ *
+ * @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_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkSelfPermissionForPreflight(Context, String)
+ */
+ @PermissionResult
+ public static int checkSelfPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, @Nullable String message) {
+ return checkPermissionForDataDelivery(context, permission, Process.myPid(),
+ Process.myUid(), context.getPackageName(), context.getAttributionTag(), message,
+ /*startDataDelivery*/ false);
+ }
+
+ /**
+ * Checks whether your app has a given permission and whether the app op
+ * that corresponds to this permission is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * preflight point where you will not deliver the permission protected data
+ * to clients but schedule permission data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have location
+ * permission (if app has only foreground location the grant state depends on the
+ * app's fg/gb state) and this check will not leave a trace that permission protected
+ * data was delivered. When you are about to deliver the location data to a registered
+ * listener you should use this method which will evaluate the permission access based
+ * on the current fg/bg state of the app and leave a record that the data was accessed.
+ *
+ * <p>This API assumes the the {@link Binder#getCallingUid()} is the same as
+ * {@link Process#myUid()}.
+ *
+ * @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_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkSelfPermissionForDataDelivery(Context, String, String)
+ */
+ @PermissionResult
+ public static int checkSelfPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission) {
+ return checkPermissionForPreflight(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.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkCallingPermissionForPreflight(Context, String, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * <p>For more details how to determine the {@code callingPackageName},
+ * {@code callingAttributionTag}, and {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param callingPackageName The package name making the IPC. If null the
+ * the first package for the calling UID will be used.
+ * @param callingAttributionTag attribution tag
+ * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+ * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkCallingPermissionForPreflight(Context, String, String)
+ */
+ @PermissionResult
+ public static int checkCallingPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, @Nullable String callingPackageName,
+ @Nullable String callingAttributionTag, @Nullable String message) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return PERMISSION_HARD_DENIED;
+ }
+ return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
+ Binder.getCallingUid(), callingPackageName, callingAttributionTag, message,
+ /*startDataDelivery*/ false);
+ }
+
+ /**
+ * Checks whether the IPC you are handling has a given permission and whether
+ * the app op that corresponds to this permission is allowed.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * preflight point where you will not deliver the permission protected data
+ * to clients but schedule permission data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have location
+ * permission (if app has only foreground location the grant state depends on the app's
+ * fg/gb state) and this check will not leave a trace that permission protected data
+ * was delivered. When you are about to deliver the location data to a registered
+ * listener you should use {@link #checkCallingOrSelfPermissionForDataDelivery(Context,
+ * String, String, String, String)} which will evaluate the permission access based on the
+ * current fg/bg stateof the app and leave a record that the data was accessed.
+ *
+ * @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_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkCallingPermissionForDataDelivery(Context, String, String, String, String)
+ */
+ @PermissionResult
+ public static int checkCallingPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, @Nullable String packageName) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return PERMISSION_HARD_DENIED;
+ }
+ return checkPermissionForPreflight(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.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * point where you will deliver the permission protected data to clients.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use {@link #checkCallingOrSelfPermissionForPreflight(Context, String)}
+ * to determine if the app has or may have location permission (if app has only foreground
+ * location the grant state depends on the app's fg/gb state) and this check will not
+ * leave a trace that permission protected data was delivered. When you are about to
+ * deliver the location data to a registered listener you should use this method which
+ * will evaluate the permission access based on the current fg/bg state of the app and
+ * leave a record that the data was accessed.
+ *
+ * <p>For more details how to determine the {@code callingPackageName},
+ * {@code callingAttributionTag}, and {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
+ * @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_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ * @param callingPackageName package name tag of caller (if not self)
+ * @param callingAttributionTag attribution tag of caller (if not self)
+ * @param message A message describing the reason the permission was checked
+ *
+ * @see #checkCallingOrSelfPermissionForPreflight(Context, String)
+ */
+ @PermissionResult
+ public static int checkCallingOrSelfPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, @Nullable String callingPackageName,
+ @Nullable String callingAttributionTag, @Nullable String message) {
+ callingPackageName = (Binder.getCallingPid() == Process.myPid())
+ ? context.getPackageName() : callingPackageName;
+ callingAttributionTag = (Binder.getCallingPid() == Process.myPid())
+ ? context.getAttributionTag() : callingAttributionTag;
+ return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
+ Binder.getCallingUid(), callingPackageName, callingAttributionTag, message,
+ /*startDataDelivery*/ false);
+ }
+
+ /**
+ * 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.
+ *
+ * <strong>NOTE:</strong> Use this method only for permission checks at the
+ * preflight point where you will not deliver the permission protected data
+ * to clients but schedule permission data delivery, apps register listeners,
+ * etc.
+ *
+ * <p>For example, if an app registers a location listener it should have the location
+ * permission but no data is actually sent to the app at the moment of registration
+ * and you should use this method to determine if the app has or may have location
+ * permission (if app has only foreground location the grant state depends on the
+ * app's fg/gb state) and this check will not leave a trace that permission protected
+ * data was delivered. When you are about to deliver the location data to a registered
+ * listener you should use {@link #checkCallingOrSelfPermissionForDataDelivery(Context,
+ * String, String, String, String)} which will evaluate the permission access based on the
+ * current fg/bg state of the app and leave a record that the data was accessed.
+ *
+ * @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_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+ *
+ * @see #checkCallingOrSelfPermissionForDataDelivery(Context, String, String, String, String)
+ */
+ @PermissionResult
+ public static int checkCallingOrSelfPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission) {
+ String packageName = (Binder.getCallingPid() == Process.myPid())
+ ? context.getPackageName() : null;
+ return checkPermissionForPreflight(context, permission, Binder.getCallingPid(),
+ Binder.getCallingUid(), packageName);
+ }
+}
diff --git a/android/content/QuickViewConstants.java b/android/content/QuickViewConstants.java
new file mode 100644
index 0000000..ffb131c
--- /dev/null
+++ b/android/content/QuickViewConstants.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+/**
+ * Constants for {@link Intent#ACTION_QUICK_VIEW}.
+ */
+public class QuickViewConstants {
+
+ private QuickViewConstants() {}
+
+ /**
+ * Feature to view a document using system standard viewing mechanism, like
+ * {@link Intent#ACTION_VIEW}.
+ *
+ * @see Intent#EXTRA_QUICK_VIEW_FEATURES
+ * @see Intent#ACTION_QUICK_VIEW
+ */
+ public static final String FEATURE_VIEW = "android:view";
+
+ /**
+ * Feature to edit a document using system standard editing mechanism, like
+ * {@link Intent#ACTION_EDIT}.
+ *
+ * @see Intent#EXTRA_QUICK_VIEW_FEATURES
+ * @see Intent#ACTION_QUICK_VIEW
+ */
+ public static final String FEATURE_EDIT = "android:edit";
+
+ /**
+ * Feature to delete an individual document. Quick viewer implementations must use
+ * Storage Access Framework to both verify delete permission and to delete content.
+ *
+ * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
+ * @see android.provider.DocumentsContract#deleteDocument(ContentResolver, android.net.Uri)
+ */
+ public static final String FEATURE_DELETE = "android:delete";
+
+ /**
+ * Feature to view a document using system standard sending mechanism, like
+ * {@link Intent#ACTION_SEND}.
+ *
+ * @see Intent#EXTRA_QUICK_VIEW_FEATURES
+ * @see Intent#ACTION_QUICK_VIEW
+ */
+ public static final String FEATURE_SEND = "android:send";
+
+ /**
+ * Feature to download a document to the local file system.
+ *
+ * @see Intent#EXTRA_QUICK_VIEW_FEATURES
+ * @see Intent#ACTION_QUICK_VIEW
+ */
+ public static final String FEATURE_DOWNLOAD = "android:download";
+
+ /**
+ * Feature to print a document.
+ *
+ * @see Intent#EXTRA_QUICK_VIEW_FEATURES
+ * @see Intent#ACTION_QUICK_VIEW
+ */
+ public static final String FEATURE_PRINT = "android:print";
+}
diff --git a/android/content/ReceiverCallNotAllowedException.java b/android/content/ReceiverCallNotAllowedException.java
new file mode 100644
index 0000000..96b269c
--- /dev/null
+++ b/android/content/ReceiverCallNotAllowedException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2006 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.util.AndroidRuntimeException;
+
+/**
+ * This exception is thrown from {@link Context#registerReceiver} and
+ * {@link Context#bindService} when these methods are being used from
+ * an {@link BroadcastReceiver} component. In this case, the component will no
+ * longer be active upon returning from receiving the Intent, so it is
+ * not valid to use asynchronous APIs.
+ */
+public class ReceiverCallNotAllowedException extends AndroidRuntimeException {
+ public ReceiverCallNotAllowedException(String msg) {
+ super(msg);
+ }
+}
diff --git a/android/content/RestrictionEntry.java b/android/content/RestrictionEntry.java
new file mode 100644
index 0000000..63fcb49
--- /dev/null
+++ b/android/content/RestrictionEntry.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.ArrayRes;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Applications can expose restrictions for a restricted user on a
+ * multiuser device. The administrator can configure these restrictions that will then be
+ * applied to the restricted user. Each RestrictionsEntry is one configurable restriction.
+ * <p/>
+ * Any application that chooses to expose such restrictions does so by implementing a
+ * receiver that handles the {@link Intent#ACTION_GET_RESTRICTION_ENTRIES} action.
+ * The receiver then returns a result bundle that contains an entry called "restrictions", whose
+ * value is an ArrayList<RestrictionsEntry>.
+ */
+public class RestrictionEntry implements Parcelable {
+
+ /**
+ * Hidden restriction type. Use this type for information that needs to be transferred
+ * across but shouldn't be presented to the user in the UI. Stores a single String value.
+ */
+ public static final int TYPE_NULL = 0;
+
+ /**
+ * Restriction of type "bool". Use this for storing a boolean value, typically presented as
+ * a checkbox in the UI.
+ */
+ public static final int TYPE_BOOLEAN = 1;
+
+ /**
+ * Restriction of type "choice". Use this for storing a string value, typically presented as
+ * a single-select list. Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ */
+ public static final int TYPE_CHOICE = 2;
+
+ /**
+ * Internal restriction type. Use this for storing a string value, typically presented as
+ * a single-select list. Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ * The presentation could imply that values in lower array indices are included when a
+ * particular value is chosen.
+ * @hide
+ */
+ public static final int TYPE_CHOICE_LEVEL = 3;
+
+ /**
+ * Restriction of type "multi-select". Use this for presenting a multi-select list where more
+ * than one entry can be selected, such as for choosing specific titles to allowlist.
+ * Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ * Use {@link #getAllSelectedStrings()} and {@link #setAllSelectedStrings(String[])} to
+ * manipulate the selections.
+ */
+ public static final int TYPE_MULTI_SELECT = 4;
+
+ /**
+ * Restriction of type "integer". Use this for storing an integer value. The range of values
+ * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
+ */
+ public static final int TYPE_INTEGER = 5;
+
+ /**
+ * Restriction of type "string". Use this for storing a string value.
+ * @see #setSelectedString
+ * @see #getSelectedString
+ */
+ public static final int TYPE_STRING = 6;
+
+ /**
+ * Restriction of type "bundle". Use this for storing {@link android.os.Bundle bundles} of
+ * restrictions
+ */
+ public static final int TYPE_BUNDLE = 7;
+
+ /**
+ * Restriction of type "bundle_array". Use this for storing arrays of
+ * {@link android.os.Bundle bundles} of restrictions
+ */
+ public static final int TYPE_BUNDLE_ARRAY = 8;
+
+ /** The type of restriction. */
+ private int mType;
+
+ /** The unique key that identifies the restriction. */
+ private String mKey;
+
+ /** The user-visible title of the restriction. */
+ private String mTitle;
+
+ /** The user-visible secondary description of the restriction. */
+ private String mDescription;
+
+ /** The user-visible set of choices used for single-select and multi-select lists. */
+ private String[] mChoiceEntries;
+
+ /** The values corresponding to the user-visible choices. The value(s) of this entry will
+ * one or more of these, returned by {@link #getAllSelectedStrings()} and
+ * {@link #getSelectedString()}.
+ */
+ private String[] mChoiceValues;
+
+ /* The chosen value, whose content depends on the type of the restriction. */
+ private String mCurrentValue;
+
+ /* List of selected choices in the multi-select case. */
+ private String[] mCurrentValues;
+
+ /**
+ * List of nested restrictions. Used by {@link #TYPE_BUNDLE bundle} and
+ * {@link #TYPE_BUNDLE_ARRAY bundle_array} restrictions.
+ */
+ private RestrictionEntry[] mRestrictions;
+
+ /**
+ * Constructor for specifying the type and key, with no initial value;
+ *
+ * @param type the restriction type.
+ * @param key the unique key for this restriction
+ */
+ public RestrictionEntry(int type, String key) {
+ mType = type;
+ mKey = key;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_CHOICE} type.
+ * @param key the unique key for this restriction
+ * @param selectedString the current value
+ */
+ public RestrictionEntry(String key, String selectedString) {
+ this.mKey = key;
+ this.mType = TYPE_CHOICE;
+ this.mCurrentValue = selectedString;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_BOOLEAN} type.
+ * @param key the unique key for this restriction
+ * @param selectedState whether this restriction is selected or not
+ */
+ public RestrictionEntry(String key, boolean selectedState) {
+ this.mKey = key;
+ this.mType = TYPE_BOOLEAN;
+ setSelectedState(selectedState);
+ }
+
+ /**
+ * Constructor for {@link #TYPE_MULTI_SELECT} type.
+ * @param key the unique key for this restriction
+ * @param selectedStrings the list of values that are currently selected
+ */
+ public RestrictionEntry(String key, String[] selectedStrings) {
+ this.mKey = key;
+ this.mType = TYPE_MULTI_SELECT;
+ this.mCurrentValues = selectedStrings;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_INTEGER} type.
+ * @param key the unique key for this restriction
+ * @param selectedInt the integer value of the restriction
+ */
+ public RestrictionEntry(String key, int selectedInt) {
+ mKey = key;
+ mType = TYPE_INTEGER;
+ setIntValue(selectedInt);
+ }
+
+ /**
+ * Constructor for {@link #TYPE_BUNDLE}/{@link #TYPE_BUNDLE_ARRAY} type.
+ * @param key the unique key for this restriction
+ * @param restrictionEntries array of nested restriction entries. If the entry, being created
+ * represents a {@link #TYPE_BUNDLE_ARRAY bundle-array}, {@code restrictionEntries} array may
+ * only contain elements of type {@link #TYPE_BUNDLE bundle}.
+ * @param isBundleArray true if this restriction represents
+ * {@link #TYPE_BUNDLE_ARRAY bundle-array} type, otherwise the type will be set to
+ * {@link #TYPE_BUNDLE bundle}.
+ */
+ private RestrictionEntry(String key, RestrictionEntry[] restrictionEntries,
+ boolean isBundleArray) {
+ mKey = key;
+ if (isBundleArray) {
+ mType = TYPE_BUNDLE_ARRAY;
+ if (restrictionEntries != null) {
+ for (RestrictionEntry restriction : restrictionEntries) {
+ if (restriction.getType() != TYPE_BUNDLE) {
+ throw new IllegalArgumentException("bundle_array restriction can only have "
+ + "nested restriction entries of type bundle");
+ }
+ }
+ }
+ } else {
+ mType = TYPE_BUNDLE;
+ }
+ setRestrictions(restrictionEntries);
+ }
+
+ /**
+ * Creates an entry of type {@link #TYPE_BUNDLE}.
+ * @param key the unique key for this restriction
+ * @param restrictionEntries array of nested restriction entries.
+ * @return the newly created restriction
+ */
+ public static RestrictionEntry createBundleEntry(String key,
+ RestrictionEntry[] restrictionEntries) {
+ return new RestrictionEntry(key, restrictionEntries, false);
+ }
+
+ /**
+ * Creates an entry of type {@link #TYPE_BUNDLE_ARRAY}.
+ * @param key the unique key for this restriction
+ * @param restrictionEntries array of nested restriction entries. The array may only contain
+ * elements of type {@link #TYPE_BUNDLE bundle}.
+ * @return the newly created restriction
+ */
+ public static RestrictionEntry createBundleArrayEntry(String key,
+ RestrictionEntry[] restrictionEntries) {
+ return new RestrictionEntry(key, restrictionEntries, true);
+ }
+
+ /**
+ * Sets the type for this restriction.
+ * @param type the type for this restriction.
+ */
+ public void setType(int type) {
+ this.mType = type;
+ }
+
+ /**
+ * Returns the type for this restriction.
+ * @return the type for this restriction
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the currently selected string value.
+ * @return the currently selected value, which can be null for types that aren't for holding
+ * single string values.
+ */
+ public String getSelectedString() {
+ return mCurrentValue;
+ }
+
+ /**
+ * Returns the list of currently selected values.
+ * @return the list of current selections, if type is {@link #TYPE_MULTI_SELECT},
+ * null otherwise.
+ */
+ public String[] getAllSelectedStrings() {
+ return mCurrentValues;
+ }
+
+ /**
+ * Returns the current selected state for an entry of type {@link #TYPE_BOOLEAN}.
+ * @return the current selected state of the entry.
+ */
+ public boolean getSelectedState() {
+ return Boolean.parseBoolean(mCurrentValue);
+ }
+
+ /**
+ * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}.
+ * @return the integer value of the entry.
+ */
+ public int getIntValue() {
+ return Integer.parseInt(mCurrentValue);
+ }
+
+ /**
+ * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}.
+ * @param value the integer value to set.
+ */
+ public void setIntValue(int value) {
+ mCurrentValue = Integer.toString(value);
+ }
+
+ /**
+ * Sets the string value to use as the selected value for this restriction. This value will
+ * be persisted by the system for later use by the application.
+ * @param selectedString the string value to select.
+ */
+ public void setSelectedString(String selectedString) {
+ mCurrentValue = selectedString;
+ }
+
+ /**
+ * Sets the current selected state for an entry of type {@link #TYPE_BOOLEAN}. This value will
+ * be persisted by the system for later use by the application.
+ * @param state the current selected state
+ */
+ public void setSelectedState(boolean state) {
+ mCurrentValue = Boolean.toString(state);
+ }
+
+ /**
+ * Sets the current list of selected values for an entry of type {@link #TYPE_MULTI_SELECT}.
+ * These values will be persisted by the system for later use by the application.
+ * @param allSelectedStrings the current list of selected values.
+ */
+ public void setAllSelectedStrings(String[] allSelectedStrings) {
+ mCurrentValues = allSelectedStrings;
+ }
+
+ /**
+ * Sets a list of string values that can be selected by the user. If no user-visible entries
+ * are set by a call to {@link #setChoiceEntries(String[])}, these values will be the ones
+ * shown to the user. Values will be chosen from this list as the user's selection and the
+ * selected values can be retrieved by a call to {@link #getAllSelectedStrings()}, or
+ * {@link #getSelectedString()}, depending on whether it is a multi-select type or choice type.
+ * This method is not relevant for types other than
+ * {@link #TYPE_CHOICE}, and {@link #TYPE_MULTI_SELECT}.
+ * @param choiceValues an array of Strings which will be the selected values for the user's
+ * selections.
+ * @see #getChoiceValues()
+ * @see #getAllSelectedStrings()
+ */
+ public void setChoiceValues(String[] choiceValues) {
+ mChoiceValues = choiceValues;
+ }
+
+ /**
+ * Sets a list of string values that can be selected by the user, similar to
+ * {@link #setChoiceValues(String[])}.
+ * @param context the application context for retrieving the resources.
+ * @param stringArrayResId the resource id for a string array containing the possible values.
+ * @see #setChoiceValues(String[])
+ */
+ public void setChoiceValues(Context context, @ArrayRes int stringArrayResId) {
+ mChoiceValues = context.getResources().getStringArray(stringArrayResId);
+ }
+
+ /**
+ * Returns array of possible restriction entries that this entry may contain.
+ */
+ public RestrictionEntry[] getRestrictions() {
+ return mRestrictions;
+ }
+
+ /**
+ * Sets an array of possible restriction entries, that this entry may contain.
+ * <p>This method is only relevant for types {@link #TYPE_BUNDLE} and
+ * {@link #TYPE_BUNDLE_ARRAY}
+ */
+ public void setRestrictions(RestrictionEntry[] restrictions) {
+ mRestrictions = restrictions;
+ }
+
+ /**
+ * Returns the list of possible string values set earlier.
+ * @return the list of possible values.
+ */
+ public String[] getChoiceValues() {
+ return mChoiceValues;
+ }
+
+ /**
+ * Sets a list of strings that will be presented as choices to the user. When the
+ * user selects one or more of these choices, the corresponding value from the possible values
+ * are stored as the selected strings. The size of this array must match the size of the array
+ * set in {@link #setChoiceValues(String[])}. This method is not relevant for types other
+ * than {@link #TYPE_CHOICE}, and {@link #TYPE_MULTI_SELECT}.
+ * @param choiceEntries the list of user-visible choices.
+ * @see #setChoiceValues(String[])
+ */
+ public void setChoiceEntries(String[] choiceEntries) {
+ mChoiceEntries = choiceEntries;
+ }
+
+ /** Sets a list of strings that will be presented as choices to the user. This is similar to
+ * {@link #setChoiceEntries(String[])}.
+ * @param context the application context, used for retrieving the resources.
+ * @param stringArrayResId the resource id of a string array containing the possible entries.
+ */
+ public void setChoiceEntries(Context context, @ArrayRes int stringArrayResId) {
+ mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
+ }
+
+ /**
+ * Returns the list of strings, set earlier, that will be presented as choices to the user.
+ * @return the list of choices presented to the user.
+ */
+ public String[] getChoiceEntries() {
+ return mChoiceEntries;
+ }
+
+ /**
+ * Returns the provided user-visible description of the entry, if any.
+ * @return the user-visible description, null if none was set earlier.
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Sets the user-visible description of the entry, as a possible sub-text for the title.
+ * You can use this to describe the entry in more detail or to display the current state of
+ * the restriction.
+ * @param description the user-visible description string.
+ */
+ public void setDescription(String description) {
+ this.mDescription = description;
+ }
+
+ /**
+ * This is the unique key for the restriction entry.
+ * @return the key for the restriction.
+ */
+ public String getKey() {
+ return mKey;
+ }
+
+ /**
+ * Returns the user-visible title for the entry, if any.
+ * @return the user-visible title for the entry, null if none was set earlier.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the user-visible title for the entry.
+ * @param title the user-visible title for the entry.
+ */
+ public void setTitle(String title) {
+ this.mTitle = title;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) return true;
+ if (!(o instanceof RestrictionEntry)) return false;
+ final RestrictionEntry other = (RestrictionEntry) o;
+ if (mType != other.mType || !mKey.equals(other.mKey)) {
+ return false;
+ }
+ if (mCurrentValues == null && other.mCurrentValues == null
+ && mRestrictions == null && other.mRestrictions == null
+ && Objects.equals(mCurrentValue, other.mCurrentValue)) {
+ return true;
+ }
+ if (mCurrentValue == null && other.mCurrentValue == null
+ && mRestrictions == null && other.mRestrictions == null
+ && Arrays.equals(mCurrentValues, other.mCurrentValues)) {
+ return true;
+ }
+ if (mCurrentValue == null && other.mCurrentValue == null
+ && mCurrentValue == null && other.mCurrentValue == null
+ && Arrays.equals(mRestrictions, other.mRestrictions)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mKey.hashCode();
+ if (mCurrentValue != null) {
+ result = 31 * result + mCurrentValue.hashCode();
+ } else if (mCurrentValues != null) {
+ for (String value : mCurrentValues) {
+ if (value != null) {
+ result = 31 * result + value.hashCode();
+ }
+ }
+ } else if (mRestrictions != null) {
+ result = 31 * result + Arrays.hashCode(mRestrictions);
+ }
+ return result;
+ }
+
+ public RestrictionEntry(Parcel in) {
+ mType = in.readInt();
+ mKey = in.readString();
+ mTitle = in.readString();
+ mDescription = in.readString();
+ mChoiceEntries = in.readStringArray();
+ mChoiceValues = in.readStringArray();
+ mCurrentValue = in.readString();
+ mCurrentValues = in.readStringArray();
+ Parcelable[] parcelables = in.readParcelableArray(null);
+ if (parcelables != null) {
+ mRestrictions = new RestrictionEntry[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ mRestrictions[i] = (RestrictionEntry) parcelables[i];
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeString(mKey);
+ dest.writeString(mTitle);
+ dest.writeString(mDescription);
+ dest.writeStringArray(mChoiceEntries);
+ dest.writeStringArray(mChoiceValues);
+ dest.writeString(mCurrentValue);
+ dest.writeStringArray(mCurrentValues);
+ dest.writeParcelableArray(mRestrictions, 0);
+ }
+
+ public static final @android.annotation.NonNull Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
+ public RestrictionEntry createFromParcel(Parcel source) {
+ return new RestrictionEntry(source);
+ }
+
+ public RestrictionEntry[] newArray(int size) {
+ return new RestrictionEntry[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "RestrictionEntry{" +
+ "mType=" + mType +
+ ", mKey='" + mKey + '\'' +
+ ", mTitle='" + mTitle + '\'' +
+ ", mDescription='" + mDescription + '\'' +
+ ", mChoiceEntries=" + Arrays.toString(mChoiceEntries) +
+ ", mChoiceValues=" + Arrays.toString(mChoiceValues) +
+ ", mCurrentValue='" + mCurrentValue + '\'' +
+ ", mCurrentValues=" + Arrays.toString(mCurrentValues) +
+ ", mRestrictions=" + Arrays.toString(mRestrictions) +
+ '}';
+ }
+}
diff --git a/android/content/RestrictionsManager.java b/android/content/RestrictionsManager.java
new file mode 100644
index 0000000..885eb70
--- /dev/null
+++ b/android/content/RestrictionsManager.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.SystemService;
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.service.restrictions.RestrictionsReceiver;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Provides a mechanism for apps to query restrictions imposed by an entity that
+ * manages the user. Apps can also send permission requests to a local or remote
+ * device administrator to override default app-specific restrictions or any other
+ * operation that needs explicit authorization from the administrator.
+ * <p>
+ * Apps can expose a set of restrictions via an XML file specified in the manifest.
+ * <p>
+ * If the user has an active Restrictions Provider, dynamic requests can be made in
+ * addition to the statically imposed restrictions. Dynamic requests are app-specific
+ * and can be expressed via a predefined set of request types.
+ * <p>
+ * The RestrictionsManager forwards the dynamic requests to the active
+ * Restrictions Provider. The Restrictions Provider can respond back to requests by calling
+ * {@link #notifyPermissionResponse(String, PersistableBundle)}, when
+ * a response is received from the administrator of the device or user.
+ * The response is relayed back to the application via a protected broadcast,
+ * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
+ * <p>
+ * Static restrictions are specified by an XML file referenced by a meta-data attribute
+ * in the manifest. This enables applications as well as any web administration consoles
+ * to be able to read the list of available restrictions from the apk.
+ * <p>
+ * The syntax of the XML format is as follows:
+ * <pre>
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <restrictions xmlns:android="http://schemas.android.com/apk/res/android" >
+ * <restriction
+ * android:key="string"
+ * android:title="string resource"
+ * android:restrictionType=["bool" | "string" | "integer"
+ * | "choice" | "multi-select" | "hidden"
+ * | "bundle" | "bundle_array"]
+ * android:description="string resource"
+ * android:entries="string-array resource"
+ * android:entryValues="string-array resource"
+ * android:defaultValue="reference" >
+ * <restriction ... />
+ * ...
+ * </restriction>
+ * <restriction ... />
+ * ...
+ * </restrictions>
+ * </pre>
+ * <p>
+ * The attributes for each restriction depend on the restriction type.
+ * <p>
+ * <ul>
+ * <li><code>key</code>, <code>title</code> and <code>restrictionType</code> are mandatory.</li>
+ * <li><code>entries</code> and <code>entryValues</code> are required if <code>restrictionType
+ * </code> is <code>choice</code> or <code>multi-select</code>.</li>
+ * <li><code>defaultValue</code> is optional and its type depends on the
+ * <code>restrictionType</code></li>
+ * <li><code>hidden</code> type must have a <code>defaultValue</code> and will
+ * not be shown to the administrator. It can be used to pass along data that cannot be modified,
+ * such as a version code.</li>
+ * <li><code>description</code> is meant to describe the restriction in more detail to the
+ * administrator controlling the values, if the title is not sufficient.</li>
+ * </ul>
+ * <p>
+ * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested
+ * restriction elements.
+ * <p>
+ * In your manifest's <code>application</code> section, add the meta-data tag to point to
+ * the restrictions XML file as shown below:
+ * <pre>
+ * <application ... >
+ * <meta-data android:name="android.content.APP_RESTRICTIONS"
+ * android:resource="@xml/app_restrictions" />
+ * ...
+ * </application>
+ * </pre>
+ *
+ * @see RestrictionEntry
+ * @see RestrictionsReceiver
+ * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+ * @see DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)
+ */
+@SystemService(Context.RESTRICTIONS_SERVICE)
+public class RestrictionsManager {
+
+ private static final String TAG = "RestrictionsManager";
+
+ /**
+ * Broadcast intent delivered when a response is received for a permission request. The
+ * application should not interrupt the user by coming to the foreground if it isn't
+ * currently in the foreground. It can either post a notification informing
+ * the user of the response or wait until the next time the user launches the app.
+ * <p>
+ * For instance, if the user requested permission to make an in-app purchase,
+ * the app can post a notification that the request had been approved or denied.
+ * <p>
+ * The broadcast Intent carries the following extra:
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ */
+ public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
+ "android.content.action.PERMISSION_RESPONSE_RECEIVED";
+
+ /**
+ * Broadcast intent sent to the Restrictions Provider to handle a permission request from
+ * an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME},
+ * {@link #EXTRA_REQUEST_TYPE}, {@link #EXTRA_REQUEST_ID} and {@link #EXTRA_REQUEST_BUNDLE}.
+ * The Restrictions Provider will handle the request and respond back to the
+ * RestrictionsManager, when a response is available, by calling
+ * {@link #notifyPermissionResponse}.
+ * <p>
+ * The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN}
+ * permission to ensure that only the system can send the broadcast.
+ */
+ public static final String ACTION_REQUEST_PERMISSION =
+ "android.content.action.REQUEST_PERMISSION";
+
+ /**
+ * Activity intent that is optionally implemented by the Restrictions Provider package
+ * to challenge for an administrator PIN or password locally on the device. Apps will
+ * call this intent using {@link Activity#startActivityForResult}. On a successful
+ * response, {@link Activity#onActivityResult} will return a resultCode of
+ * {@link Activity#RESULT_OK}.
+ * <p>
+ * The intent must contain {@link #EXTRA_REQUEST_BUNDLE} as an extra and the bundle must
+ * contain at least {@link #REQUEST_KEY_MESSAGE} for the activity to display.
+ * <p>
+ * @see #createLocalApprovalIntent()
+ */
+ public static final String ACTION_REQUEST_LOCAL_APPROVAL =
+ "android.content.action.REQUEST_LOCAL_APPROVAL";
+
+ /**
+ * The package name of the application making the request.
+ * <p>
+ * Type: String
+ */
+ public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+
+ /**
+ * The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ * <p>
+ * Type: String
+ */
+ public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
+
+ /**
+ * The request ID passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ * <p>
+ * Type: String
+ */
+ public static final String EXTRA_REQUEST_ID = "android.content.extra.REQUEST_ID";
+
+ /**
+ * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ * <p>
+ * Type: {@link PersistableBundle}
+ */
+ public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
+
+ /**
+ * Contains a response from the administrator for specific request.
+ * The bundle contains the following information, at least:
+ * <ul>
+ * <li>{@link #REQUEST_KEY_ID}: The request ID.</li>
+ * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li>
+ * </ul>
+ * <p>
+ * Type: {@link PersistableBundle}
+ */
+ public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
+
+ /**
+ * Request type for a simple question, with a possible title and icon.
+ * <p>
+ * Required keys are: {@link #REQUEST_KEY_MESSAGE}
+ * <p>
+ * Optional keys are
+ * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
+ * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
+ */
+ public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
+
+ /**
+ * Key for request ID contained in the request bundle.
+ * <p>
+ * App-generated request ID to identify the specific request when receiving
+ * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_ID = "android.request.id";
+
+ /**
+ * Key for request data contained in the request bundle.
+ * <p>
+ * Optional, typically used to identify the specific data that is being referred to,
+ * such as the unique identifier for a movie or book. This is not used for display
+ * purposes and is more like a cookie. This value is returned in the
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DATA = "android.request.data";
+
+ /**
+ * Key for request title contained in the request bundle.
+ * <p>
+ * Optional, typically used as the title of any notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_TITLE = "android.request.title";
+
+ /**
+ * Key for request message contained in the request bundle.
+ * <p>
+ * Required, shown as the actual message in a notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_MESSAGE = "android.request.mesg";
+
+ /**
+ * Key for request icon contained in the request bundle.
+ * <p>
+ * Optional, shown alongside the request message presented to the administrator
+ * who approves the request. The content must be a compressed image such as a
+ * PNG or JPEG, as a byte array.
+ * <p>
+ * Type: byte[]
+ */
+ public static final String REQUEST_KEY_ICON = "android.request.icon";
+
+ /**
+ * Key for request approval button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the positive button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label";
+
+ /**
+ * Key for request rejection button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the negative button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
+
+ /**
+ * Key for issuing a new request, contained in the request bundle. If this is set to true,
+ * the Restrictions Provider must make a new request. If it is false or not specified, then
+ * the Restrictions Provider can return a cached response that has the same requestId, if
+ * available. If there's no cached response, it will issue a new one to the administrator.
+ * <p>
+ * Type: boolean
+ */
+ public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
+
+ /**
+ * Key for the response result in the response bundle sent to the application, for a permission
+ * request. It indicates the status of the request. In some cases an additional message might
+ * be available in {@link #RESPONSE_KEY_MESSAGE}, to be displayed to the user.
+ * <p>
+ * Type: int
+ * <p>
+ * Possible values: {@link #RESULT_APPROVED}, {@link #RESULT_DENIED},
+ * {@link #RESULT_NO_RESPONSE}, {@link #RESULT_UNKNOWN_REQUEST} or
+ * {@link #RESULT_ERROR}.
+ */
+ public static final String RESPONSE_KEY_RESULT = "android.response.result";
+
+ /**
+ * Response result value indicating that the request was approved.
+ */
+ public static final int RESULT_APPROVED = 1;
+
+ /**
+ * Response result value indicating that the request was denied.
+ */
+ public static final int RESULT_DENIED = 2;
+
+ /**
+ * Response result value indicating that the request has not received a response yet.
+ */
+ public static final int RESULT_NO_RESPONSE = 3;
+
+ /**
+ * Response result value indicating that the request is unknown, when it's not a new
+ * request.
+ */
+ public static final int RESULT_UNKNOWN_REQUEST = 4;
+
+ /**
+ * Response result value indicating an error condition. Additional error code might be available
+ * in the response bundle, for the key {@link #RESPONSE_KEY_ERROR_CODE}. There might also be
+ * an associated error message in the response bundle, for the key
+ * {@link #RESPONSE_KEY_MESSAGE}.
+ */
+ public static final int RESULT_ERROR = 5;
+
+ /**
+ * Error code indicating that there was a problem with the request.
+ * <p>
+ * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
+ */
+ public static final int RESULT_ERROR_BAD_REQUEST = 1;
+
+ /**
+ * Error code indicating that there was a problem with the network.
+ * <p>
+ * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
+ */
+ public static final int RESULT_ERROR_NETWORK = 2;
+
+ /**
+ * Error code indicating that there was an internal error.
+ * <p>
+ * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
+ */
+ public static final int RESULT_ERROR_INTERNAL = 3;
+
+ /**
+ * Key for the optional error code in the response bundle sent to the application.
+ * <p>
+ * Type: int
+ * <p>
+ * Possible values: {@link #RESULT_ERROR_BAD_REQUEST}, {@link #RESULT_ERROR_NETWORK} or
+ * {@link #RESULT_ERROR_INTERNAL}.
+ */
+ public static final String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
+
+ /**
+ * Key for the optional message in the response bundle sent to the application.
+ * <p>
+ * Type: String
+ */
+ public static final String RESPONSE_KEY_MESSAGE = "android.response.msg";
+
+ /**
+ * Key for the optional timestamp of when the administrator responded to the permission
+ * request. It is an represented in milliseconds since January 1, 1970 00:00:00.0 UTC.
+ * <p>
+ * Type: long
+ */
+ public static final String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
+
+ /**
+ * Name of the meta-data entry in the manifest that points to the XML file containing the
+ * application's available restrictions.
+ * @see #getManifestRestrictions(String)
+ */
+ public static final String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS";
+
+ private static final String TAG_RESTRICTION = "restriction";
+
+ private final Context mContext;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final IRestrictionsManager mService;
+
+ /**
+ * @hide
+ */
+ public RestrictionsManager(Context context, IRestrictionsManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns any available set of application-specific restrictions applicable
+ * to this application.
+ * @return the application restrictions as a Bundle. Returns null if there
+ * are no restrictions.
+ */
+ public Bundle getApplicationRestrictions() {
+ try {
+ if (mService != null) {
+ return mService.getApplicationRestrictions(mContext.getPackageName());
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return null;
+ }
+
+ /**
+ * Called by an application to check if there is an active Restrictions Provider. If
+ * there isn't, {@link #requestPermission(String, String, PersistableBundle)} is not available.
+ *
+ * @return whether there is an active Restrictions Provider.
+ */
+ public boolean hasRestrictionsProvider() {
+ try {
+ if (mService != null) {
+ return mService.hasRestrictionsProvider();
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
+ * Called by an application to request permission for an operation. The contents of the
+ * request are passed in a Bundle that contains several pieces of data depending on the
+ * chosen request type.
+ *
+ * @param requestType The type of request. The type could be one of the
+ * predefined types specified here or a custom type that the specific
+ * Restrictions Provider might understand. For custom types, the type name should be
+ * namespaced to avoid collisions with predefined types and types specified by
+ * other Restrictions Providers.
+ * @param requestId A unique id generated by the app that contains sufficient information
+ * to identify the parameters of the request when it receives the id in the response.
+ * @param request A PersistableBundle containing the data corresponding to the specified request
+ * type. The keys for the data in the bundle depend on the request type.
+ *
+ * @throws IllegalArgumentException if any of the required parameters are missing.
+ */
+ public void requestPermission(String requestType, String requestId, PersistableBundle request) {
+ if (requestType == null) {
+ throw new NullPointerException("requestType cannot be null");
+ }
+ if (requestId == null) {
+ throw new NullPointerException("requestId cannot be null");
+ }
+ if (request == null) {
+ throw new NullPointerException("request cannot be null");
+ }
+ try {
+ if (mService != null) {
+ mService.requestPermission(mContext.getPackageName(), requestType, requestId,
+ request);
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ public Intent createLocalApprovalIntent() {
+ try {
+ if (mService != null) {
+ return mService.createLocalApprovalIntent();
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return null;
+ }
+
+ /**
+ * Called by the Restrictions Provider to deliver a response to an application.
+ *
+ * @param packageName the application to deliver the response to. Cannot be null.
+ * @param response the bundle containing the response status, request ID and other information.
+ * Cannot be null.
+ *
+ * @throws IllegalArgumentException if any of the required parameters are missing.
+ */
+ public void notifyPermissionResponse(String packageName, PersistableBundle response) {
+ if (packageName == null) {
+ throw new NullPointerException("packageName cannot be null");
+ }
+ if (response == null) {
+ throw new NullPointerException("request cannot be null");
+ }
+ if (!response.containsKey(REQUEST_KEY_ID)) {
+ throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
+ }
+ if (!response.containsKey(RESPONSE_KEY_RESULT)) {
+ throw new IllegalArgumentException("RESPONSE_KEY_RESULT must be specified");
+ }
+ try {
+ if (mService != null) {
+ mService.notifyPermissionResponse(packageName, response);
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Parse and return the list of restrictions defined in the manifest for the specified
+ * package, if any.
+ *
+ * @param packageName The application for which to fetch the restrictions list.
+ * @return The list of RestrictionEntry objects created from the XML file specified
+ * in the manifest, or null if none was specified.
+ */
+ public List<RestrictionEntry> getManifestRestrictions(String packageName) {
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mContext.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException pnfe) {
+ throw new IllegalArgumentException("No such package " + packageName);
+ }
+ if (appInfo == null || !appInfo.metaData.containsKey(META_DATA_APP_RESTRICTIONS)) {
+ return null;
+ }
+
+ XmlResourceParser xml =
+ appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS);
+ return loadManifestRestrictions(packageName, xml);
+ }
+
+ private List<RestrictionEntry> loadManifestRestrictions(String packageName,
+ XmlResourceParser xml) {
+ Context appContext;
+ try {
+ appContext = mContext.createPackageContext(packageName, 0 /* flags */);
+ } catch (NameNotFoundException nnfe) {
+ return null;
+ }
+ ArrayList<RestrictionEntry> restrictions = new ArrayList<>();
+ RestrictionEntry restriction;
+
+ try {
+ int tagType = xml.next();
+ while (tagType != XmlPullParser.END_DOCUMENT) {
+ if (tagType == XmlPullParser.START_TAG) {
+ restriction = loadRestrictionElement(appContext, xml);
+ if (restriction != null) {
+ restrictions.add(restriction);
+ }
+ }
+ tagType = xml.next();
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Reading restriction metadata for " + packageName, e);
+ return null;
+ } catch (IOException e) {
+ Log.w(TAG, "Reading restriction metadata for " + packageName, e);
+ return null;
+ }
+
+ return restrictions;
+ }
+
+ private RestrictionEntry loadRestrictionElement(Context appContext, XmlResourceParser xml)
+ throws IOException, XmlPullParserException {
+ if (xml.getName().equals(TAG_RESTRICTION)) {
+ AttributeSet attrSet = Xml.asAttributeSet(xml);
+ if (attrSet != null) {
+ TypedArray a = appContext.obtainStyledAttributes(attrSet,
+ com.android.internal.R.styleable.RestrictionEntry);
+ return loadRestriction(appContext, a, xml);
+ }
+ }
+ return null;
+ }
+
+ private RestrictionEntry loadRestriction(Context appContext, TypedArray a, XmlResourceParser xml)
+ throws IOException, XmlPullParserException {
+ String key = a.getString(R.styleable.RestrictionEntry_key);
+ int restrictionType = a.getInt(
+ R.styleable.RestrictionEntry_restrictionType, -1);
+ String title = a.getString(R.styleable.RestrictionEntry_title);
+ String description = a.getString(R.styleable.RestrictionEntry_description);
+ int entries = a.getResourceId(R.styleable.RestrictionEntry_entries, 0);
+ int entryValues = a.getResourceId(R.styleable.RestrictionEntry_entryValues, 0);
+
+ if (restrictionType == -1) {
+ Log.w(TAG, "restrictionType cannot be omitted");
+ return null;
+ }
+
+ if (key == null) {
+ Log.w(TAG, "key cannot be omitted");
+ return null;
+ }
+
+ RestrictionEntry restriction = new RestrictionEntry(restrictionType, key);
+ restriction.setTitle(title);
+ restriction.setDescription(description);
+ if (entries != 0) {
+ restriction.setChoiceEntries(appContext, entries);
+ }
+ if (entryValues != 0) {
+ restriction.setChoiceValues(appContext, entryValues);
+ }
+ // Extract the default value based on the type
+ switch (restrictionType) {
+ case RestrictionEntry.TYPE_NULL: // hidden
+ case RestrictionEntry.TYPE_STRING:
+ case RestrictionEntry.TYPE_CHOICE:
+ restriction.setSelectedString(
+ a.getString(R.styleable.RestrictionEntry_defaultValue));
+ break;
+ case RestrictionEntry.TYPE_INTEGER:
+ restriction.setIntValue(
+ a.getInt(R.styleable.RestrictionEntry_defaultValue, 0));
+ break;
+ case RestrictionEntry.TYPE_MULTI_SELECT:
+ int resId = a.getResourceId(R.styleable.RestrictionEntry_defaultValue, 0);
+ if (resId != 0) {
+ restriction.setAllSelectedStrings(
+ appContext.getResources().getStringArray(resId));
+ }
+ break;
+ case RestrictionEntry.TYPE_BOOLEAN:
+ restriction.setSelectedState(
+ a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false));
+ break;
+ case RestrictionEntry.TYPE_BUNDLE:
+ case RestrictionEntry.TYPE_BUNDLE_ARRAY:
+ final int outerDepth = xml.getDepth();
+ List<RestrictionEntry> restrictionEntries = new ArrayList<>();
+ while (XmlUtils.nextElementWithin(xml, outerDepth)) {
+ RestrictionEntry childEntry = loadRestrictionElement(appContext, xml);
+ if (childEntry == null) {
+ Log.w(TAG, "Child entry cannot be loaded for bundle restriction " + key);
+ } else {
+ restrictionEntries.add(childEntry);
+ if (restrictionType == RestrictionEntry.TYPE_BUNDLE_ARRAY
+ && childEntry.getType() != RestrictionEntry.TYPE_BUNDLE) {
+ Log.w(TAG, "bundle_array " + key
+ + " can only contain entries of type bundle");
+ }
+ }
+ }
+ restriction.setRestrictions(restrictionEntries.toArray(new RestrictionEntry[
+ restrictionEntries.size()]));
+ break;
+ default:
+ Log.w(TAG, "Unknown restriction type " + restrictionType);
+ }
+ return restriction;
+ }
+
+ /**
+ * Converts a list of restrictions to the corresponding bundle, using the following mapping:
+ * <table>
+ * <tr><th>RestrictionEntry</th><th>Bundle</th></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_BOOLEAN}</td><td>{@link Bundle#putBoolean}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_CHOICE},
+ * {@link RestrictionEntry#TYPE_MULTI_SELECT}</td>
+ * <td>{@link Bundle#putStringArray}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_INTEGER}</td><td>{@link Bundle#putInt}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_STRING}</td><td>{@link Bundle#putString}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE}</td><td>{@link Bundle#putBundle}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE_ARRAY}</td>
+ * <td>{@link Bundle#putParcelableArray}</td></tr>
+ * </table>
+ * @param entries list of restrictions
+ */
+ public static Bundle convertRestrictionsToBundle(List<RestrictionEntry> entries) {
+ final Bundle bundle = new Bundle();
+ for (RestrictionEntry entry : entries) {
+ addRestrictionToBundle(bundle, entry);
+ }
+ return bundle;
+ }
+
+ private static Bundle addRestrictionToBundle(Bundle bundle, RestrictionEntry entry) {
+ switch (entry.getType()) {
+ case RestrictionEntry.TYPE_BOOLEAN:
+ bundle.putBoolean(entry.getKey(), entry.getSelectedState());
+ break;
+ case RestrictionEntry.TYPE_CHOICE:
+ case RestrictionEntry.TYPE_CHOICE_LEVEL:
+ case RestrictionEntry.TYPE_MULTI_SELECT:
+ bundle.putStringArray(entry.getKey(), entry.getAllSelectedStrings());
+ break;
+ case RestrictionEntry.TYPE_INTEGER:
+ bundle.putInt(entry.getKey(), entry.getIntValue());
+ break;
+ case RestrictionEntry.TYPE_STRING:
+ case RestrictionEntry.TYPE_NULL:
+ bundle.putString(entry.getKey(), entry.getSelectedString());
+ break;
+ case RestrictionEntry.TYPE_BUNDLE:
+ RestrictionEntry[] restrictions = entry.getRestrictions();
+ Bundle childBundle = convertRestrictionsToBundle(Arrays.asList(restrictions));
+ bundle.putBundle(entry.getKey(), childBundle);
+ break;
+ case RestrictionEntry.TYPE_BUNDLE_ARRAY:
+ RestrictionEntry[] bundleRestrictionArray = entry.getRestrictions();
+ Bundle[] bundleArray = new Bundle[bundleRestrictionArray.length];
+ for (int i = 0; i < bundleRestrictionArray.length; i++) {
+ RestrictionEntry[] bundleRestrictions =
+ bundleRestrictionArray[i].getRestrictions();
+ if (bundleRestrictions == null) {
+ // Non-bundle entry found in bundle array.
+ Log.w(TAG, "addRestrictionToBundle: " +
+ "Non-bundle entry found in bundle array");
+ bundleArray[i] = new Bundle();
+ } else {
+ bundleArray[i] = convertRestrictionsToBundle(Arrays.asList(
+ bundleRestrictions));
+ }
+ }
+ bundle.putParcelableArray(entry.getKey(), bundleArray);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported restrictionEntry type: " + entry.getType());
+ }
+ return bundle;
+ }
+
+}
diff --git a/android/content/SearchRecentSuggestionsProvider.java b/android/content/SearchRecentSuggestionsProvider.java
new file mode 100644
index 0000000..fc3ddf6
--- /dev/null
+++ b/android/content/SearchRecentSuggestionsProvider.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2008 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.app.SearchManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * This superclass can be used to create a simple search suggestions provider for your application.
+ * It creates suggestions (as the user types) based on recent queries and/or recent views.
+ *
+ * <p>In order to use this class, you must do the following.
+ *
+ * <ul>
+ * <li>Implement and test query search, as described in {@link android.app.SearchManager}. (This
+ * provider will send any suggested queries via the standard
+ * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} Intent, which you'll already
+ * support once you have implemented and tested basic searchability.)</li>
+ * <li>Create a Content Provider within your application by extending
+ * {@link android.content.SearchRecentSuggestionsProvider}. The class you create will be
+ * very simple - typically, it will have only a constructor. But the constructor has a very
+ * important responsibility: When it calls {@link #setupSuggestions(String, int)}, it
+ * <i>configures</i> the provider to match the requirements of your searchable activity.</li>
+ * <li>Create a manifest entry describing your provider. Typically this would be as simple
+ * as adding the following lines:
+ * <pre class="prettyprint">
+ * <!-- Content provider for search suggestions -->
+ * <provider android:name="YourSuggestionProviderClass"
+ * android:authorities="your.suggestion.authority" /></pre>
+ * </li>
+ * <li>Please note that you <i>do not</i> instantiate this content provider directly from within
+ * your code. This is done automatically by the system Content Resolver, when the search dialog
+ * looks for suggestions.</li>
+ * <li>In order for the Content Resolver to do this, you must update your searchable activity's
+ * XML configuration file with information about your content provider. The following additions
+ * are usually sufficient:
+ * <pre class="prettyprint">
+ * android:searchSuggestAuthority="your.suggestion.authority"
+ * android:searchSuggestSelection=" ? "</pre>
+ * </li>
+ * <li>In your searchable activities, capture any user-generated queries and record them
+ * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery
+ * SearchRecentSuggestions.saveRecentQuery()}.</li>
+ * </ul>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about using search suggestions in your application, read the
+ * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p>
+ * </div>
+ *
+ * @see android.provider.SearchRecentSuggestions
+ */
+public class SearchRecentSuggestionsProvider extends ContentProvider {
+ // debugging support
+ private static final String TAG = "SuggestionsProvider";
+
+ // client-provided configuration values
+ private String mAuthority;
+ private int mMode;
+ private boolean mTwoLineDisplay;
+
+ // general database configuration and tables
+ private SQLiteOpenHelper mOpenHelper;
+ private static final String sDatabaseName = "suggestions.db";
+ private static final String sSuggestions = "suggestions";
+ private static final String ORDER_BY = "date DESC";
+ private static final String NULL_COLUMN = "query";
+
+ // Table of database versions. Don't forget to update!
+ // NOTE: These version values are shifted left 8 bits (x 256) in order to create space for
+ // a small set of mode bitflags in the version int.
+ //
+ // 1 original implementation with queries, and 1 or 2 display columns
+ // 1->2 added UNIQUE constraint to display1 column
+ private static final int DATABASE_VERSION = 2 * 256;
+
+ /**
+ * This mode bit configures the database to record recent queries. <i>required</i>
+ *
+ * @see #setupSuggestions(String, int)
+ */
+ public static final int DATABASE_MODE_QUERIES = 1;
+ /**
+ * This mode bit configures the database to include a 2nd annotation line with each entry.
+ * <i>optional</i>
+ *
+ * @see #setupSuggestions(String, int)
+ */
+ public static final int DATABASE_MODE_2LINES = 2;
+
+ // Uri and query support
+ private static final int URI_MATCH_SUGGEST = 1;
+
+ private Uri mSuggestionsUri;
+ private UriMatcher mUriMatcher;
+
+ private String mSuggestSuggestionClause;
+ @UnsupportedAppUsage
+ private String[] mSuggestionProjection;
+
+ /**
+ * Builds the database. This version has extra support for using the version field
+ * as a mode flags field, and configures the database columns depending on the mode bits
+ * (features) requested by the extending class.
+ *
+ * @hide
+ */
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ private int mNewVersion;
+
+ public DatabaseHelper(Context context, int newVersion) {
+ super(context, sDatabaseName, null, newVersion);
+ mNewVersion = newVersion;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CREATE TABLE suggestions (" +
+ "_id INTEGER PRIMARY KEY" +
+ ",display1 TEXT UNIQUE ON CONFLICT REPLACE");
+ if (0 != (mNewVersion & DATABASE_MODE_2LINES)) {
+ builder.append(",display2 TEXT");
+ }
+ builder.append(",query TEXT" +
+ ",date LONG" +
+ ");");
+ db.execSQL(builder.toString());
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS suggestions");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * In order to use this class, you must extend it, and call this setup function from your
+ * constructor. In your application or activities, you must provide the same values when
+ * you create the {@link android.provider.SearchRecentSuggestions} helper.
+ *
+ * @param authority This must match the authority that you've declared in your manifest.
+ * @param mode You can use mode flags here to determine certain functional aspects of your
+ * database. Note, this value should not change from run to run, because when it does change,
+ * your suggestions database may be wiped.
+ *
+ * @see #DATABASE_MODE_QUERIES
+ * @see #DATABASE_MODE_2LINES
+ */
+ protected void setupSuggestions(String authority, int mode) {
+ if (TextUtils.isEmpty(authority) ||
+ ((mode & DATABASE_MODE_QUERIES) == 0)) {
+ throw new IllegalArgumentException();
+ }
+ // unpack mode flags
+ mTwoLineDisplay = (0 != (mode & DATABASE_MODE_2LINES));
+
+ // saved values
+ mAuthority = new String(authority);
+ mMode = mode;
+
+ // derived values
+ mSuggestionsUri = Uri.parse("content://" + mAuthority + "/suggestions");
+ mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mUriMatcher.addURI(mAuthority, SearchManager.SUGGEST_URI_PATH_QUERY, URI_MATCH_SUGGEST);
+
+ if (mTwoLineDisplay) {
+ mSuggestSuggestionClause = "display1 LIKE ? OR display2 LIKE ?";
+
+ mSuggestionProjection = new String [] {
+ "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
+ "'android.resource://system/"
+ + com.android.internal.R.drawable.ic_menu_recent_history + "' AS "
+ + SearchManager.SUGGEST_COLUMN_ICON_1,
+ "display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
+ "display2 AS " + SearchManager.SUGGEST_COLUMN_TEXT_2,
+ "query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
+ "_id"
+ };
+ } else {
+ mSuggestSuggestionClause = "display1 LIKE ?";
+
+ mSuggestionProjection = new String [] {
+ "0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
+ "'android.resource://system/"
+ + com.android.internal.R.drawable.ic_menu_recent_history + "' AS "
+ + SearchManager.SUGGEST_COLUMN_ICON_1,
+ "display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
+ "query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
+ "_id"
+ };
+ }
+
+
+ }
+
+ /**
+ * This method is provided for use by the ContentResolver. Do not override, or directly
+ * call from your own code.
+ */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ final int length = uri.getPathSegments().size();
+ if (length != 1) {
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+
+ final String base = uri.getPathSegments().get(0);
+ int count = 0;
+ if (base.equals(sSuggestions)) {
+ count = db.delete(sSuggestions, selection, selectionArgs);
+ } else {
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return count;
+ }
+
+ /**
+ * This method is provided for use by the ContentResolver. Do not override, or directly
+ * call from your own code.
+ */
+ @Override
+ public String getType(Uri uri) {
+ if (mUriMatcher.match(uri) == URI_MATCH_SUGGEST) {
+ return SearchManager.SUGGEST_MIME_TYPE;
+ }
+ int length = uri.getPathSegments().size();
+ if (length >= 1) {
+ String base = uri.getPathSegments().get(0);
+ if (base.equals(sSuggestions)) {
+ if (length == 1) {
+ return "vnd.android.cursor.dir/suggestion";
+ } else if (length == 2) {
+ return "vnd.android.cursor.item/suggestion";
+ }
+ }
+ }
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+
+ /**
+ * This method is provided for use by the ContentResolver. Do not override, or directly
+ * call from your own code.
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ int length = uri.getPathSegments().size();
+ if (length < 1) {
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+ // Note: This table has on-conflict-replace semantics, so insert() may actually replace()
+ long rowID = -1;
+ String base = uri.getPathSegments().get(0);
+ Uri newUri = null;
+ if (base.equals(sSuggestions)) {
+ if (length == 1) {
+ rowID = db.insert(sSuggestions, NULL_COLUMN, values);
+ if (rowID > 0) {
+ newUri = Uri.withAppendedPath(mSuggestionsUri, String.valueOf(rowID));
+ }
+ }
+ }
+ if (rowID < 0) {
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+ getContext().getContentResolver().notifyChange(newUri, null);
+ return newUri;
+ }
+
+ /**
+ * This method is provided for use by the ContentResolver. Do not override, or directly
+ * call from your own code.
+ */
+ @Override
+ public boolean onCreate() {
+ if (mAuthority == null || mMode == 0) {
+ throw new IllegalArgumentException("Provider not configured");
+ }
+ int mWorkingDbVersion = DATABASE_VERSION + mMode;
+ mOpenHelper = new DatabaseHelper(getContext(), mWorkingDbVersion);
+
+ return true;
+ }
+
+ /**
+ * This method is provided for use by the ContentResolver. Do not override, or directly
+ * call from your own code.
+ */
+ // TODO: Confirm no injection attacks here, or rewrite.
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+
+ // special case for actual suggestions (from search manager)
+ if (mUriMatcher.match(uri) == URI_MATCH_SUGGEST) {
+ String suggestSelection;
+ String[] myArgs;
+ if (TextUtils.isEmpty(selectionArgs[0])) {
+ suggestSelection = null;
+ myArgs = null;
+ } else {
+ String like = "%" + selectionArgs[0] + "%";
+ if (mTwoLineDisplay) {
+ myArgs = new String [] { like, like };
+ } else {
+ myArgs = new String [] { like };
+ }
+ suggestSelection = mSuggestSuggestionClause;
+ }
+ // Suggestions are always performed with the default sort order
+ Cursor c = db.query(sSuggestions, mSuggestionProjection,
+ suggestSelection, myArgs, null, null, ORDER_BY, null);
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ return c;
+ }
+
+ // otherwise process arguments and perform a standard query
+ int length = uri.getPathSegments().size();
+ if (length != 1 && length != 2) {
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+
+ String base = uri.getPathSegments().get(0);
+ if (!base.equals(sSuggestions)) {
+ throw new IllegalArgumentException("Unknown Uri");
+ }
+
+ String[] useProjection = null;
+ if (projection != null && projection.length > 0) {
+ useProjection = new String[projection.length + 1];
+ System.arraycopy(projection, 0, useProjection, 0, projection.length);
+ useProjection[projection.length] = "_id AS _id";
+ }
+
+ StringBuilder whereClause = new StringBuilder(256);
+ if (length == 2) {
+ whereClause.append("(_id = ").append(uri.getPathSegments().get(1)).append(")");
+ }
+
+ // Tack on the user's selection, if present
+ if (selection != null && selection.length() > 0) {
+ if (whereClause.length() > 0) {
+ whereClause.append(" AND ");
+ }
+
+ whereClause.append('(');
+ whereClause.append(selection);
+ whereClause.append(')');
+ }
+
+ // And perform the generic query as requested
+ Cursor c = db.query(base, useProjection, whereClause.toString(),
+ selectionArgs, null, null, sortOrder,
+ null);
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ return c;
+ }
+
+ /**
+ * This method is provided for use by the ContentResolver. Do not override, or directly
+ * call from your own code.
+ */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+}
diff --git a/android/content/ServiceConnection.java b/android/content/ServiceConnection.java
new file mode 100644
index 0000000..21398f6
--- /dev/null
+++ b/android/content/ServiceConnection.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2006 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.os.IBinder;
+
+/**
+ * Interface for monitoring the state of an application service. See
+ * {@link android.app.Service} and
+ * {@link Context#bindService Context.bindService()} for more information.
+ * <p>Like many callbacks from the system, the methods on this class are called
+ * from the main thread of your process.
+ */
+public interface ServiceConnection {
+ /**
+ * Called when a connection to the Service has been established, with
+ * the {@link android.os.IBinder} of the communication channel to the
+ * Service.
+ *
+ * <p class="note"><b>Note:</b> If the system has started to bind your
+ * client app to a service, it's possible that your app will never receive
+ * this callback. Your app won't receive a callback if there's an issue with
+ * the service, such as the service crashing while being created.
+ *
+ * @param name The concrete component name of the service that has
+ * been connected.
+ *
+ * @param service The IBinder of the Service's communication channel,
+ * which you can now make calls on.
+ */
+ void onServiceConnected(ComponentName name, IBinder service);
+
+ /**
+ * Called when a connection to the Service has been lost. This typically
+ * happens when the process hosting the service has crashed or been killed.
+ * This does <em>not</em> remove the ServiceConnection itself -- this
+ * binding to the service will remain active, and you will receive a call
+ * to {@link #onServiceConnected} when the Service is next running.
+ *
+ * @param name The concrete component name of the service whose
+ * connection has been lost.
+ */
+ void onServiceDisconnected(ComponentName name);
+
+ /**
+ * Called when the binding to this connection is dead. This means the
+ * interface will never receive another connection. The application will
+ * need to unbind and rebind the connection to activate it again. This may
+ * happen, for example, if the application hosting the service it is bound to
+ * has been updated.
+ *
+ * @param name The concrete component name of the service whose
+ * connection is dead.
+ */
+ default void onBindingDied(ComponentName name) {
+ }
+
+ /**
+ * Called when the service being bound has returned {@code null} from its
+ * {@link android.app.Service#onBind(Intent) onBind()} method. This indicates
+ * that the attempting service binding represented by this ServiceConnection
+ * will never become usable.
+ *
+ * <p class="note">The app which requested the binding must still call
+ * {@link Context#unbindService(ServiceConnection)} to release the tracking
+ * resources associated with this ServiceConnection even if this callback was
+ * invoked following {@link Context#bindService Context.bindService() bindService()}.
+ *
+ * @param name The concrete component name of the service whose binding
+ * has been rejected by the Service implementation.
+ */
+ default void onNullBinding(ComponentName name) {
+ }
+}
diff --git a/android/content/SharedPreferences.java b/android/content/SharedPreferences.java
new file mode 100644
index 0000000..c193868
--- /dev/null
+++ b/android/content/SharedPreferences.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2006 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.Nullable;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for accessing and modifying preference data returned by {@link
+ * Context#getSharedPreferences}. For any particular set of preferences,
+ * there is a single instance of this class that all clients share.
+ * Modifications to the preferences must go through an {@link Editor} object
+ * to ensure the preference values remain in a consistent state and control
+ * when they are committed to storage. Objects that are returned from the
+ * various <code>get</code> methods must be treated as immutable by the application.
+ *
+ * <p>Note: This class provides strong consistency guarantees. It is using expensive operations
+ * which might slow down an app. Frequently changing properties or properties where loss can be
+ * tolerated should use other mechanisms. For more details read the comments on
+ * {@link Editor#commit()} and {@link Editor#apply()}.
+ *
+ * <p><em>Note: This class does not support use across multiple processes.</em>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using SharedPreferences, read the
+ * <a href="{@docRoot}guide/topics/data/data-storage.html#pref">Data Storage</a>
+ * developer guide.</p></div>
+ *
+ * @see Context#getSharedPreferences
+ */
+public interface SharedPreferences {
+ /**
+ * Interface definition for a callback to be invoked when a shared
+ * preference is changed.
+ */
+ public interface OnSharedPreferenceChangeListener {
+ /**
+ * Called when a shared preference is changed, added, or removed. This
+ * may be called even if a preference is set to its existing value.
+ *
+ * <p>This callback will be run on your main thread.
+ *
+ * <p><em>Note: This callback will not be triggered when preferences are cleared
+ * via {@link Editor#clear()}, unless targeting {@link android.os.Build.VERSION_CODES#R}
+ * on devices running OS versions {@link android.os.Build.VERSION_CODES#R Android R}
+ * or later.</em>
+ *
+ * @param sharedPreferences The {@link SharedPreferences} that received the change.
+ * @param key The key of the preference that was changed, added, or removed. Apps targeting
+ * {@link android.os.Build.VERSION_CODES#R} on devices running OS versions
+ * {@link android.os.Build.VERSION_CODES#R Android R} or later, will receive
+ * a {@code null} value when preferences are cleared.
+ */
+ void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
+ }
+
+ /**
+ * Interface used for modifying values in a {@link SharedPreferences}
+ * object. All changes you make in an editor are batched, and not copied
+ * back to the original {@link SharedPreferences} until you call {@link #commit}
+ * or {@link #apply}
+ */
+ public interface Editor {
+ /**
+ * Set a String value in the preferences editor, to be written back once
+ * {@link #commit} or {@link #apply} are called.
+ *
+ * @param key The name of the preference to modify.
+ * @param value The new value for the preference. Passing {@code null}
+ * for this argument is equivalent to calling {@link #remove(String)} with
+ * this key.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putString(String key, @Nullable String value);
+
+ /**
+ * Set a set of String values in the preferences editor, to be written
+ * back once {@link #commit} or {@link #apply} is called.
+ *
+ * @param key The name of the preference to modify.
+ * @param values The set of new values for the preference. Passing {@code null}
+ * for this argument is equivalent to calling {@link #remove(String)} with
+ * this key.
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putStringSet(String key, @Nullable Set<String> values);
+
+ /**
+ * Set an int value in the preferences editor, to be written back once
+ * {@link #commit} or {@link #apply} are called.
+ *
+ * @param key The name of the preference to modify.
+ * @param value The new value for the preference.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putInt(String key, int value);
+
+ /**
+ * Set a long value in the preferences editor, to be written back once
+ * {@link #commit} or {@link #apply} are called.
+ *
+ * @param key The name of the preference to modify.
+ * @param value The new value for the preference.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putLong(String key, long value);
+
+ /**
+ * Set a float value in the preferences editor, to be written back once
+ * {@link #commit} or {@link #apply} are called.
+ *
+ * @param key The name of the preference to modify.
+ * @param value The new value for the preference.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putFloat(String key, float value);
+
+ /**
+ * Set a boolean value in the preferences editor, to be written back
+ * once {@link #commit} or {@link #apply} are called.
+ *
+ * @param key The name of the preference to modify.
+ * @param value The new value for the preference.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor putBoolean(String key, boolean value);
+
+ /**
+ * Mark in the editor that a preference value should be removed, which
+ * will be done in the actual preferences once {@link #commit} is
+ * called.
+ *
+ * <p>Note that when committing back to the preferences, all removals
+ * are done first, regardless of whether you called remove before
+ * or after put methods on this editor.
+ *
+ * @param key The name of the preference to remove.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor remove(String key);
+
+ /**
+ * Mark in the editor to remove <em>all</em> values from the
+ * preferences. Once commit is called, the only remaining preferences
+ * will be any that you have defined in this editor.
+ *
+ * <p>Note that when committing back to the preferences, the clear
+ * is done first, regardless of whether you called clear before
+ * or after put methods on this editor.
+ *
+ * @return Returns a reference to the same Editor object, so you can
+ * chain put calls together.
+ */
+ Editor clear();
+
+ /**
+ * Commit your preferences changes back from this Editor to the
+ * {@link SharedPreferences} object it is editing. This atomically
+ * performs the requested modifications, replacing whatever is currently
+ * in the SharedPreferences.
+ *
+ * <p>Note that when two editors are modifying preferences at the same
+ * time, the last one to call commit wins.
+ *
+ * <p>If you don't care about the return value and you're
+ * using this from your application's main thread, consider
+ * using {@link #apply} instead.
+ *
+ * @return Returns true if the new values were successfully written
+ * to persistent storage.
+ */
+ boolean commit();
+
+ /**
+ * Commit your preferences changes back from this Editor to the
+ * {@link SharedPreferences} object it is editing. This atomically
+ * performs the requested modifications, replacing whatever is currently
+ * in the SharedPreferences.
+ *
+ * <p>Note that when two editors are modifying preferences at the same
+ * time, the last one to call apply wins.
+ *
+ * <p>Unlike {@link #commit}, which writes its preferences out
+ * to persistent storage synchronously, {@link #apply}
+ * commits its changes to the in-memory
+ * {@link SharedPreferences} immediately but starts an
+ * asynchronous commit to disk and you won't be notified of
+ * any failures. If another editor on this
+ * {@link SharedPreferences} does a regular {@link #commit}
+ * while a {@link #apply} is still outstanding, the
+ * {@link #commit} will block until all async commits are
+ * completed as well as the commit itself.
+ *
+ * <p>As {@link SharedPreferences} instances are singletons within
+ * a process, it's safe to replace any instance of {@link #commit} with
+ * {@link #apply} if you were already ignoring the return value.
+ *
+ * <p>You don't need to worry about Android component
+ * lifecycles and their interaction with <code>apply()</code>
+ * writing to disk. The framework makes sure in-flight disk
+ * writes from <code>apply()</code> complete before switching
+ * states.
+ *
+ * <p class='note'>The SharedPreferences.Editor interface
+ * isn't expected to be implemented directly. However, if you
+ * previously did implement it and are now getting errors
+ * about missing <code>apply()</code>, you can simply call
+ * {@link #commit} from <code>apply()</code>.
+ */
+ void apply();
+ }
+
+ /**
+ * Retrieve all values from the preferences.
+ *
+ * <p>Note that you <em>must not</em> modify the collection returned
+ * by this method, or alter any of its contents. The consistency of your
+ * stored data is not guaranteed if you do.
+ *
+ * @return Returns a map containing a list of pairs key/value representing
+ * the preferences.
+ *
+ * @throws NullPointerException
+ */
+ Map<String, ?> getAll();
+
+ /**
+ * Retrieve a String value from the preferences.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValue Value to return if this preference does not exist.
+ *
+ * @return Returns the preference value if it exists, or defValue. Throws
+ * ClassCastException if there is a preference with this name that is not
+ * a String.
+ *
+ * @throws ClassCastException
+ */
+ @Nullable
+ String getString(String key, @Nullable String defValue);
+
+ /**
+ * Retrieve a set of String values from the preferences.
+ *
+ * <p>Note that you <em>must not</em> modify the set instance returned
+ * by this call. The consistency of the stored data is not guaranteed
+ * if you do, nor is your ability to modify the instance at all.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValues Values to return if this preference does not exist.
+ *
+ * @return Returns the preference values if they exist, or defValues.
+ * Throws ClassCastException if there is a preference with this name
+ * that is not a Set.
+ *
+ * @throws ClassCastException
+ */
+ @Nullable
+ Set<String> getStringSet(String key, @Nullable Set<String> defValues);
+
+ /**
+ * Retrieve an int value from the preferences.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValue Value to return if this preference does not exist.
+ *
+ * @return Returns the preference value if it exists, or defValue. Throws
+ * ClassCastException if there is a preference with this name that is not
+ * an int.
+ *
+ * @throws ClassCastException
+ */
+ int getInt(String key, int defValue);
+
+ /**
+ * Retrieve a long value from the preferences.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValue Value to return if this preference does not exist.
+ *
+ * @return Returns the preference value if it exists, or defValue. Throws
+ * ClassCastException if there is a preference with this name that is not
+ * a long.
+ *
+ * @throws ClassCastException
+ */
+ long getLong(String key, long defValue);
+
+ /**
+ * Retrieve a float value from the preferences.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValue Value to return if this preference does not exist.
+ *
+ * @return Returns the preference value if it exists, or defValue. Throws
+ * ClassCastException if there is a preference with this name that is not
+ * a float.
+ *
+ * @throws ClassCastException
+ */
+ float getFloat(String key, float defValue);
+
+ /**
+ * Retrieve a boolean value from the preferences.
+ *
+ * @param key The name of the preference to retrieve.
+ * @param defValue Value to return if this preference does not exist.
+ *
+ * @return Returns the preference value if it exists, or defValue. Throws
+ * ClassCastException if there is a preference with this name that is not
+ * a boolean.
+ *
+ * @throws ClassCastException
+ */
+ boolean getBoolean(String key, boolean defValue);
+
+ /**
+ * Checks whether the preferences contains a preference.
+ *
+ * @param key The name of the preference to check.
+ * @return Returns true if the preference exists in the preferences,
+ * otherwise false.
+ */
+ boolean contains(String key);
+
+ /**
+ * Create a new Editor for these preferences, through which you can make
+ * modifications to the data in the preferences and atomically commit those
+ * changes back to the SharedPreferences object.
+ *
+ * <p>Note that you <em>must</em> call {@link Editor#commit} to have any
+ * changes you perform in the Editor actually show up in the
+ * SharedPreferences.
+ *
+ * @return Returns a new instance of the {@link Editor} interface, allowing
+ * you to modify the values in this SharedPreferences object.
+ */
+ Editor edit();
+
+ /**
+ * Registers a callback to be invoked when a change happens to a preference.
+ *
+ * <p class="caution"><strong>Caution:</strong> The preference manager does
+ * not currently store a strong reference to the listener. You must store a
+ * strong reference to the listener, or it will be susceptible to garbage
+ * collection. We recommend you keep a reference to the listener in the
+ * instance data of an object that will exist as long as you need the
+ * listener.</p>
+ *
+ * @param listener The callback that will run.
+ * @see #unregisterOnSharedPreferenceChangeListener
+ */
+ void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
+
+ /**
+ * Unregisters a previous callback.
+ *
+ * @param listener The callback that should be unregistered.
+ * @see #registerOnSharedPreferenceChangeListener
+ */
+ void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
+}
diff --git a/android/content/SyncActivityTooManyDeletes.java b/android/content/SyncActivityTooManyDeletes.java
new file mode 100644
index 0000000..093fb08
--- /dev/null
+++ b/android/content/SyncActivityTooManyDeletes.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 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 com.android.internal.R;
+import android.accounts.Account;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Presents multiple options for handling the case where a sync was aborted because there
+ * were too many pending deletes. One option is to force the delete, another is to rollback
+ * the deletes, the third is to do nothing.
+ * @hide
+ */
+public class SyncActivityTooManyDeletes extends Activity
+ implements AdapterView.OnItemClickListener {
+
+ private long mNumDeletes;
+ private Account mAccount;
+ private String mAuthority;
+ private String mProvider;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ finish();
+ return;
+ }
+
+ mNumDeletes = extras.getLong("numDeletes");
+ mAccount = (Account) extras.getParcelable("account");
+ mAuthority = extras.getString("authority");
+ mProvider = extras.getString("provider");
+
+ // the order of these must match up with the constants for position used in onItemClick
+ CharSequence[] options = new CharSequence[]{
+ getResources().getText(R.string.sync_really_delete),
+ getResources().getText(R.string.sync_undo_deletes),
+ getResources().getText(R.string.sync_do_nothing)
+ };
+
+ ListAdapter adapter = new ArrayAdapter<CharSequence>(this,
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ options);
+
+ ListView listView = new ListView(this);
+ listView.setAdapter(adapter);
+ listView.setItemsCanFocus(true);
+ listView.setOnItemClickListener(this);
+
+ TextView textView = new TextView(this);
+ CharSequence tooManyDeletesDescFormat =
+ getResources().getText(R.string.sync_too_many_deletes_desc);
+ textView.setText(String.format(tooManyDeletesDescFormat.toString(),
+ mNumDeletes, mProvider, mAccount.name));
+
+ final LinearLayout ll = new LinearLayout(this);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ ll.addView(textView, lp);
+ ll.addView(listView, lp);
+
+ // TODO: consider displaying the icon of the account type
+// AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
+// for (AuthenticatorDescription desc : descs) {
+// if (desc.type.equals(mAccount.type)) {
+// try {
+// final Context authContext = createPackageContext(desc.packageName, 0);
+// ImageView imageView = new ImageView(this);
+// imageView.setImageDrawable(authContext.getDrawable(desc.iconId));
+// ll.addView(imageView, lp);
+// } catch (PackageManager.NameNotFoundException e) {
+// }
+// break;
+// }
+// }
+
+ setContentView(ll);
+ }
+
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // the constants for position correspond to the items options array in onCreate()
+ if (position == 0) startSyncReallyDelete();
+ else if (position == 1) startSyncUndoDeletes();
+ finish();
+ }
+
+ private void startSyncReallyDelete() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ ContentResolver.requestSync(mAccount, mAuthority, extras);
+ }
+
+ private void startSyncUndoDeletes() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ ContentResolver.requestSync(mAccount, mAuthority, extras);
+ }
+}
diff --git a/android/content/SyncAdapterType.java b/android/content/SyncAdapterType.java
new file mode 100644
index 0000000..47c333c
--- /dev/null
+++ b/android/content/SyncAdapterType.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2009 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.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Value type that represents a SyncAdapterType. This object overrides {@link #equals} and
+ * {@link #hashCode}, making it suitable for use as the key of a {@link java.util.Map}
+ */
+public class SyncAdapterType implements Parcelable {
+ public final String authority;
+ public final String accountType;
+ public final boolean isKey;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final boolean userVisible;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final boolean supportsUploading;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final boolean isAlwaysSyncable;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final boolean allowParallelSyncs;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final String settingsActivity;
+ private final String packageName;
+
+ public SyncAdapterType(String authority, String accountType, boolean userVisible,
+ boolean supportsUploading) {
+ if (TextUtils.isEmpty(authority)) {
+ throw new IllegalArgumentException("the authority must not be empty: " + authority);
+ }
+ if (TextUtils.isEmpty(accountType)) {
+ throw new IllegalArgumentException("the accountType must not be empty: " + accountType);
+ }
+ this.authority = authority;
+ this.accountType = accountType;
+ this.userVisible = userVisible;
+ this.supportsUploading = supportsUploading;
+ this.isAlwaysSyncable = false;
+ this.allowParallelSyncs = false;
+ this.settingsActivity = null;
+ this.isKey = false;
+ this.packageName = null;
+ }
+
+ /** @hide */
+ public SyncAdapterType(String authority, String accountType, boolean userVisible,
+ boolean supportsUploading,
+ boolean isAlwaysSyncable,
+ boolean allowParallelSyncs,
+ String settingsActivity,
+ String packageName) {
+ if (TextUtils.isEmpty(authority)) {
+ throw new IllegalArgumentException("the authority must not be empty: " + authority);
+ }
+ if (TextUtils.isEmpty(accountType)) {
+ throw new IllegalArgumentException("the accountType must not be empty: " + accountType);
+ }
+ this.authority = authority;
+ this.accountType = accountType;
+ this.userVisible = userVisible;
+ this.supportsUploading = supportsUploading;
+ this.isAlwaysSyncable = isAlwaysSyncable;
+ this.allowParallelSyncs = allowParallelSyncs;
+ this.settingsActivity = settingsActivity;
+ this.isKey = false;
+ this.packageName = packageName;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private SyncAdapterType(String authority, String accountType) {
+ if (TextUtils.isEmpty(authority)) {
+ throw new IllegalArgumentException("the authority must not be empty: " + authority);
+ }
+ if (TextUtils.isEmpty(accountType)) {
+ throw new IllegalArgumentException("the accountType must not be empty: " + accountType);
+ }
+ this.authority = authority;
+ this.accountType = accountType;
+ this.userVisible = true;
+ this.supportsUploading = true;
+ this.isAlwaysSyncable = false;
+ this.allowParallelSyncs = false;
+ this.settingsActivity = null;
+ this.isKey = true;
+ this.packageName = null;
+ }
+
+ public boolean supportsUploading() {
+ if (isKey) {
+ throw new IllegalStateException(
+ "this method is not allowed to be called when this is a key");
+ }
+ return supportsUploading;
+ }
+
+ public boolean isUserVisible() {
+ if (isKey) {
+ throw new IllegalStateException(
+ "this method is not allowed to be called when this is a key");
+ }
+ return userVisible;
+ }
+
+ /**
+ * @return True if this SyncAdapter supports syncing multiple accounts simultaneously.
+ * If false then the SyncManager will take care to only start one sync at a time
+ * using this SyncAdapter.
+ */
+ public boolean allowParallelSyncs() {
+ if (isKey) {
+ throw new IllegalStateException(
+ "this method is not allowed to be called when this is a key");
+ }
+ return allowParallelSyncs;
+ }
+
+ /**
+ * If true then the SyncManager will never issue an initialization sync to the SyncAdapter
+ * and will instead automatically call
+ * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with a
+ * value of 1 for each account and provider that this sync adapter supports.
+ * @return true if the SyncAdapter does not require initialization and if it is ok for the
+ * SyncAdapter to treat it as syncable automatically.
+ */
+ public boolean isAlwaysSyncable() {
+ if (isKey) {
+ throw new IllegalStateException(
+ "this method is not allowed to be called when this is a key");
+ }
+ return isAlwaysSyncable;
+ }
+
+ /**
+ * @return The activity to use to invoke this SyncAdapter's settings activity.
+ * May be null.
+ */
+ public String getSettingsActivity() {
+ if (isKey) {
+ throw new IllegalStateException(
+ "this method is not allowed to be called when this is a key");
+ }
+ return settingsActivity;
+ }
+
+ /**
+ * The package hosting the sync adapter.
+ * @return The package name.
+ *
+ * @hide
+ */
+ @TestApi
+ public @Nullable String getPackageName() {
+ return packageName;
+ }
+
+ public static SyncAdapterType newKey(String authority, String accountType) {
+ return new SyncAdapterType(authority, accountType);
+ }
+
+ public boolean equals(@Nullable Object o) {
+ if (o == this) return true;
+ if (!(o instanceof SyncAdapterType)) return false;
+ final SyncAdapterType other = (SyncAdapterType)o;
+ // don't include userVisible or supportsUploading in the equality check
+ return authority.equals(other.authority) && accountType.equals(other.accountType);
+ }
+
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + authority.hashCode();
+ result = 31 * result + accountType.hashCode();
+ // don't include userVisible or supportsUploading the hash
+ return result;
+ }
+
+ public String toString() {
+ if (isKey) {
+ return "SyncAdapterType Key {name=" + authority
+ + ", type=" + accountType
+ + "}";
+ } else {
+ return "SyncAdapterType {name=" + authority
+ + ", type=" + accountType
+ + ", userVisible=" + userVisible
+ + ", supportsUploading=" + supportsUploading
+ + ", isAlwaysSyncable=" + isAlwaysSyncable
+ + ", allowParallelSyncs=" + allowParallelSyncs
+ + ", settingsActivity=" + settingsActivity
+ + ", packageName=" + packageName
+ + "}";
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ if (isKey) {
+ throw new IllegalStateException("keys aren't parcelable");
+ }
+
+ dest.writeString(authority);
+ dest.writeString(accountType);
+ dest.writeInt(userVisible ? 1 : 0);
+ dest.writeInt(supportsUploading ? 1 : 0);
+ dest.writeInt(isAlwaysSyncable ? 1 : 0);
+ dest.writeInt(allowParallelSyncs ? 1 : 0);
+ dest.writeString(settingsActivity);
+ dest.writeString(packageName);
+ }
+
+ public SyncAdapterType(Parcel source) {
+ this(
+ source.readString(),
+ source.readString(),
+ source.readInt() != 0,
+ source.readInt() != 0,
+ source.readInt() != 0,
+ source.readInt() != 0,
+ source.readString(),
+ source.readString());
+ }
+
+ public static final @android.annotation.NonNull Creator<SyncAdapterType> CREATOR = new Creator<SyncAdapterType>() {
+ public SyncAdapterType createFromParcel(Parcel source) {
+ return new SyncAdapterType(source);
+ }
+
+ public SyncAdapterType[] newArray(int size) {
+ return new SyncAdapterType[size];
+ }
+ };
+}
diff --git a/android/content/SyncAdaptersCache.java b/android/content/SyncAdaptersCache.java
new file mode 100644
index 0000000..495f94f
--- /dev/null
+++ b/android/content/SyncAdaptersCache.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2009 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.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A cache of services that export the {@link android.content.ISyncAdapter} interface.
+ * @hide
+ */
+public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
+ private static final String TAG = "Account";
+
+ private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
+ private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
+ private static final String ATTRIBUTES_NAME = "sync-adapter";
+ private static final MySerializer sSerializer = new MySerializer();
+
+ @GuardedBy("mServicesLock")
+ private SparseArray<ArrayMap<String,String[]>> mAuthorityToSyncAdapters
+ = new SparseArray<>();
+
+ @UnsupportedAppUsage
+ public SyncAdaptersCache(Context context) {
+ super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
+ }
+
+ public SyncAdapterType parseServiceAttributes(Resources res,
+ String packageName, AttributeSet attrs) {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.SyncAdapter);
+ try {
+ final String authority =
+ sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority);
+ final String accountType =
+ sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType);
+ if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(accountType)) {
+ return null;
+ }
+ final boolean userVisible =
+ sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_userVisible, true);
+ final boolean supportsUploading =
+ sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_supportsUploading,
+ true);
+ final boolean isAlwaysSyncable =
+ sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_isAlwaysSyncable,
+ false);
+ final boolean allowParallelSyncs =
+ sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_allowParallelSyncs,
+ false);
+ final String settingsActivity =
+ sa.getString(com.android.internal.R.styleable
+ .SyncAdapter_settingsActivity);
+ return new SyncAdapterType(authority, accountType, userVisible, supportsUploading,
+ isAlwaysSyncable, allowParallelSyncs, settingsActivity, packageName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @Override
+ protected void onServicesChangedLocked(int userId) {
+ synchronized (mServicesLock) {
+ ArrayMap<String,String[]> adapterMap = mAuthorityToSyncAdapters.get(userId);
+ if (adapterMap != null) {
+ adapterMap.clear();
+ }
+ }
+
+ super.onServicesChangedLocked(userId);
+ }
+
+ public String[] getSyncAdapterPackagesForAuthority(String authority, int userId) {
+ synchronized (mServicesLock) {
+ ArrayMap<String,String[]> adapterMap = mAuthorityToSyncAdapters.get(userId);
+ if (adapterMap == null) {
+ adapterMap = new ArrayMap<>();
+ mAuthorityToSyncAdapters.put(userId, adapterMap);
+ }
+ // If the mapping exists, return it
+ if (adapterMap.containsKey(authority)) {
+ return adapterMap.get(authority);
+ }
+ // Create the mapping and cache it
+ String[] syncAdapterPackages;
+ final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos;
+ serviceInfos = getAllServices(userId);
+ ArrayList<String> packages = new ArrayList<>();
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
+ if (authority.equals(serviceInfo.type.authority)
+ && serviceInfo.componentName != null) {
+ packages.add(serviceInfo.componentName.getPackageName());
+ }
+ }
+ syncAdapterPackages = new String[packages.size()];
+ packages.toArray(syncAdapterPackages);
+ adapterMap.put(authority, syncAdapterPackages);
+
+ return syncAdapterPackages;
+ }
+ }
+
+ @Override
+ protected void onUserRemoved(int userId) {
+ synchronized (mServicesLock) {
+ mAuthorityToSyncAdapters.remove(userId);
+ }
+
+ super.onUserRemoved(userId);
+ }
+
+ static class MySerializer implements XmlSerializerAndParser<SyncAdapterType> {
+ public void writeAsXml(SyncAdapterType item, TypedXmlSerializer out) throws IOException {
+ out.attribute(null, "authority", item.authority);
+ out.attribute(null, "accountType", item.accountType);
+ }
+
+ public SyncAdapterType createFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final String authority = parser.getAttributeValue(null, "authority");
+ final String accountType = parser.getAttributeValue(null, "accountType");
+ return SyncAdapterType.newKey(authority, accountType);
+ }
+ }
+}
diff --git a/android/content/SyncContext.java b/android/content/SyncContext.java
new file mode 100644
index 0000000..4a9f66c
--- /dev/null
+++ b/android/content/SyncContext.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 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.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+public class SyncContext {
+ private ISyncContext mSyncContext;
+ private long mLastHeartbeatSendTime;
+
+ private static final long HEARTBEAT_SEND_INTERVAL_IN_MS = 1000;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public SyncContext(ISyncContext syncContextInterface) {
+ mSyncContext = syncContextInterface;
+ mLastHeartbeatSendTime = 0;
+ }
+
+ /**
+ * Call to update the status text for this sync. This internally invokes
+ * {@link #updateHeartbeat}, so it also takes the place of a call to that.
+ *
+ * @param message the current status message for this sync
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setStatusText(String message) {
+ updateHeartbeat();
+ }
+
+ /**
+ * Call to indicate that the SyncAdapter is making progress. E.g., if this SyncAdapter
+ * downloads or sends records to/from the server, this may be called after each record
+ * is downloaded or uploaded.
+ */
+ private void updateHeartbeat() {
+ final long now = SystemClock.elapsedRealtime();
+ if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
+ try {
+ mLastHeartbeatSendTime = now;
+ if (mSyncContext != null) {
+ mSyncContext.sendHeartbeat();
+ }
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onFinished(SyncResult result) {
+ try {
+ if (mSyncContext != null) {
+ mSyncContext.onFinished(result);
+ }
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public IBinder getSyncContextBinder() {
+ return (mSyncContext == null) ? null : mSyncContext.asBinder();
+ }
+}
diff --git a/android/content/SyncInfo.java b/android/content/SyncInfo.java
new file mode 100644
index 0000000..017a92b
--- /dev/null
+++ b/android/content/SyncInfo.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2009 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.accounts.Account;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about the sync operation that is currently underway.
+ */
+public class SyncInfo implements Parcelable {
+ /**
+ * Used when the caller receiving this object doesn't have permission to access the accounts
+ * on device.
+ * @See Manifest.permission.GET_ACCOUNTS
+ */
+ private static final Account REDACTED_ACCOUNT = new Account("*****", "*****");
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public final int authorityId;
+
+ /**
+ * The {@link Account} that is currently being synced.
+ */
+ public final Account account;
+
+ /**
+ * The authority of the provider that is currently being synced.
+ */
+ public final String authority;
+
+ /**
+ * The start time of the current sync operation in milliseconds since boot.
+ * This is represented in elapsed real time.
+ * See {@link android.os.SystemClock#elapsedRealtime()}.
+ */
+ public final long startTime;
+
+ /**
+ * Creates a SyncInfo object with an unusable Account. Used when the caller receiving this
+ * object doesn't have access to the accounts on the device.
+ * @See Manifest.permission.GET_ACCOUNTS
+ * @hide
+ */
+ public static SyncInfo createAccountRedacted(
+ int authorityId, String authority, long startTime) {
+ return new SyncInfo(authorityId, REDACTED_ACCOUNT, authority, startTime);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public SyncInfo(int authorityId, Account account, String authority, long startTime) {
+ this.authorityId = authorityId;
+ this.account = account;
+ this.authority = authority;
+ this.startTime = startTime;
+ }
+
+ /** @hide */
+ public SyncInfo(SyncInfo other) {
+ this.authorityId = other.authorityId;
+ this.account = new Account(other.account.name, other.account.type);
+ this.authority = other.authority;
+ this.startTime = other.startTime;
+ }
+
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(authorityId);
+ parcel.writeParcelable(account, flags);
+ parcel.writeString(authority);
+ parcel.writeLong(startTime);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ SyncInfo(Parcel parcel) {
+ authorityId = parcel.readInt();
+ account = parcel.readParcelable(Account.class.getClassLoader());
+ authority = parcel.readString();
+ startTime = parcel.readLong();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<SyncInfo> CREATOR = new Creator<SyncInfo>() {
+ public SyncInfo createFromParcel(Parcel in) {
+ return new SyncInfo(in);
+ }
+
+ public SyncInfo[] newArray(int size) {
+ return new SyncInfo[size];
+ }
+ };
+}
diff --git a/android/content/SyncRequest.java b/android/content/SyncRequest.java
new file mode 100644
index 0000000..e1e6f75
--- /dev/null
+++ b/android/content/SyncRequest.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.accounts.Account;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Convenience class to construct sync requests. See {@link android.content.SyncRequest.Builder}
+ * for an explanation of the various functions. The resulting object is passed through to the
+ * framework via {@link android.content.ContentResolver#requestSync(SyncRequest)}.
+ */
+public class SyncRequest implements Parcelable {
+ private static final String TAG = "SyncRequest";
+ /** Account to pass to the sync adapter. Can be null. */
+ @UnsupportedAppUsage
+ private final Account mAccountToSync;
+ /** Authority string that corresponds to a ContentProvider. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final String mAuthority;
+ /** Bundle containing user info as well as sync settings. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final Bundle mExtras;
+ /** Don't allow this sync request on metered networks. */
+ private final boolean mDisallowMetered;
+ /**
+ * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be
+ * started.
+ */
+ private final long mSyncFlexTimeSecs;
+ /**
+ * Specifies a point in the future at which the sync must have been scheduled to run.
+ */
+ @UnsupportedAppUsage
+ private final long mSyncRunTimeSecs;
+ /** Periodic versus one-off. */
+ @UnsupportedAppUsage
+ private final boolean mIsPeriodic;
+ /** Service versus provider. */
+ private final boolean mIsAuthority;
+ /** Sync should be run in lieu of other syncs. */
+ private final boolean mIsExpedited;
+ /** Sync sound be ran as an expedited job. */
+ private final boolean mIsScheduledAsExpeditedJob;
+
+ /**
+ * {@hide}
+ * @return whether this sync is periodic or one-time. A Sync Request must be
+ * either one of these or an InvalidStateException will be thrown in
+ * Builder.build().
+ */
+ public boolean isPeriodic() {
+ return mIsPeriodic;
+ }
+
+ /**
+ * {@hide}
+ * @return whether this sync is expedited.
+ */
+ public boolean isExpedited() {
+ return mIsExpedited;
+ }
+
+ /**
+ * {@hide}
+ * @return whether this sync is scheduled as an expedited job.
+ */
+ public boolean isScheduledAsExpeditedJob() {
+ return mIsScheduledAsExpeditedJob;
+ }
+
+ /**
+ * {@hide}
+ *
+ * @return account object for this sync.
+ * @throws IllegalArgumentException if this function is called for a request that targets a
+ * sync service.
+ */
+ public Account getAccount() {
+ return mAccountToSync;
+ }
+
+ /**
+ * {@hide}
+ *
+ * @return provider for this sync.
+ * @throws IllegalArgumentException if this function is called for a request that targets a
+ * sync service.
+ */
+ public String getProvider() {
+ return mAuthority;
+ }
+
+ /**
+ * {@hide}
+ * Retrieve bundle for this SyncRequest. Will not be null.
+ */
+ public Bundle getBundle() {
+ return mExtras;
+ }
+
+ /**
+ * {@hide}
+ * @return the earliest point in time that this sync can be scheduled.
+ */
+ public long getSyncFlexTime() {
+ return mSyncFlexTimeSecs;
+ }
+ /**
+ * {@hide}
+ * @return the last point in time at which this sync must scheduled.
+ */
+ public long getSyncRunTime() {
+ return mSyncRunTimeSecs;
+ }
+
+ public static final @android.annotation.NonNull Creator<SyncRequest> CREATOR = new Creator<SyncRequest>() {
+
+ @Override
+ public SyncRequest createFromParcel(Parcel in) {
+ return new SyncRequest(in);
+ }
+
+ @Override
+ public SyncRequest[] newArray(int size) {
+ return new SyncRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeBundle(mExtras);
+ parcel.writeLong(mSyncFlexTimeSecs);
+ parcel.writeLong(mSyncRunTimeSecs);
+ parcel.writeInt((mIsPeriodic ? 1 : 0));
+ parcel.writeInt((mDisallowMetered ? 1 : 0));
+ parcel.writeInt((mIsAuthority ? 1 : 0));
+ parcel.writeInt((mIsExpedited? 1 : 0));
+ parcel.writeInt(mIsScheduledAsExpeditedJob ? 1 : 0);
+ parcel.writeParcelable(mAccountToSync, flags);
+ parcel.writeString(mAuthority);
+ }
+
+ private SyncRequest(Parcel in) {
+ mExtras = Bundle.setDefusable(in.readBundle(), true);
+ mSyncFlexTimeSecs = in.readLong();
+ mSyncRunTimeSecs = in.readLong();
+ mIsPeriodic = (in.readInt() != 0);
+ mDisallowMetered = (in.readInt() != 0);
+ mIsAuthority = (in.readInt() != 0);
+ mIsExpedited = (in.readInt() != 0);
+ mIsScheduledAsExpeditedJob = (in.readInt() != 0);
+ mAccountToSync = in.readParcelable(null);
+ mAuthority = in.readString();
+ }
+
+ /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */
+ protected SyncRequest(SyncRequest.Builder b) {
+ mSyncFlexTimeSecs = b.mSyncFlexTimeSecs;
+ mSyncRunTimeSecs = b.mSyncRunTimeSecs;
+ mAccountToSync = b.mAccount;
+ mAuthority = b.mAuthority;
+ mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC);
+ mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER);
+ mIsExpedited = b.mExpedited;
+ mIsScheduledAsExpeditedJob = b.mScheduleAsExpeditedJob;
+ mExtras = new Bundle(b.mCustomExtras);
+ // For now we merge the sync config extras & the custom extras into one bundle.
+ // TODO: pass the configuration extras through separately.
+ mExtras.putAll(b.mSyncConfigExtras);
+ mDisallowMetered = b.mDisallowMetered;
+ }
+
+ /**
+ * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
+ * perform validation.
+ */
+ public static class Builder {
+ /** Unknown sync type. */
+ private static final int SYNC_TYPE_UNKNOWN = 0;
+ /** Specify that this is a periodic sync. */
+ private static final int SYNC_TYPE_PERIODIC = 1;
+ /** Specify that this is a one-time sync. */
+ private static final int SYNC_TYPE_ONCE = 2;
+ /** Unknown sync target. */
+ private static final int SYNC_TARGET_UNKNOWN = 0;
+ /** Specify that this is a sync with a provider. */
+ private static final int SYNC_TARGET_ADAPTER = 2;
+ /**
+ * Earliest point of displacement into the future at which this sync can
+ * occur.
+ */
+ private long mSyncFlexTimeSecs;
+ /** Displacement into the future at which this sync must occur. */
+ private long mSyncRunTimeSecs;
+ /**
+ * Sync configuration information - custom user data explicitly provided by the developer.
+ * This data is handed over to the sync operation.
+ */
+ private Bundle mCustomExtras;
+ /**
+ * Sync system configuration - used to store system sync configuration. Corresponds to
+ * ContentResolver.SYNC_EXTRAS_* flags.
+ * TODO: Use this instead of dumping into one bundle. Need to decide if these flags should
+ * discriminate between equivalent syncs.
+ */
+ private Bundle mSyncConfigExtras;
+ /** Whether or not this sync can occur on metered networks. Default false. */
+ private boolean mDisallowMetered;
+ /**
+ * Whether this builder is building a periodic sync, or a one-time sync.
+ */
+ private int mSyncType = SYNC_TYPE_UNKNOWN;
+ /** Whether this will go to a sync adapter. */
+ private int mSyncTarget = SYNC_TARGET_UNKNOWN;
+ /** Whether this is a user-activated sync. */
+ private boolean mIsManual;
+ /**
+ * Whether to retry this one-time sync if the sync fails. Not valid for
+ * periodic syncs. See {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}.
+ */
+ private boolean mNoRetry;
+ /**
+ * Whether to respect back-off for this one-time sync. Not valid for
+ * periodic syncs. See
+ * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF};
+ */
+ private boolean mIgnoreBackoff;
+
+ /** Ignore sync system settings and perform sync anyway. */
+ private boolean mIgnoreSettings;
+
+ /** This sync will run in preference to other non-expedited syncs. */
+ private boolean mExpedited;
+
+ /**
+ * The Account object that together with an Authority name define the SyncAdapter (if
+ * this sync is bound to a provider), otherwise null.
+ */
+ private Account mAccount;
+ /**
+ * The Authority name that together with an Account define the SyncAdapter (if
+ * this sync is bound to a provider), otherwise null.
+ */
+ private String mAuthority;
+ /**
+ * Whether the sync requires the phone to be plugged in.
+ */
+ private boolean mRequiresCharging;
+
+ /**
+ * Whether the sync should be scheduled as an expedited job.
+ */
+ private boolean mScheduleAsExpeditedJob;
+
+ public Builder() {
+ }
+
+ /**
+ * Request that a sync occur immediately.
+ *
+ * Example
+ * <pre>
+ * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
+ * </pre>
+ */
+ public Builder syncOnce() {
+ if (mSyncType != SYNC_TYPE_UNKNOWN) {
+ throw new IllegalArgumentException("Sync type has already been defined.");
+ }
+ mSyncType = SYNC_TYPE_ONCE;
+ setupInterval(0, 0);
+ return this;
+ }
+
+ /**
+ * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
+ * Syncs are identified by target {@link android.provider} and by the
+ * contents of the extras bundle.
+ * You cannot reuse the same builder for one-time syncs after having specified a periodic
+ * sync (by calling this function). If you do, an <code>IllegalArgumentException</code>
+ * will be thrown.
+ * <p>The bundle for a periodic sync can be queried by applications with the correct
+ * permissions using
+ * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
+ * sensitive data should be transferred here.
+ *
+ * Example usage.
+ *
+ * <pre>
+ * Request a periodic sync every 5 hours with 20 minutes of flex.
+ * SyncRequest.Builder builder =
+ * (new SyncRequest.Builder()).syncPeriodic(5 * HOUR_IN_SECS, 20 * MIN_IN_SECS);
+ *
+ * Schedule a periodic sync every hour at any point in time during that hour.
+ * SyncRequest.Builder builder =
+ * (new SyncRequest.Builder()).syncPeriodic(1 * HOUR_IN_SECS, 1 * HOUR_IN_SECS);
+ * </pre>
+ *
+ * N.B.: Periodic syncs are not allowed to have any of
+ * {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY},
+ * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF},
+ * {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS},
+ * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE},
+ * {@link ContentResolver#SYNC_EXTRAS_FORCE},
+ * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED},
+ * {@link ContentResolver#SYNC_EXTRAS_MANUAL},
+ * {@link ContentResolver#SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB}
+ * set to true. If any are supplied then an <code>IllegalArgumentException</code> will
+ * be thrown.
+ *
+ * @param pollFrequency the amount of time in seconds that you wish
+ * to elapse between periodic syncs. A minimum period of 1 hour is enforced.
+ * @param beforeSeconds the amount of flex time in seconds before
+ * {@code pollFrequency} that you permit for the sync to take
+ * place. Must be less than {@code pollFrequency} and greater than
+ * MAX(5% of {@code pollFrequency}, 5 minutes)
+ */
+ public Builder syncPeriodic(long pollFrequency, long beforeSeconds) {
+ if (mSyncType != SYNC_TYPE_UNKNOWN) {
+ throw new IllegalArgumentException("Sync type has already been defined.");
+ }
+ mSyncType = SYNC_TYPE_PERIODIC;
+ setupInterval(pollFrequency, beforeSeconds);
+ return this;
+ }
+
+ private void setupInterval(long at, long before) {
+ if (before > at) {
+ throw new IllegalArgumentException("Specified run time for the sync must be" +
+ " after the specified flex time.");
+ }
+ mSyncRunTimeSecs = at;
+ mSyncFlexTimeSecs = before;
+ }
+
+ /**
+ * Will throw an <code>IllegalArgumentException</code> if called and
+ * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called.
+ * @param disallow true to allow this transfer on metered networks. Default false.
+ *
+ */
+ public Builder setDisallowMetered(boolean disallow) {
+ if (mIgnoreSettings && disallow) {
+ throw new IllegalArgumentException("setDisallowMetered(true) after having"
+ + " specified that settings are ignored.");
+ }
+ mDisallowMetered = disallow;
+ return this;
+ }
+
+ /**
+ * Specify whether the sync requires the phone to be plugged in.
+ * @param requiresCharging true if sync requires the phone to be plugged in. Default false.
+ */
+ public Builder setRequiresCharging(boolean requiresCharging) {
+ mRequiresCharging = requiresCharging;
+ return this;
+ }
+
+ /**
+ * Specify an authority and account for this transfer.
+ *
+ * @param authority A String identifying the content provider to be synced.
+ * @param account Account to sync. Can be null unless this is a periodic
+ * sync, for which verification by the ContentResolver will
+ * fail. If a sync is performed without an account, the
+ */
+ public Builder setSyncAdapter(Account account, String authority) {
+ if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
+ throw new IllegalArgumentException("Sync target has already been defined.");
+ }
+ if (authority != null && authority.length() == 0) {
+ throw new IllegalArgumentException("Authority must be non-empty");
+ }
+ mSyncTarget = SYNC_TARGET_ADAPTER;
+ mAccount = account;
+ mAuthority = authority;
+ return this;
+ }
+
+ /**
+ * Developer-provided extras handed back when sync actually occurs. This bundle is copied
+ * into the SyncRequest returned by {@link #build()}.
+ *
+ * Example:
+ * <pre>
+ * String[] syncItems = {"dog", "cat", "frog", "child"};
+ * SyncRequest.Builder builder =
+ * new SyncRequest.Builder()
+ * .setSyncAdapter(dummyAccount, dummyProvider)
+ * .syncOnce();
+ *
+ * for (String syncData : syncItems) {
+ * Bundle extras = new Bundle();
+ * extras.setString("data", syncData);
+ * builder.setExtras(extras);
+ * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync.
+ * }
+ * </pre>
+ * Only values of the following types may be used in the extras bundle:
+ * <ul>
+ * <li>Integer</li>
+ * <li>Long</li>
+ * <li>Boolean</li>
+ * <li>Float</li>
+ * <li>Double</li>
+ * <li>String</li>
+ * <li>Account</li>
+ * <li>null</li>
+ * </ul>
+ * If any data is present in the bundle not of this type, build() will
+ * throw a runtime exception.
+ *
+ * @param bundle extras bundle to set.
+ */
+ public Builder setExtras(Bundle bundle) {
+ mCustomExtras = bundle;
+ return this;
+ }
+
+ /**
+ * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}.
+ *
+ * A one-off sync operation that fails will be retried with exponential back-off unless
+ * this is set to false. Not valid for periodic sync and will throw an
+ * <code>IllegalArgumentException</code> in build().
+ *
+ * @param noRetry true to not retry a failed sync. Default false.
+ */
+ public Builder setNoRetry(boolean noRetry) {
+ mNoRetry = noRetry;
+ return this;
+ }
+
+ /**
+ * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}.
+ *
+ * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * {@link #build()}.
+ * <p>Throws <code>IllegalArgumentException</code> if called and
+ * {@link #setDisallowMetered(boolean)} has been set.
+ *
+ *
+ * @param ignoreSettings true to ignore the sync automatically settings. Default false.
+ */
+ public Builder setIgnoreSettings(boolean ignoreSettings) {
+ if (mDisallowMetered && ignoreSettings) {
+ throw new IllegalArgumentException("setIgnoreSettings(true) after having specified"
+ + " sync settings with this builder.");
+ }
+ mIgnoreSettings = ignoreSettings;
+ return this;
+ }
+
+ /**
+ * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}.
+ *
+ * Ignoring back-off will force the sync scheduling process to ignore any back-off that was
+ * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil}
+ * value that may have been set by the adapter. Successive failures will not honor this
+ * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code>
+ * in {@link #build()}.
+ *
+ * @param ignoreBackoff ignore back off settings. Default false.
+ */
+ public Builder setIgnoreBackoff(boolean ignoreBackoff) {
+ mIgnoreBackoff = ignoreBackoff;
+ return this;
+ }
+
+ /**
+ * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}.
+ *
+ * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * {@link #build()}.
+ *
+ * @param isManual User-initiated sync or not. Default false.
+ */
+ public Builder setManual(boolean isManual) {
+ mIsManual = isManual;
+ return this;
+ }
+
+ /**
+ * An expedited sync runs immediately and can preempt other non-expedited running syncs.
+ *
+ * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * {@link #build()}.
+ *
+ * @param expedited whether to run expedited. Default false.
+ */
+ public Builder setExpedited(boolean expedited) {
+ mExpedited = expedited;
+ return this;
+ }
+
+ /**
+ * Convenience function for setting
+ * {@link ContentResolver#SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB}.
+ *
+ * <p> Not to be confused with {@link ContentResolver#SYNC_EXTRAS_EXPEDITED}.
+ *
+ * <p> Not valid for periodic syncs, expedited syncs, and syncs that require charging - an
+ * <code>IllegalArgumentException</code> will be thrown in {@link #build()}.
+ *
+ * @param scheduleAsExpeditedJob whether to schedule as an expedited job. Default false.
+ */
+ public @NonNull Builder setScheduleAsExpeditedJob(boolean scheduleAsExpeditedJob) {
+ mScheduleAsExpeditedJob = scheduleAsExpeditedJob;
+ return this;
+ }
+
+ /**
+ * Performs validation over the request and throws the runtime exception
+ * <code>IllegalArgumentException</code> if this validation fails.
+ *
+ * @return a SyncRequest with the information contained within this
+ * builder.
+ */
+ public SyncRequest build() {
+ // Combine builder extra flags into the config bundle.
+ mSyncConfigExtras = new Bundle();
+ if (mIgnoreBackoff) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ }
+ if (mDisallowMetered) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true);
+ }
+ if (mRequiresCharging) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, true);
+ }
+ if (mIgnoreSettings) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ if (mNoRetry) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
+ }
+ if (mExpedited) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ }
+ if (mScheduleAsExpeditedJob) {
+ mSyncConfigExtras.putBoolean(
+ ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, true);
+ }
+ if (mIsManual) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+
+ if (mCustomExtras == null) {
+ mCustomExtras = new Bundle();
+ }
+ // Validate the extras bundles
+ ContentResolver.validateSyncExtrasBundle(mCustomExtras);
+ // If this is a periodic sync ensure than invalid extras were not set.
+ if (mSyncType == SYNC_TYPE_PERIODIC) {
+ if (ContentResolver.invalidPeriodicExtras(mCustomExtras) ||
+ ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) {
+ throw new IllegalArgumentException("Illegal extras were set");
+ }
+ }
+ // If this sync is scheduled as an EJ, ensure that invalid extras were not set.
+ if (mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB)
+ || mScheduleAsExpeditedJob) {
+ if (ContentResolver.hasInvalidScheduleAsEjExtras(mCustomExtras)
+ || ContentResolver.hasInvalidScheduleAsEjExtras(mSyncConfigExtras)) {
+ throw new IllegalArgumentException("Illegal extras were set");
+ }
+ }
+ // Ensure that a target for the sync has been set.
+ if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
+ throw new IllegalArgumentException("Must specify an adapter with" +
+ " setSyncAdapter(Account, String");
+ }
+ return new SyncRequest(this);
+ }
+ }
+}
diff --git a/android/content/SyncResult.java b/android/content/SyncResult.java
new file mode 100644
index 0000000..7e68dca
--- /dev/null
+++ b/android/content/SyncResult.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2008 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to communicate the results of a sync operation to the SyncManager.
+ * Based on the values here the SyncManager will determine the disposition of the
+ * sync and whether or not a new sync operation needs to be scheduled in the future.
+ *
+ */
+public final class SyncResult implements Parcelable {
+ /**
+ * Used to indicate that the SyncAdapter is already performing a sync operation, though
+ * not necessarily for the requested account and authority and that it wasn't able to
+ * process this request. The SyncManager will reschedule the request to run later.
+ */
+ public final boolean syncAlreadyInProgress;
+
+ /**
+ * Used to indicate that the SyncAdapter determined that it would need to issue
+ * too many delete operations to the server in order to satisfy the request
+ * (as defined by the SyncAdapter). The SyncManager will record
+ * that the sync request failed and will cause a System Notification to be created
+ * asking the user what they want to do about this. It will give the user a chance to
+ * choose between (1) go ahead even with those deletes, (2) revert the deletes,
+ * or (3) take no action. If the user decides (1) or (2) the SyncManager will issue another
+ * sync request with either {@link ContentResolver#SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS}
+ * or {@link ContentResolver#SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS} set in the extras.
+ * It is then up to the SyncAdapter to decide how to honor that request.
+ */
+ public boolean tooManyDeletions;
+
+ /**
+ * Used to indicate that the SyncAdapter experienced a hard error due to trying the same
+ * operation too many times (as defined by the SyncAdapter). The SyncManager will record
+ * that the sync request failed and it will not reschedule the request.
+ */
+ public boolean tooManyRetries;
+
+ /**
+ * Used to indicate that the SyncAdapter experienced a hard error due to an error it
+ * received from interacting with the storage layer. The SyncManager will record that
+ * the sync request failed and it will not reschedule the request.
+ */
+ public boolean databaseError;
+
+ /**
+ * If set the SyncManager will request an immediate sync with the same Account and authority
+ * (but empty extras Bundle) as was used in the sync request.
+ */
+ public boolean fullSyncRequested;
+
+ /**
+ * This field is ignored by the SyncManager.
+ */
+ public boolean partialSyncUnavailable;
+
+ /**
+ * This field is ignored by the SyncManager.
+ */
+ public boolean moreRecordsToGet;
+
+ /**
+ * Used to indicate to the SyncManager that future sync requests that match the request's
+ * 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;
+
+ /**
+ * Used to hold extras statistics about the sync operation. Some of these indicate that
+ * the sync request resulted in a hard or soft error, others are for purely informational
+ * purposes.
+ */
+ public final SyncStats stats;
+
+ /**
+ * This instance of a SyncResult is returned by the SyncAdapter in response to a
+ * sync request when a sync is already underway. The SyncManager will reschedule the
+ * sync request to try again later.
+ */
+ public static final SyncResult ALREADY_IN_PROGRESS;
+
+ static {
+ ALREADY_IN_PROGRESS = new SyncResult(true);
+ }
+
+ /**
+ * Create a "clean" SyncResult. If this is returned without any changes then the
+ * SyncManager will consider the sync to have completed successfully. The various fields
+ * can be set by the SyncAdapter in order to give the SyncManager more information as to
+ * the disposition of the sync.
+ * <p>
+ * The errors are classified into two broad categories: hard errors and soft errors.
+ * Soft errors are retried with exponential backoff. Hard errors are not retried (except
+ * when the hard error is for a {@link ContentResolver#SYNC_EXTRAS_UPLOAD} request,
+ * in which the request is retryed without the {@link ContentResolver#SYNC_EXTRAS_UPLOAD}
+ * extra set). The SyncManager checks the type of error by calling
+ * {@link SyncResult#hasHardError()} and {@link SyncResult#hasSoftError()}. If both are
+ * true then the SyncManager treats it as a hard error, not a soft error.
+ */
+ public SyncResult() {
+ this(false);
+ }
+
+ /**
+ * Internal helper for creating a clean SyncResult or one that indicated that
+ * a sync is already in progress.
+ * @param syncAlreadyInProgress if true then set the {@link #syncAlreadyInProgress} flag
+ */
+ private SyncResult(boolean syncAlreadyInProgress) {
+ this.syncAlreadyInProgress = syncAlreadyInProgress;
+ this.tooManyDeletions = false;
+ this.tooManyRetries = false;
+ this.fullSyncRequested = false;
+ this.partialSyncUnavailable = false;
+ this.moreRecordsToGet = false;
+ this.delayUntil = 0;
+ this.stats = new SyncStats();
+ }
+
+ private SyncResult(Parcel parcel) {
+ syncAlreadyInProgress = parcel.readInt() != 0;
+ tooManyDeletions = parcel.readInt() != 0;
+ tooManyRetries = parcel.readInt() != 0;
+ databaseError = parcel.readInt() != 0;
+ fullSyncRequested = parcel.readInt() != 0;
+ partialSyncUnavailable = parcel.readInt() != 0;
+ moreRecordsToGet = parcel.readInt() != 0;
+ delayUntil = parcel.readLong();
+ stats = new SyncStats(parcel);
+ }
+
+ /**
+ * Convenience method for determining if the SyncResult indicates that a hard error
+ * occurred. See {@link #SyncResult()} for an explanation of what the SyncManager does
+ * when it sees a hard error.
+ * <p>
+ * A hard error is indicated when any of the following is true:
+ * <ul>
+ * <li> {@link SyncStats#numParseExceptions} > 0
+ * <li> {@link SyncStats#numConflictDetectedExceptions} > 0
+ * <li> {@link SyncStats#numAuthExceptions} > 0
+ * <li> {@link #tooManyDeletions}
+ * <li> {@link #tooManyRetries}
+ * <li> {@link #databaseError}
+ * @return true if a hard error is indicated
+ */
+ public boolean hasHardError() {
+ return stats.numParseExceptions > 0
+ || stats.numConflictDetectedExceptions > 0
+ || stats.numAuthExceptions > 0
+ || tooManyDeletions
+ || tooManyRetries
+ || databaseError;
+ }
+
+ /**
+ * Convenience method for determining if the SyncResult indicates that a soft error
+ * occurred. See {@link #SyncResult()} for an explanation of what the SyncManager does
+ * when it sees a soft error.
+ * <p>
+ * A soft error is indicated when any of the following is true:
+ * <ul>
+ * <li> {@link SyncStats#numIoExceptions} > 0
+ * <li> {@link #syncAlreadyInProgress}
+ * </ul>
+ * @return true if a soft error is indicated
+ */
+ public boolean hasSoftError() {
+ return syncAlreadyInProgress || stats.numIoExceptions > 0;
+ }
+
+ /**
+ * A convenience method for determining of the SyncResult indicates that an error occurred.
+ * @return true if either a soft or hard error occurred
+ */
+ public boolean hasError() {
+ return hasSoftError() || hasHardError();
+ }
+
+ /**
+ * Convenience method for determining if the Sync should be rescheduled after failing for some
+ * reason.
+ * @return true if the SyncManager should reschedule this sync.
+ */
+ public boolean madeSomeProgress() {
+ return ((stats.numDeletes > 0) && !tooManyDeletions)
+ || stats.numInserts > 0
+ || stats.numUpdates > 0;
+ }
+
+ /**
+ * Clears the SyncResult to a clean state. Throws an {@link UnsupportedOperationException}
+ * if this is called when {@link #syncAlreadyInProgress} is set.
+ */
+ public void clear() {
+ if (syncAlreadyInProgress) {
+ throw new UnsupportedOperationException(
+ "you are not allowed to clear the ALREADY_IN_PROGRESS SyncStats");
+ }
+ tooManyDeletions = false;
+ tooManyRetries = false;
+ databaseError = false;
+ fullSyncRequested = false;
+ partialSyncUnavailable = false;
+ moreRecordsToGet = false;
+ delayUntil = 0;
+ stats.clear();
+ }
+
+ public static final @android.annotation.NonNull Creator<SyncResult> CREATOR = new Creator<SyncResult>() {
+ public SyncResult createFromParcel(Parcel in) {
+ return new SyncResult(in);
+ }
+
+ public SyncResult[] newArray(int size) {
+ return new SyncResult[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(syncAlreadyInProgress ? 1 : 0);
+ parcel.writeInt(tooManyDeletions ? 1 : 0);
+ parcel.writeInt(tooManyRetries ? 1 : 0);
+ parcel.writeInt(databaseError ? 1 : 0);
+ parcel.writeInt(fullSyncRequested ? 1 : 0);
+ parcel.writeInt(partialSyncUnavailable ? 1 : 0);
+ parcel.writeInt(moreRecordsToGet ? 1 : 0);
+ parcel.writeLong(delayUntil);
+ stats.writeToParcel(parcel, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("SyncResult:");
+ if (syncAlreadyInProgress) {
+ sb.append(" syncAlreadyInProgress: ").append(syncAlreadyInProgress);
+ }
+ if (tooManyDeletions) sb.append(" tooManyDeletions: ").append(tooManyDeletions);
+ if (tooManyRetries) sb.append(" tooManyRetries: ").append(tooManyRetries);
+ if (databaseError) sb.append(" databaseError: ").append(databaseError);
+ if (fullSyncRequested) sb.append(" fullSyncRequested: ").append(fullSyncRequested);
+ if (partialSyncUnavailable) {
+ sb.append(" partialSyncUnavailable: ").append(partialSyncUnavailable);
+ }
+ if (moreRecordsToGet) sb.append(" moreRecordsToGet: ").append(moreRecordsToGet);
+ if (delayUntil > 0) sb.append(" delayUntil: ").append(delayUntil);
+ sb.append(stats);
+ return sb.toString();
+ }
+
+ /**
+ * Generates a debugging string indicating the status.
+ * The string consist of a sequence of code letter followed by the count.
+ * Code letters are f - fullSyncRequested, r - partialSyncUnavailable,
+ * X - hardError, e - numParseExceptions, c - numConflictDetectedExceptions,
+ * a - numAuthExceptions, D - tooManyDeletions, R - tooManyRetries,
+ * b - databaseError, x - softError, l - syncAlreadyInProgress,
+ * I - numIoExceptions
+ * @return debugging string.
+ */
+ public String toDebugString() {
+ StringBuilder sb = new StringBuilder();
+
+ if (fullSyncRequested) {
+ sb.append("f1");
+ }
+ if (partialSyncUnavailable) {
+ sb.append("r1");
+ }
+ if (hasHardError()) {
+ sb.append("X1");
+ }
+ if (stats.numParseExceptions > 0) {
+ sb.append("e").append(stats.numParseExceptions);
+ }
+ if (stats.numConflictDetectedExceptions > 0) {
+ sb.append("c").append(stats.numConflictDetectedExceptions);
+ }
+ if (stats.numAuthExceptions > 0) {
+ sb.append("a").append(stats.numAuthExceptions);
+ }
+ if (tooManyDeletions) {
+ sb.append("D1");
+ }
+ if (tooManyRetries) {
+ sb.append("R1");
+ }
+ if (databaseError) {
+ sb.append("b1");
+ }
+ if (hasSoftError()) {
+ sb.append("x1");
+ }
+ if (syncAlreadyInProgress) {
+ sb.append("l1");
+ }
+ if (stats.numIoExceptions > 0) {
+ sb.append("I").append(stats.numIoExceptions);
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/content/SyncStats.java b/android/content/SyncStats.java
new file mode 100644
index 0000000..9596a60
--- /dev/null
+++ b/android/content/SyncStats.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2007 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.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * Used to record various statistics about the result of a sync operation. The SyncManager
+ * gets access to these via a {@link SyncResult} and uses some of them to determine the
+ * disposition of the sync. See {@link SyncResult} for further dicussion on how the
+ * SyncManager uses these values.
+ */
+public class SyncStats implements Parcelable {
+ /**
+ * The SyncAdapter was unable to authenticate the {@link android.accounts.Account}
+ * that was specified in the request. The user needs to take some action to resolve
+ * before a future request can expect to succeed. This is considered a hard error.
+ */
+ public long numAuthExceptions;
+
+ /**
+ * The SyncAdapter had a problem, most likely with the network connectivity or a timeout
+ * while waiting for a network response. The request may succeed if it is tried again
+ * later. This is considered a soft error.
+ */
+ public long numIoExceptions;
+
+ /**
+ * The SyncAdapter had a problem with the data it received from the server or the storage
+ * later. This problem will likely repeat if the request is tried again. The problem
+ * will need to be cleared up by either the server or the storage layer (likely with help
+ * from the user). If the SyncAdapter cleans up the data itself then it typically won't
+ * increment this value although it may still do so in order to record that it had to
+ * perform some cleanup. E.g., if the SyncAdapter received a bad entry from the server
+ * when processing a feed of entries, it may choose to drop the entry and thus make
+ * progress and still increment this value just so the SyncAdapter can record that an
+ * error occurred. This is considered a hard error.
+ */
+ public long numParseExceptions;
+
+ /**
+ * The SyncAdapter detected that there was an unrecoverable version conflict when it
+ * attempted to update or delete a version of a resource on the server. This is expected
+ * to clear itself automatically once the new state is retrieved from the server,
+ * though it may remain until the user intervenes manually, perhaps by clearing the
+ * local storage and starting over from scratch. This is considered a hard error.
+ */
+ public long numConflictDetectedExceptions;
+
+ /**
+ * Counter for tracking how many inserts were performed by the sync operation, as defined
+ * by the SyncAdapter.
+ */
+ public long numInserts;
+
+ /**
+ * Counter for tracking how many updates were performed by the sync operation, as defined
+ * by the SyncAdapter.
+ */
+ public long numUpdates;
+
+ /**
+ * Counter for tracking how many deletes were performed by the sync operation, as defined
+ * by the SyncAdapter.
+ */
+ public long numDeletes;
+
+ /**
+ * Counter for tracking how many entries were affected by the sync operation, as defined
+ * by the SyncAdapter.
+ */
+ public long numEntries;
+
+ /**
+ * Counter for tracking how many entries, either from the server or the local store, were
+ * ignored during the sync operation. This could happen if the SyncAdapter detected some
+ * unparsable data but decided to skip it and move on rather than failing immediately.
+ */
+ public long numSkippedEntries;
+
+ public SyncStats() {
+ numAuthExceptions = 0;
+ numIoExceptions = 0;
+ numParseExceptions = 0;
+ numConflictDetectedExceptions = 0;
+ numInserts = 0;
+ numUpdates = 0;
+ numDeletes = 0;
+ numEntries = 0;
+ numSkippedEntries = 0;
+ }
+
+ public SyncStats(Parcel in) {
+ numAuthExceptions = in.readLong();
+ numIoExceptions = in.readLong();
+ numParseExceptions = in.readLong();
+ numConflictDetectedExceptions = in.readLong();
+ numInserts = in.readLong();
+ numUpdates = in.readLong();
+ numDeletes = in.readLong();
+ numEntries = in.readLong();
+ numSkippedEntries = in.readLong();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" stats [");
+ if (numAuthExceptions > 0) sb.append(" numAuthExceptions: ").append(numAuthExceptions);
+ if (numIoExceptions > 0) sb.append(" numIoExceptions: ").append(numIoExceptions);
+ if (numParseExceptions > 0) sb.append(" numParseExceptions: ").append(numParseExceptions);
+ if (numConflictDetectedExceptions > 0)
+ sb.append(" numConflictDetectedExceptions: ").append(numConflictDetectedExceptions);
+ if (numInserts > 0) sb.append(" numInserts: ").append(numInserts);
+ if (numUpdates > 0) sb.append(" numUpdates: ").append(numUpdates);
+ if (numDeletes > 0) sb.append(" numDeletes: ").append(numDeletes);
+ if (numEntries > 0) sb.append(" numEntries: ").append(numEntries);
+ if (numSkippedEntries > 0) sb.append(" numSkippedEntries: ").append(numSkippedEntries);
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Reset all the counters to 0.
+ */
+ public void clear() {
+ numAuthExceptions = 0;
+ numIoExceptions = 0;
+ numParseExceptions = 0;
+ numConflictDetectedExceptions = 0;
+ numInserts = 0;
+ numUpdates = 0;
+ numDeletes = 0;
+ numEntries = 0;
+ numSkippedEntries = 0;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(numAuthExceptions);
+ dest.writeLong(numIoExceptions);
+ dest.writeLong(numParseExceptions);
+ dest.writeLong(numConflictDetectedExceptions);
+ dest.writeLong(numInserts);
+ dest.writeLong(numUpdates);
+ dest.writeLong(numDeletes);
+ dest.writeLong(numEntries);
+ dest.writeLong(numSkippedEntries);
+ }
+
+ public static final @android.annotation.NonNull Creator<SyncStats> CREATOR = new Creator<SyncStats>() {
+ public SyncStats createFromParcel(Parcel in) {
+ return new SyncStats(in);
+ }
+
+ public SyncStats[] newArray(int size) {
+ return new SyncStats[size];
+ }
+ };
+}
diff --git a/android/content/SyncStatusInfo.java b/android/content/SyncStatusInfo.java
new file mode 100644
index 0000000..8d0baa7
--- /dev/null
+++ b/android/content/SyncStatusInfo.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2009 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+/** @hide */
+public class SyncStatusInfo implements Parcelable {
+ private static final String TAG = "Sync";
+
+ static final int VERSION = 6;
+
+ private static final int MAX_EVENT_COUNT = 10;
+
+ /**
+ * Number of sync sources. KEEP THIS AND SyncStorageEngine.SOURCES IN SYNC.
+ */
+ private static final int SOURCE_COUNT = 6;
+
+ @UnsupportedAppUsage
+ public final int authorityId;
+
+ /**
+ * # of syncs for each sync source, etc.
+ */
+ public static class Stats {
+ public long totalElapsedTime;
+ public int numSyncs;
+ public int numSourcePoll;
+ public int numSourceOther;
+ public int numSourceLocal;
+ public int numSourceUser;
+ public int numSourcePeriodic;
+ public int numSourceFeed;
+ public int numFailures;
+ public int numCancels;
+
+ /** Copy all the stats to another instance. */
+ public void copyTo(Stats to) {
+ to.totalElapsedTime = totalElapsedTime;
+ to.numSyncs = numSyncs;
+ to.numSourcePoll = numSourcePoll;
+ to.numSourceOther = numSourceOther;
+ to.numSourceLocal = numSourceLocal;
+ to.numSourceUser = numSourceUser;
+ to.numSourcePeriodic = numSourcePeriodic;
+ to.numSourceFeed = numSourceFeed;
+ to.numFailures = numFailures;
+ to.numCancels = numCancels;
+ }
+
+ /** Clear all the stats. */
+ public void clear() {
+ totalElapsedTime = 0;
+ numSyncs = 0;
+ numSourcePoll = 0;
+ numSourceOther = 0;
+ numSourceLocal = 0;
+ numSourceUser = 0;
+ numSourcePeriodic = 0;
+ numSourceFeed = 0;
+ numFailures = 0;
+ numCancels = 0;
+ }
+
+ /** Write all the stats to a parcel. */
+ public void writeToParcel(Parcel parcel) {
+ parcel.writeLong(totalElapsedTime);
+ parcel.writeInt(numSyncs);
+ parcel.writeInt(numSourcePoll);
+ parcel.writeInt(numSourceOther);
+ parcel.writeInt(numSourceLocal);
+ parcel.writeInt(numSourceUser);
+ parcel.writeInt(numSourcePeriodic);
+ parcel.writeInt(numSourceFeed);
+ parcel.writeInt(numFailures);
+ parcel.writeInt(numCancels);
+ }
+
+ /** Read all the stats from a parcel. */
+ public void readFromParcel(Parcel parcel) {
+ totalElapsedTime = parcel.readLong();
+ numSyncs = parcel.readInt();
+ numSourcePoll = parcel.readInt();
+ numSourceOther = parcel.readInt();
+ numSourceLocal = parcel.readInt();
+ numSourceUser = parcel.readInt();
+ numSourcePeriodic = parcel.readInt();
+ numSourceFeed = parcel.readInt();
+ numFailures = parcel.readInt();
+ numCancels = parcel.readInt();
+ }
+ }
+
+ public long lastTodayResetTime;
+
+ public final Stats totalStats = new Stats();
+ public final Stats todayStats = new Stats();
+ public final Stats yesterdayStats = new Stats();
+
+ @UnsupportedAppUsage
+ public long lastSuccessTime;
+ @UnsupportedAppUsage
+ public int lastSuccessSource;
+ @UnsupportedAppUsage
+ public long lastFailureTime;
+ @UnsupportedAppUsage
+ public int lastFailureSource;
+ @UnsupportedAppUsage
+ public String lastFailureMesg;
+ @UnsupportedAppUsage
+ public long initialFailureTime;
+ @UnsupportedAppUsage
+ public boolean pending;
+ @UnsupportedAppUsage
+ public boolean initialize;
+
+ public final long[] perSourceLastSuccessTimes = new long[SOURCE_COUNT];
+ public final long[] perSourceLastFailureTimes = new long[SOURCE_COUNT];
+
+ // Warning: It is up to the external caller to ensure there are
+ // no race conditions when accessing this list
+ @UnsupportedAppUsage
+ private ArrayList<Long> periodicSyncTimes;
+
+ private final ArrayList<Long> mLastEventTimes = new ArrayList<>();
+ private final ArrayList<String> mLastEvents = new ArrayList<>();
+
+ @UnsupportedAppUsage
+ public SyncStatusInfo(int authorityId) {
+ this.authorityId = authorityId;
+ }
+
+ @UnsupportedAppUsage
+ public int getLastFailureMesgAsInt(int def) {
+ final int i = ContentResolver.syncErrorStringToInt(lastFailureMesg);
+ if (i > 0) {
+ return i;
+ } else {
+ Log.d(TAG, "Unknown lastFailureMesg:" + lastFailureMesg);
+ return def;
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(VERSION);
+ parcel.writeInt(authorityId);
+
+ // Note we can't use Stats.writeToParcel() here; see the below constructor for the reason.
+ parcel.writeLong(totalStats.totalElapsedTime);
+ parcel.writeInt(totalStats.numSyncs);
+ parcel.writeInt(totalStats.numSourcePoll);
+ parcel.writeInt(totalStats.numSourceOther);
+ parcel.writeInt(totalStats.numSourceLocal);
+ parcel.writeInt(totalStats.numSourceUser);
+
+ parcel.writeLong(lastSuccessTime);
+ parcel.writeInt(lastSuccessSource);
+ parcel.writeLong(lastFailureTime);
+ parcel.writeInt(lastFailureSource);
+ parcel.writeString(lastFailureMesg);
+ parcel.writeLong(initialFailureTime);
+ parcel.writeInt(pending ? 1 : 0);
+ parcel.writeInt(initialize ? 1 : 0);
+ if (periodicSyncTimes != null) {
+ parcel.writeInt(periodicSyncTimes.size());
+ for (long periodicSyncTime : periodicSyncTimes) {
+ parcel.writeLong(periodicSyncTime);
+ }
+ } else {
+ parcel.writeInt(-1);
+ }
+ parcel.writeInt(mLastEventTimes.size());
+ for (int i = 0; i < mLastEventTimes.size(); i++) {
+ parcel.writeLong(mLastEventTimes.get(i));
+ parcel.writeString(mLastEvents.get(i));
+ }
+ // Version 4
+ parcel.writeInt(totalStats.numSourcePeriodic);
+
+ // Version 5
+ parcel.writeInt(totalStats.numSourceFeed);
+ parcel.writeInt(totalStats.numFailures);
+ parcel.writeInt(totalStats.numCancels);
+
+ parcel.writeLong(lastTodayResetTime);
+
+ todayStats.writeToParcel(parcel);
+ yesterdayStats.writeToParcel(parcel);
+
+ // Version 6.
+ parcel.writeLongArray(perSourceLastSuccessTimes);
+ parcel.writeLongArray(perSourceLastFailureTimes);
+ }
+
+ @UnsupportedAppUsage
+ public SyncStatusInfo(Parcel parcel) {
+ int version = parcel.readInt();
+ if (version != VERSION && version != 1) {
+ Log.w("SyncStatusInfo", "Unknown version: " + version);
+ }
+ authorityId = parcel.readInt();
+
+ // Note we can't use Stats.writeToParcel() here because the data is persisted and we need
+ // to be able to read from the old format too.
+ totalStats.totalElapsedTime = parcel.readLong();
+ totalStats.numSyncs = parcel.readInt();
+ totalStats.numSourcePoll = parcel.readInt();
+ totalStats.numSourceOther = parcel.readInt();
+ totalStats.numSourceLocal = parcel.readInt();
+ totalStats.numSourceUser = parcel.readInt();
+ lastSuccessTime = parcel.readLong();
+ lastSuccessSource = parcel.readInt();
+ lastFailureTime = parcel.readLong();
+ lastFailureSource = parcel.readInt();
+ lastFailureMesg = parcel.readString();
+ initialFailureTime = parcel.readLong();
+ pending = parcel.readInt() != 0;
+ initialize = parcel.readInt() != 0;
+ if (version == 1) {
+ periodicSyncTimes = null;
+ } else {
+ final int count = parcel.readInt();
+ if (count < 0) {
+ periodicSyncTimes = null;
+ } else {
+ periodicSyncTimes = new ArrayList<Long>();
+ for (int i = 0; i < count; i++) {
+ periodicSyncTimes.add(parcel.readLong());
+ }
+ }
+ if (version >= 3) {
+ mLastEventTimes.clear();
+ mLastEvents.clear();
+ final int nEvents = parcel.readInt();
+ for (int i = 0; i < nEvents; i++) {
+ mLastEventTimes.add(parcel.readLong());
+ mLastEvents.add(parcel.readString());
+ }
+ }
+ }
+ if (version < 4) {
+ // Before version 4, numSourcePeriodic wasn't persisted.
+ totalStats.numSourcePeriodic =
+ totalStats.numSyncs - totalStats.numSourceLocal - totalStats.numSourcePoll
+ - totalStats.numSourceOther
+ - totalStats.numSourceUser;
+ if (totalStats.numSourcePeriodic < 0) { // Consistency check.
+ totalStats.numSourcePeriodic = 0;
+ }
+ } else {
+ totalStats.numSourcePeriodic = parcel.readInt();
+ }
+ if (version >= 5) {
+ totalStats.numSourceFeed = parcel.readInt();
+ totalStats.numFailures = parcel.readInt();
+ totalStats.numCancels = parcel.readInt();
+
+ lastTodayResetTime = parcel.readLong();
+
+ todayStats.readFromParcel(parcel);
+ yesterdayStats.readFromParcel(parcel);
+ }
+ if (version >= 6) {
+ parcel.readLongArray(perSourceLastSuccessTimes);
+ parcel.readLongArray(perSourceLastFailureTimes);
+ }
+ }
+
+ /**
+ * Copies all data from the given SyncStatusInfo object.
+ *
+ * @param other the SyncStatusInfo object to copy data from
+ */
+ public SyncStatusInfo(SyncStatusInfo other) {
+ authorityId = other.authorityId;
+ copyFrom(other);
+ }
+
+ /**
+ * Copies all data from the given SyncStatusInfo object except for its authority id.
+ *
+ * @param authorityId the new authority id
+ * @param other the SyncStatusInfo object to copy data from
+ */
+ public SyncStatusInfo(int authorityId, SyncStatusInfo other) {
+ this.authorityId = authorityId;
+ copyFrom(other);
+ }
+
+ private void copyFrom(SyncStatusInfo other) {
+ other.totalStats.copyTo(totalStats);
+ other.todayStats.copyTo(todayStats);
+ other.yesterdayStats.copyTo(yesterdayStats);
+
+ lastTodayResetTime = other.lastTodayResetTime;
+
+ lastSuccessTime = other.lastSuccessTime;
+ lastSuccessSource = other.lastSuccessSource;
+ lastFailureTime = other.lastFailureTime;
+ lastFailureSource = other.lastFailureSource;
+ lastFailureMesg = other.lastFailureMesg;
+ initialFailureTime = other.initialFailureTime;
+ pending = other.pending;
+ initialize = other.initialize;
+ if (other.periodicSyncTimes != null) {
+ periodicSyncTimes = new ArrayList<Long>(other.periodicSyncTimes);
+ }
+ mLastEventTimes.addAll(other.mLastEventTimes);
+ mLastEvents.addAll(other.mLastEvents);
+
+ copy(perSourceLastSuccessTimes, other.perSourceLastSuccessTimes);
+ copy(perSourceLastFailureTimes, other.perSourceLastFailureTimes);
+ }
+
+ private static void copy(long[] to, long[] from) {
+ System.arraycopy(from, 0, to, 0, to.length);
+ }
+
+ public int getPeriodicSyncTimesSize() {
+ return periodicSyncTimes == null ? 0 : periodicSyncTimes.size();
+ }
+
+ public void addPeriodicSyncTime(long time) {
+ periodicSyncTimes = ArrayUtils.add(periodicSyncTimes, time);
+ }
+
+ @UnsupportedAppUsage
+ public void setPeriodicSyncTime(int index, long when) {
+ // The list is initialized lazily when scheduling occurs so we need to make sure
+ // we initialize elements < index to zero (zero is ignore for scheduling purposes)
+ ensurePeriodicSyncTimeSize(index);
+ periodicSyncTimes.set(index, when);
+ }
+
+ @UnsupportedAppUsage
+ public long getPeriodicSyncTime(int index) {
+ if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
+ return periodicSyncTimes.get(index);
+ } else {
+ return 0;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public void removePeriodicSyncTime(int index) {
+ if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
+ periodicSyncTimes.remove(index);
+ }
+ }
+
+ /**
+ * Populates {@code mLastEventTimes} and {@code mLastEvents} with the given list. <br>
+ * <i>Note: This method is mainly used to repopulate the event info from disk and it will clear
+ * both {@code mLastEventTimes} and {@code mLastEvents} before populating.</i>
+ *
+ * @param lastEventInformation the list to populate with
+ */
+ public void populateLastEventsInformation(ArrayList<Pair<Long, String>> lastEventInformation) {
+ mLastEventTimes.clear();
+ mLastEvents.clear();
+ final int size = lastEventInformation.size();
+ for (int i = 0; i < size; i++) {
+ final Pair<Long, String> lastEventInfo = lastEventInformation.get(i);
+ mLastEventTimes.add(lastEventInfo.first);
+ mLastEvents.add(lastEventInfo.second);
+ }
+ }
+
+ /** */
+ public void addEvent(String message) {
+ if (mLastEventTimes.size() >= MAX_EVENT_COUNT) {
+ mLastEventTimes.remove(MAX_EVENT_COUNT - 1);
+ mLastEvents.remove(MAX_EVENT_COUNT - 1);
+ }
+ mLastEventTimes.add(0, System.currentTimeMillis());
+ mLastEvents.add(0, message);
+ }
+
+ /** */
+ public int getEventCount() {
+ return mLastEventTimes.size();
+ }
+
+ /** */
+ public long getEventTime(int i) {
+ return mLastEventTimes.get(i);
+ }
+
+ /** */
+ public String getEvent(int i) {
+ return mLastEvents.get(i);
+ }
+
+ /** Call this when a sync has succeeded. */
+ public void setLastSuccess(int source, long lastSyncTime) {
+ lastSuccessTime = lastSyncTime;
+ lastSuccessSource = source;
+ lastFailureTime = 0;
+ lastFailureSource = -1;
+ lastFailureMesg = null;
+ initialFailureTime = 0;
+
+ if (0 <= source && source < perSourceLastSuccessTimes.length) {
+ perSourceLastSuccessTimes[source] = lastSyncTime;
+ }
+ }
+
+ /** Call this when a sync has failed. */
+ public void setLastFailure(int source, long lastSyncTime, String failureMessage) {
+ lastFailureTime = lastSyncTime;
+ lastFailureSource = source;
+ lastFailureMesg = failureMessage;
+ if (initialFailureTime == 0) {
+ initialFailureTime = lastSyncTime;
+ }
+
+ if (0 <= source && source < perSourceLastFailureTimes.length) {
+ perSourceLastFailureTimes[source] = lastSyncTime;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
+ public SyncStatusInfo createFromParcel(Parcel in) {
+ return new SyncStatusInfo(in);
+ }
+
+ public SyncStatusInfo[] newArray(int size) {
+ return new SyncStatusInfo[size];
+ }
+ };
+
+ @UnsupportedAppUsage
+ private void ensurePeriodicSyncTimeSize(int index) {
+ if (periodicSyncTimes == null) {
+ periodicSyncTimes = new ArrayList<Long>(0);
+ }
+
+ final int requiredSize = index + 1;
+ if (periodicSyncTimes.size() < requiredSize) {
+ for (int i = periodicSyncTimes.size(); i < requiredSize; i++) {
+ periodicSyncTimes.add((long) 0);
+ }
+ }
+ }
+
+ /**
+ * If the last reset was not today, move today's stats to yesterday's and clear today's.
+ */
+ public void maybeResetTodayStats(boolean clockValid, boolean force) {
+ final long now = System.currentTimeMillis();
+
+ if (!force) {
+ // Last reset was the same day, nothing to do.
+ if (areSameDates(now, lastTodayResetTime)) {
+ return;
+ }
+
+ // Hack -- on devices with no RTC, until the NTP kicks in, the device won't have the
+ // correct time. So if the time goes back, don't reset, unless we're sure the current
+ // time is correct.
+ if (now < lastTodayResetTime && !clockValid) {
+ return;
+ }
+ }
+
+ lastTodayResetTime = now;
+
+ todayStats.copyTo(yesterdayStats);
+ todayStats.clear();
+ }
+
+ private static boolean areSameDates(long time1, long time2) {
+ final Calendar c1 = new GregorianCalendar();
+ final Calendar c2 = new GregorianCalendar();
+
+ c1.setTimeInMillis(time1);
+ c2.setTimeInMillis(time2);
+
+ return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
+ && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
+ }
+}
diff --git a/android/content/SyncStatusObserver.java b/android/content/SyncStatusObserver.java
new file mode 100644
index 0000000..663378a
--- /dev/null
+++ b/android/content/SyncStatusObserver.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2009 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;
+
+public interface SyncStatusObserver {
+ void onStatusChanged(int which);
+}
diff --git a/android/content/UndoManager.java b/android/content/UndoManager.java
new file mode 100644
index 0000000..87afbf8
--- /dev/null
+++ b/android/content/UndoManager.java
@@ -0,0 +1,957 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+
+/**
+ * Top-level class for managing and interacting with the global undo state for
+ * a document or application. This class supports both undo and redo and has
+ * helpers for merging undoable operations together as they are performed.
+ *
+ * <p>A single undoable operation is represented by {@link UndoOperation} which
+ * apps implement to define their undo/redo behavior. The UndoManager keeps
+ * a stack of undo states; each state can have one or more undo operations
+ * inside of it.</p>
+ *
+ * <p>Updates to the stack must be done inside of a {@link #beginUpdate}/{@link #endUpdate()}
+ * pair. During this time you can add new operations to the stack with
+ * {@link #addOperation}, retrieve and modify existing operations with
+ * {@link #getLastOperation}, control the label shown to the user for this operation
+ * with {@link #setUndoLabel} and {@link #suggestUndoLabel}, etc.</p>
+ *
+ * <p>Every {link UndoOperation} is associated with an {@link UndoOwner}, which identifies
+ * the data it belongs to. The owner is used to indicate how operations are dependent
+ * on each other -- operations with the same owner are dependent on others with the
+ * same owner. For example, you may have a document with multiple embedded objects. If the
+ * document itself and each embedded object use different owners, then you
+ * can provide undo semantics appropriate to the user's context: while within
+ * an embedded object, only edits to that object are seen and the user can
+ * undo/redo them without needing to impact edits in other objects; while
+ * within the larger document, all edits can be seen and the user must
+ * undo/redo them as a single stream.</p>
+ *
+ * @hide
+ */
+public class UndoManager {
+ // The common case is a single undo owner (e.g. for a TextView), so default to that capacity.
+ private final ArrayMap<String, UndoOwner> mOwners =
+ new ArrayMap<String, UndoOwner>(1 /* capacity */);
+ private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>();
+ private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>();
+ private int mUpdateCount;
+ private int mHistorySize = 20;
+ private UndoState mWorking;
+ private int mCommitId = 1;
+ private boolean mInUndo;
+ private boolean mMerged;
+
+ private int mStateSeq;
+ private int mNextSavedIdx;
+ private UndoOwner[] mStateOwners;
+
+ /**
+ * Never merge with the last undo state.
+ */
+ public static final int MERGE_MODE_NONE = 0;
+
+ /**
+ * Allow merge with the last undo state only if it contains
+ * operations with the caller's owner.
+ */
+ public static final int MERGE_MODE_UNIQUE = 1;
+
+ /**
+ * Always allow merge with the last undo state, if possible.
+ */
+ public static final int MERGE_MODE_ANY = 2;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public UndoManager() {
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public UndoOwner getOwner(String tag, Object data) {
+ if (tag == null) {
+ throw new NullPointerException("tag can't be null");
+ }
+ if (data == null) {
+ throw new NullPointerException("data can't be null");
+ }
+ UndoOwner owner = mOwners.get(tag);
+ if (owner != null) {
+ if (owner.mData != data) {
+ if (owner.mData != null) {
+ throw new IllegalStateException("Owner " + owner + " already exists with data "
+ + owner.mData + " but giving different data " + data);
+ }
+ owner.mData = data;
+ }
+ return owner;
+ }
+
+ owner = new UndoOwner(tag, this);
+ owner.mData = data;
+ mOwners.put(tag, owner);
+ return owner;
+ }
+
+ void removeOwner(UndoOwner owner) {
+ // XXX need to figure out how to prune.
+ if (false) {
+ mOwners.remove(owner.mTag);
+ }
+ }
+
+ /**
+ * Flatten the current undo state into a Parcel object, which can later be restored
+ * with {@link #restoreInstanceState(android.os.Parcel, java.lang.ClassLoader)}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void saveInstanceState(Parcel p) {
+ if (mUpdateCount > 0) {
+ throw new IllegalStateException("Can't save state while updating");
+ }
+ mStateSeq++;
+ if (mStateSeq <= 0) {
+ mStateSeq = 0;
+ }
+ mNextSavedIdx = 0;
+ p.writeInt(mHistorySize);
+ p.writeInt(mOwners.size());
+ // XXX eventually we need to be smart here about limiting the
+ // number of undo states we write to not exceed X bytes.
+ int i = mUndos.size();
+ while (i > 0) {
+ p.writeInt(1);
+ i--;
+ mUndos.get(i).writeToParcel(p);
+ }
+ i = mRedos.size();
+ while (i > 0) {
+ p.writeInt(2);
+ i--;
+ mRedos.get(i).writeToParcel(p);
+ }
+ p.writeInt(0);
+ }
+
+ void saveOwner(UndoOwner owner, Parcel out) {
+ if (owner.mStateSeq == mStateSeq) {
+ out.writeInt(owner.mSavedIdx);
+ } else {
+ owner.mStateSeq = mStateSeq;
+ owner.mSavedIdx = mNextSavedIdx;
+ out.writeInt(owner.mSavedIdx);
+ out.writeString(owner.mTag);
+ out.writeInt(owner.mOpCount);
+ mNextSavedIdx++;
+ }
+ }
+
+ /**
+ * Restore an undo state previously created with {@link #saveInstanceState(Parcel)}. This
+ * will restore the UndoManager's state to almost exactly what it was at the point it had
+ * been previously saved; the only information not restored is the data object
+ * associated with each {@link UndoOwner}, which requires separate calls to
+ * {@link #getOwner(String, Object)} to re-associate the owner with its data.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void restoreInstanceState(Parcel p, ClassLoader loader) {
+ if (mUpdateCount > 0) {
+ throw new IllegalStateException("Can't save state while updating");
+ }
+ forgetUndos(null, -1);
+ forgetRedos(null, -1);
+ mHistorySize = p.readInt();
+ mStateOwners = new UndoOwner[p.readInt()];
+
+ int stype;
+ while ((stype=p.readInt()) != 0) {
+ UndoState ustate = new UndoState(this, p, loader);
+ if (stype == 1) {
+ mUndos.add(0, ustate);
+ } else {
+ mRedos.add(0, ustate);
+ }
+ }
+ }
+
+ UndoOwner restoreOwner(Parcel in) {
+ int idx = in.readInt();
+ UndoOwner owner = mStateOwners[idx];
+ if (owner == null) {
+ String tag = in.readString();
+ int opCount = in.readInt();
+ owner = new UndoOwner(tag, this);
+ owner.mOpCount = opCount;
+ mStateOwners[idx] = owner;
+ mOwners.put(tag, owner);
+ }
+ return owner;
+ }
+
+ /**
+ * Set the maximum number of undo states that will be retained.
+ */
+ public void setHistorySize(int size) {
+ mHistorySize = size;
+ if (mHistorySize >= 0 && countUndos(null) > mHistorySize) {
+ forgetUndos(null, countUndos(null) - mHistorySize);
+ }
+ }
+
+ /**
+ * Return the current maximum number of undo states.
+ */
+ public int getHistorySize() {
+ return mHistorySize;
+ }
+
+ /**
+ * Perform undo of last/top <var>count</var> undo states. The states impacted
+ * by this can be limited through <var>owners</var>.
+ * @param owners Optional set of owners that should be impacted. If null, all
+ * undo states will be visible and available for undo. If non-null, only those
+ * states that contain one of the owners specified here will be visible.
+ * @param count Number of undo states to pop.
+ * @return Returns the number of undo states that were actually popped.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int undo(UndoOwner[] owners, int count) {
+ if (mWorking != null) {
+ throw new IllegalStateException("Can't be called during an update");
+ }
+
+ int num = 0;
+ int i = -1;
+
+ mInUndo = true;
+
+ UndoState us = getTopUndo(null);
+ if (us != null) {
+ us.makeExecuted();
+ }
+
+ while (count > 0 && (i=findPrevState(mUndos, owners, i)) >= 0) {
+ UndoState state = mUndos.remove(i);
+ state.undo();
+ mRedos.add(state);
+ count--;
+ num++;
+ }
+
+ mInUndo = false;
+
+ return num;
+ }
+
+ /**
+ * Perform redo of last/top <var>count</var> undo states in the transient redo stack.
+ * The states impacted by this can be limited through <var>owners</var>.
+ * @param owners Optional set of owners that should be impacted. If null, all
+ * undo states will be visible and available for undo. If non-null, only those
+ * states that contain one of the owners specified here will be visible.
+ * @param count Number of undo states to pop.
+ * @return Returns the number of undo states that were actually redone.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int redo(UndoOwner[] owners, int count) {
+ if (mWorking != null) {
+ throw new IllegalStateException("Can't be called during an update");
+ }
+
+ int num = 0;
+ int i = -1;
+
+ mInUndo = true;
+
+ while (count > 0 && (i=findPrevState(mRedos, owners, i)) >= 0) {
+ UndoState state = mRedos.remove(i);
+ state.redo();
+ mUndos.add(state);
+ count--;
+ num++;
+ }
+
+ mInUndo = false;
+
+ return num;
+ }
+
+ /**
+ * Returns true if we are currently inside of an undo/redo operation. This is
+ * useful for editors to know whether they should be generating new undo state
+ * when they see edit operations happening.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean isInUndo() {
+ return mInUndo;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int forgetUndos(UndoOwner[] owners, int count) {
+ if (count < 0) {
+ count = mUndos.size();
+ }
+
+ int removed = 0;
+ int i = 0;
+ while (i < mUndos.size() && removed < count) {
+ UndoState state = mUndos.get(i);
+ if (count > 0 && matchOwners(state, owners)) {
+ state.destroy();
+ mUndos.remove(i);
+ removed++;
+ } else {
+ i++;
+ }
+ }
+
+ return removed;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int forgetRedos(UndoOwner[] owners, int count) {
+ if (count < 0) {
+ count = mRedos.size();
+ }
+
+ int removed = 0;
+ int i = 0;
+ while (i < mRedos.size() && removed < count) {
+ UndoState state = mRedos.get(i);
+ if (count > 0 && matchOwners(state, owners)) {
+ state.destroy();
+ mRedos.remove(i);
+ removed++;
+ } else {
+ i++;
+ }
+ }
+
+ return removed;
+ }
+
+ /**
+ * Return the number of undo states on the undo stack.
+ * @param owners If non-null, only those states containing an operation with one of
+ * the owners supplied here will be counted.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int countUndos(UndoOwner[] owners) {
+ if (owners == null) {
+ return mUndos.size();
+ }
+
+ int count=0;
+ int i=0;
+ while ((i=findNextState(mUndos, owners, i)) >= 0) {
+ count++;
+ i++;
+ }
+ return count;
+ }
+
+ /**
+ * Return the number of redo states on the undo stack.
+ * @param owners If non-null, only those states containing an operation with one of
+ * the owners supplied here will be counted.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int countRedos(UndoOwner[] owners) {
+ if (owners == null) {
+ return mRedos.size();
+ }
+
+ int count=0;
+ int i=0;
+ while ((i=findNextState(mRedos, owners, i)) >= 0) {
+ count++;
+ i++;
+ }
+ return count;
+ }
+
+ /**
+ * Return the user-visible label for the top undo state on the stack.
+ * @param owners If non-null, will select the top-most undo state containing an
+ * operation with one of the owners supplied here.
+ */
+ public CharSequence getUndoLabel(UndoOwner[] owners) {
+ UndoState state = getTopUndo(owners);
+ return state != null ? state.getLabel() : null;
+ }
+
+ /**
+ * Return the user-visible label for the top redo state on the stack.
+ * @param owners If non-null, will select the top-most undo state containing an
+ * operation with one of the owners supplied here.
+ */
+ public CharSequence getRedoLabel(UndoOwner[] owners) {
+ UndoState state = getTopRedo(owners);
+ return state != null ? state.getLabel() : null;
+ }
+
+ /**
+ * Start creating a new undo state. Multiple calls to this function will nest until
+ * they are all matched by a later call to {@link #endUpdate}.
+ * @param label Optional user-visible label for this new undo state.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void beginUpdate(CharSequence label) {
+ if (mInUndo) {
+ throw new IllegalStateException("Can't being update while performing undo/redo");
+ }
+ if (mUpdateCount <= 0) {
+ createWorkingState();
+ mMerged = false;
+ mUpdateCount = 0;
+ }
+
+ mWorking.updateLabel(label);
+ mUpdateCount++;
+ }
+
+ private void createWorkingState() {
+ mWorking = new UndoState(this, mCommitId++);
+ if (mCommitId < 0) {
+ mCommitId = 1;
+ }
+ }
+
+ /**
+ * Returns true if currently inside of a {@link #beginUpdate}.
+ */
+ public boolean isInUpdate() {
+ return mUpdateCount > 0;
+ }
+
+ /**
+ * Forcibly set a new for the new undo state being built within a {@link #beginUpdate}.
+ * Any existing label will be replaced with this one.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void setUndoLabel(CharSequence label) {
+ if (mWorking == null) {
+ throw new IllegalStateException("Must be called during an update");
+ }
+ mWorking.setLabel(label);
+ }
+
+ /**
+ * Set a new for the new undo state being built within a {@link #beginUpdate}, but
+ * only if there is not a label currently set for it.
+ */
+ public void suggestUndoLabel(CharSequence label) {
+ if (mWorking == null) {
+ throw new IllegalStateException("Must be called during an update");
+ }
+ mWorking.updateLabel(label);
+ }
+
+ /**
+ * Return the number of times {@link #beginUpdate} has been called without a matching
+ * {@link #endUpdate} call.
+ */
+ public int getUpdateNestingLevel() {
+ return mUpdateCount;
+ }
+
+ /**
+ * Check whether there is an {@link UndoOperation} in the current {@link #beginUpdate}
+ * undo state.
+ * @param owner Optional owner of the operation to look for. If null, will succeed
+ * if there is any operation; if non-null, will only succeed if there is an operation
+ * with the given owner.
+ * @return Returns true if there is a matching operation in the current undo state.
+ */
+ public boolean hasOperation(UndoOwner owner) {
+ if (mWorking == null) {
+ throw new IllegalStateException("Must be called during an update");
+ }
+ return mWorking.hasOperation(owner);
+ }
+
+ /**
+ * Return the most recent {@link UndoOperation} that was added to the update.
+ * @param mergeMode May be either {@link #MERGE_MODE_NONE} or {@link #MERGE_MODE_ANY}.
+ */
+ public UndoOperation<?> getLastOperation(int mergeMode) {
+ return getLastOperation(null, null, mergeMode);
+ }
+
+ /**
+ * Return the most recent {@link UndoOperation} that was added to the update and
+ * has the given owner.
+ * @param owner Optional owner of last operation to retrieve. If null, the last
+ * operation regardless of owner will be retrieved; if non-null, the last operation
+ * matching the given owner will be retrieved.
+ * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE},
+ * or {@link #MERGE_MODE_ANY}.
+ */
+ public UndoOperation<?> getLastOperation(UndoOwner owner, int mergeMode) {
+ return getLastOperation(null, owner, mergeMode);
+ }
+
+ /**
+ * Return the most recent {@link UndoOperation} that was added to the update and
+ * has the given owner.
+ * @param clazz Optional class of the last operation to retrieve. If null, the
+ * last operation regardless of class will be retrieved; if non-null, the last
+ * operation whose class is the same as the given class will be retrieved.
+ * @param owner Optional owner of last operation to retrieve. If null, the last
+ * operation regardless of owner will be retrieved; if non-null, the last operation
+ * matching the given owner will be retrieved.
+ * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE},
+ * or {@link #MERGE_MODE_ANY}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public <T extends UndoOperation> T getLastOperation(Class<T> clazz, UndoOwner owner,
+ int mergeMode) {
+ if (mWorking == null) {
+ throw new IllegalStateException("Must be called during an update");
+ }
+ if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) {
+ UndoState state = getTopUndo(null);
+ UndoOperation<?> last;
+ if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners())
+ && state.canMerge() && (last=state.getLastOperation(clazz, owner)) != null) {
+ if (last.allowMerge()) {
+ mWorking.destroy();
+ mWorking = state;
+ mUndos.remove(state);
+ mMerged = true;
+ return (T)last;
+ }
+ }
+ }
+
+ return mWorking.getLastOperation(clazz, owner);
+ }
+
+ /**
+ * Add a new UndoOperation to the current update.
+ * @param op The new operation to add.
+ * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE},
+ * or {@link #MERGE_MODE_ANY}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void addOperation(UndoOperation<?> op, int mergeMode) {
+ if (mWorking == null) {
+ throw new IllegalStateException("Must be called during an update");
+ }
+ UndoOwner owner = op.getOwner();
+ if (owner.mManager != this) {
+ throw new IllegalArgumentException(
+ "Given operation's owner is not in this undo manager.");
+ }
+ if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) {
+ UndoState state = getTopUndo(null);
+ if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners())
+ && state.canMerge() && state.hasOperation(op.getOwner())) {
+ mWorking.destroy();
+ mWorking = state;
+ mUndos.remove(state);
+ mMerged = true;
+ }
+ }
+ mWorking.addOperation(op);
+ }
+
+ /**
+ * Finish the creation of an undo state, matching a previous call to
+ * {@link #beginUpdate}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void endUpdate() {
+ if (mWorking == null) {
+ throw new IllegalStateException("Must be called during an update");
+ }
+ mUpdateCount--;
+
+ if (mUpdateCount == 0) {
+ pushWorkingState();
+ }
+ }
+
+ private void pushWorkingState() {
+ int N = mUndos.size() + 1;
+
+ if (mWorking.hasData()) {
+ mUndos.add(mWorking);
+ forgetRedos(null, -1);
+ mWorking.commit();
+ if (N >= 2) {
+ // The state before this one can no longer be merged, ever.
+ // The only way to get back to it is for the user to perform
+ // an undo.
+ mUndos.get(N-2).makeExecuted();
+ }
+ } else {
+ mWorking.destroy();
+ }
+ mWorking = null;
+
+ if (mHistorySize >= 0 && N > mHistorySize) {
+ forgetUndos(null, N - mHistorySize);
+ }
+ }
+
+ /**
+ * Commit the last finished undo state. This undo state can no longer be
+ * modified with further {@link #MERGE_MODE_UNIQUE} or
+ * {@link #MERGE_MODE_ANY} merge modes. If called while inside of an update,
+ * this will push any changes in the current update on to the undo stack
+ * and result with a fresh undo state, behaving as if {@link #endUpdate()}
+ * had been called enough to unwind the current update, then the last state
+ * committed, and {@link #beginUpdate} called to restore the update nesting.
+ * @param owner The optional owner to determine whether to perform the commit.
+ * If this is non-null, the commit will only execute if the current top undo
+ * state contains an operation with the given owner.
+ * @return Returns an integer identifier for the committed undo state, which
+ * can later be used to try to uncommit the state to perform further edits on it.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int commitState(UndoOwner owner) {
+ if (mWorking != null && mWorking.hasData()) {
+ if (owner == null || mWorking.hasOperation(owner)) {
+ mWorking.setCanMerge(false);
+ int commitId = mWorking.getCommitId();
+ pushWorkingState();
+ createWorkingState();
+ mMerged = true;
+ return commitId;
+ }
+ } else {
+ UndoState state = getTopUndo(null);
+ if (state != null && (owner == null || state.hasOperation(owner))) {
+ state.setCanMerge(false);
+ return state.getCommitId();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Attempt to undo a previous call to {@link #commitState}. This will work
+ * if the undo state at the top of the stack has the given id, and has not been
+ * involved in an undo operation. Otherwise false is returned.
+ * @param commitId The identifier for the state to be uncommitted, as returned
+ * by {@link #commitState}.
+ * @param owner Optional owner that must appear in the committed state.
+ * @return Returns true if the uncommit is successful, else false.
+ */
+ public boolean uncommitState(int commitId, UndoOwner owner) {
+ if (mWorking != null && mWorking.getCommitId() == commitId) {
+ if (owner == null || mWorking.hasOperation(owner)) {
+ return mWorking.setCanMerge(true);
+ }
+ } else {
+ UndoState state = getTopUndo(null);
+ if (state != null && (owner == null || state.hasOperation(owner))) {
+ if (state.getCommitId() == commitId) {
+ return state.setCanMerge(true);
+ }
+ }
+ }
+ return false;
+ }
+
+ UndoState getTopUndo(UndoOwner[] owners) {
+ if (mUndos.size() <= 0) {
+ return null;
+ }
+ int i = findPrevState(mUndos, owners, -1);
+ return i >= 0 ? mUndos.get(i) : null;
+ }
+
+ UndoState getTopRedo(UndoOwner[] owners) {
+ if (mRedos.size() <= 0) {
+ return null;
+ }
+ int i = findPrevState(mRedos, owners, -1);
+ return i >= 0 ? mRedos.get(i) : null;
+ }
+
+ boolean matchOwners(UndoState state, UndoOwner[] owners) {
+ if (owners == null) {
+ return true;
+ }
+ for (int i=0; i<owners.length; i++) {
+ if (state.matchOwner(owners[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ int findPrevState(ArrayList<UndoState> states, UndoOwner[] owners, int from) {
+ final int N = states.size();
+
+ if (from == -1) {
+ from = N-1;
+ }
+ if (from >= N) {
+ return -1;
+ }
+ if (owners == null) {
+ return from;
+ }
+
+ while (from >= 0) {
+ UndoState state = states.get(from);
+ if (matchOwners(state, owners)) {
+ return from;
+ }
+ from--;
+ }
+
+ return -1;
+ }
+
+ int findNextState(ArrayList<UndoState> states, UndoOwner[] owners, int from) {
+ final int N = states.size();
+
+ if (from < 0) {
+ from = 0;
+ }
+ if (from >= N) {
+ return -1;
+ }
+ if (owners == null) {
+ return from;
+ }
+
+ while (from < N) {
+ UndoState state = states.get(from);
+ if (matchOwners(state, owners)) {
+ return from;
+ }
+ from++;
+ }
+
+ return -1;
+ }
+
+ final static class UndoState {
+ private final UndoManager mManager;
+ private final int mCommitId;
+ private final ArrayList<UndoOperation<?>> mOperations = new ArrayList<UndoOperation<?>>();
+ private ArrayList<UndoOperation<?>> mRecent;
+ private CharSequence mLabel;
+ private boolean mCanMerge = true;
+ private boolean mExecuted;
+
+ UndoState(UndoManager manager, int commitId) {
+ mManager = manager;
+ mCommitId = commitId;
+ }
+
+ UndoState(UndoManager manager, Parcel p, ClassLoader loader) {
+ mManager = manager;
+ mCommitId = p.readInt();
+ mCanMerge = p.readInt() != 0;
+ mExecuted = p.readInt() != 0;
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
+ final int N = p.readInt();
+ for (int i=0; i<N; i++) {
+ UndoOwner owner = mManager.restoreOwner(p);
+ UndoOperation op = (UndoOperation)p.readParcelable(loader);
+ op.mOwner = owner;
+ mOperations.add(op);
+ }
+ }
+
+ void writeToParcel(Parcel p) {
+ if (mRecent != null) {
+ throw new IllegalStateException("Can't save state before committing");
+ }
+ p.writeInt(mCommitId);
+ p.writeInt(mCanMerge ? 1 : 0);
+ p.writeInt(mExecuted ? 1 : 0);
+ TextUtils.writeToParcel(mLabel, p, 0);
+ final int N = mOperations.size();
+ p.writeInt(N);
+ for (int i=0; i<N; i++) {
+ UndoOperation op = mOperations.get(i);
+ mManager.saveOwner(op.mOwner, p);
+ p.writeParcelable(op, 0);
+ }
+ }
+
+ int getCommitId() {
+ return mCommitId;
+ }
+
+ void setLabel(CharSequence label) {
+ mLabel = label;
+ }
+
+ void updateLabel(CharSequence label) {
+ if (mLabel != null) {
+ mLabel = label;
+ }
+ }
+
+ CharSequence getLabel() {
+ return mLabel;
+ }
+
+ boolean setCanMerge(boolean state) {
+ // Don't allow re-enabling of merging if state has been executed.
+ if (state && mExecuted) {
+ return false;
+ }
+ mCanMerge = state;
+ return true;
+ }
+
+ void makeExecuted() {
+ mExecuted = true;
+ }
+
+ boolean canMerge() {
+ return mCanMerge && !mExecuted;
+ }
+
+ int countOperations() {
+ return mOperations.size();
+ }
+
+ boolean hasOperation(UndoOwner owner) {
+ final int N = mOperations.size();
+ if (owner == null) {
+ return N != 0;
+ }
+ for (int i=0; i<N; i++) {
+ if (mOperations.get(i).getOwner() == owner) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean hasMultipleOwners() {
+ final int N = mOperations.size();
+ if (N <= 1) {
+ return false;
+ }
+ UndoOwner owner = mOperations.get(0).getOwner();
+ for (int i=1; i<N; i++) {
+ if (mOperations.get(i).getOwner() != owner) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void addOperation(UndoOperation<?> op) {
+ if (mOperations.contains(op)) {
+ throw new IllegalStateException("Already holds " + op);
+ }
+ mOperations.add(op);
+ if (mRecent == null) {
+ mRecent = new ArrayList<UndoOperation<?>>();
+ mRecent.add(op);
+ }
+ op.mOwner.mOpCount++;
+ }
+
+ <T extends UndoOperation> T getLastOperation(Class<T> clazz, UndoOwner owner) {
+ final int N = mOperations.size();
+ if (clazz == null && owner == null) {
+ return N > 0 ? (T)mOperations.get(N-1) : null;
+ }
+ // First look for the top-most operation with the same owner.
+ for (int i=N-1; i>=0; i--) {
+ UndoOperation<?> op = mOperations.get(i);
+ if (owner != null && op.getOwner() != owner) {
+ continue;
+ }
+ // Return this operation if it has the same class that the caller wants.
+ // Note that we don't search deeper for the class, because we don't want
+ // to end up with a different order of operations for the same owner.
+ if (clazz != null && op.getClass() != clazz) {
+ return null;
+ }
+ return (T)op;
+ }
+
+ return null;
+ }
+
+ boolean matchOwner(UndoOwner owner) {
+ for (int i=mOperations.size()-1; i>=0; i--) {
+ if (mOperations.get(i).matchOwner(owner)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean hasData() {
+ for (int i=mOperations.size()-1; i>=0; i--) {
+ if (mOperations.get(i).hasData()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void commit() {
+ final int N = mRecent != null ? mRecent.size() : 0;
+ for (int i=0; i<N; i++) {
+ mRecent.get(i).commit();
+ }
+ mRecent = null;
+ }
+
+ void undo() {
+ for (int i=mOperations.size()-1; i>=0; i--) {
+ mOperations.get(i).undo();
+ }
+ }
+
+ void redo() {
+ final int N = mOperations.size();
+ for (int i=0; i<N; i++) {
+ mOperations.get(i).redo();
+ }
+ }
+
+ void destroy() {
+ for (int i=mOperations.size()-1; i>=0; i--) {
+ UndoOwner owner = mOperations.get(i).mOwner;
+ owner.mOpCount--;
+ if (owner.mOpCount <= 0) {
+ if (owner.mOpCount < 0) {
+ throw new IllegalStateException("Underflow of op count on owner " + owner
+ + " in op " + mOperations.get(i));
+ }
+ mManager.removeOwner(owner);
+ }
+ }
+ }
+ }
+}
diff --git a/android/content/UndoOperation.java b/android/content/UndoOperation.java
new file mode 100644
index 0000000..25aeca3
--- /dev/null
+++ b/android/content/UndoOperation.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A single undoable operation. You must subclass this to implement the state
+ * and behavior for your operation. Instances of this class are placed and
+ * managed in an {@link UndoManager}.
+ *
+ * @hide
+ */
+public abstract class UndoOperation<DATA> implements Parcelable {
+ UndoOwner mOwner;
+
+ /**
+ * Create a new instance of the operation.
+ * @param owner Who owns the data being modified by this undo state; must be
+ * returned by {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public UndoOperation(UndoOwner owner) {
+ mOwner = owner;
+ }
+
+ /**
+ * Construct from a Parcel.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ protected UndoOperation(Parcel src, ClassLoader loader) {
+ }
+
+ /**
+ * Owning object as given to {@link #UndoOperation(UndoOwner)}.
+ */
+ public UndoOwner getOwner() {
+ return mOwner;
+ }
+
+ /**
+ * Synonym for {@link #getOwner()}.{@link android.content.UndoOwner#getData()}.
+ */
+ public DATA getOwnerData() {
+ return (DATA)mOwner.getData();
+ }
+
+ /**
+ * Return true if this undo operation is a member of the given owner.
+ * The default implementation is <code>owner == getOwner()</code>. You
+ * can override this to provide more sophisticated dependencies between
+ * owners.
+ */
+ public boolean matchOwner(UndoOwner owner) {
+ return owner == getOwner();
+ }
+
+ /**
+ * Return true if this operation actually contains modification data. The
+ * default implementation always returns true. If you return false, the
+ * operation will be dropped when the final undo state is being built.
+ */
+ public boolean hasData() {
+ return true;
+ }
+
+ /**
+ * Return true if this operation can be merged with a later operation.
+ * The default implementation always returns true.
+ */
+ public boolean allowMerge() {
+ return true;
+ }
+
+ /**
+ * Called when this undo state is being committed to the undo stack.
+ * The implementation should perform the initial edits and save any state that
+ * may be needed to undo them.
+ */
+ public abstract void commit();
+
+ /**
+ * Called when this undo state is being popped off the undo stack (in to
+ * the temporary redo stack). The implementation should remove the original
+ * edits and thus restore the target object to its prior value.
+ */
+ public abstract void undo();
+
+ /**
+ * Called when this undo state is being pushed back from the transient
+ * redo stack to the main undo stack. The implementation should re-apply
+ * the edits that were previously removed by {@link #undo}.
+ */
+ public abstract void redo();
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/content/UndoOwner.java b/android/content/UndoOwner.java
new file mode 100644
index 0000000..fd257ab
--- /dev/null
+++ b/android/content/UndoOwner.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+/**
+ * Representation of an owner of {@link UndoOperation} objects in an {@link UndoManager}.
+ *
+ * @hide
+ */
+public class UndoOwner {
+ final String mTag;
+ final UndoManager mManager;
+
+ Object mData;
+ int mOpCount;
+
+ // For saving/restoring state.
+ int mStateSeq;
+ int mSavedIdx;
+
+ UndoOwner(String tag, UndoManager manager) {
+ if (tag == null) {
+ throw new NullPointerException("tag can't be null");
+ }
+ if (manager == null) {
+ throw new NullPointerException("manager can't be null");
+ }
+ mTag = tag;
+ mManager = manager;
+ }
+
+ /**
+ * Return the unique tag name identifying this owner. This is the tag
+ * supplied to {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}
+ * and is immutable.
+ */
+ public String getTag() {
+ return mTag;
+ }
+
+ /**
+ * Return the actual data object of the owner. This is the data object
+ * supplied to {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}. An
+ * owner may have a null data if it was restored from a previously saved state with
+ * no getOwner call to associate it with its data.
+ */
+ public Object getData() {
+ return mData;
+ }
+
+ @Override
+ public String toString() {
+ return "UndoOwner:[mTag=" + mTag +
+ " mManager=" + mManager +
+ " mData=" + mData +
+ " mData=" + mData +
+ " mOpCount=" + mOpCount +
+ " mStateSeq=" + mStateSeq +
+ " mSavedIdx=" + mSavedIdx + "]";
+ }
+}
diff --git a/android/content/UriMatcher.java b/android/content/UriMatcher.java
new file mode 100644
index 0000000..7fa48f0
--- /dev/null
+++ b/android/content/UriMatcher.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2006 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.compat.annotation.UnsupportedAppUsage;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+Utility class to aid in matching URIs in content providers.
+
+<p>To use this class, build up a tree of <code>UriMatcher</code> objects.
+For example:
+<pre>
+ private static final int PEOPLE = 1;
+ private static final int PEOPLE_ID = 2;
+ private static final int PEOPLE_PHONES = 3;
+ private static final int PEOPLE_PHONES_ID = 4;
+ private static final int PEOPLE_CONTACTMETHODS = 7;
+ private static final int PEOPLE_CONTACTMETHODS_ID = 8;
+
+ private static final int DELETED_PEOPLE = 20;
+
+ private static final int PHONES = 9;
+ private static final int PHONES_ID = 10;
+ private static final int PHONES_FILTER = 14;
+
+ private static final int CONTACTMETHODS = 18;
+ private static final int CONTACTMETHODS_ID = 19;
+
+ private static final int CALLS = 11;
+ private static final int CALLS_ID = 12;
+ private static final int CALLS_FILTER = 15;
+
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ static
+ {
+ sURIMatcher.addURI("contacts", "people", PEOPLE);
+ sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
+ sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
+ sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
+ sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
+ sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
+ sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);
+ sURIMatcher.addURI("contacts", "phones", PHONES);
+ sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);
+ sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);
+ sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);
+ sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);
+ sURIMatcher.addURI("call_log", "calls", CALLS);
+ sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);
+ sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
+ }
+</pre>
+<p>Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start
+ with a leading slash. For example:
+<pre>
+ sURIMatcher.addURI("contacts", "/people", PEOPLE);
+</pre>
+<p>Then when you need to match against a URI, call {@link #match}, providing
+the URL that you have been given. You can use the result to build a query,
+return a type, insert or delete a row, or whatever you need, without duplicating
+all of the if-else logic that you would otherwise need. For example:
+<pre>
+ public String getType(Uri url)
+ {
+ int match = sURIMatcher.match(url);
+ switch (match)
+ {
+ case PEOPLE:
+ return "vnd.android.cursor.dir/person";
+ case PEOPLE_ID:
+ return "vnd.android.cursor.item/person";
+... snip ...
+ return "vnd.android.cursor.dir/snail-mail";
+ case PEOPLE_ADDRESS_ID:
+ return "vnd.android.cursor.item/snail-mail";
+ default:
+ return null;
+ }
+ }
+</pre>
+instead of:
+<pre>
+ public String getType(Uri url)
+ {
+ List<String> pathSegments = url.getPathSegments();
+ if (pathSegments.size() >= 2) {
+ if ("people".equals(pathSegments.get(1))) {
+ if (pathSegments.size() == 2) {
+ return "vnd.android.cursor.dir/person";
+ } else if (pathSegments.size() == 3) {
+ return "vnd.android.cursor.item/person";
+... snip ...
+ return "vnd.android.cursor.dir/snail-mail";
+ } else if (pathSegments.size() == 3) {
+ return "vnd.android.cursor.item/snail-mail";
+ }
+ }
+ }
+ return null;
+ }
+</pre>
+*/
+public class UriMatcher
+{
+ public static final int NO_MATCH = -1;
+ /**
+ * Creates the root node of the URI tree.
+ *
+ * @param code the code to match for the root URI
+ */
+ public UriMatcher(int code)
+ {
+ mCode = code;
+ mWhich = -1;
+ mChildren = new ArrayList<UriMatcher>();
+ mText = null;
+ }
+
+ private UriMatcher(int which, String text)
+ {
+ mCode = NO_MATCH;
+ mWhich = which;
+ mChildren = new ArrayList<UriMatcher>();
+ mText = text;
+ }
+
+ /**
+ * Add a URI to match, and the code to return when this URI is
+ * matched. URI nodes may be exact match string, the token "*"
+ * that matches any text, or the token "#" that matches only
+ * numbers.
+ * <p>
+ * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * this method will accept a leading slash in the path.
+ *
+ * @param authority the authority to match
+ * @param path the path to match. * may be used as a wild card for
+ * any text, and # may be used as a wild card for numbers.
+ * @param code the code that is returned when a URI is matched
+ * against the given components. Must be positive.
+ */
+ public void addURI(String authority, String path, int code)
+ {
+ if (code < 0) {
+ throw new IllegalArgumentException("code " + code + " is invalid: it must be positive");
+ }
+
+ String[] tokens = null;
+ if (path != null) {
+ String newPath = path;
+ // Strip leading slash if present.
+ if (path.length() > 1 && path.charAt(0) == '/') {
+ newPath = path.substring(1);
+ }
+ tokens = newPath.split("/");
+ }
+
+ int numTokens = tokens != null ? tokens.length : 0;
+ UriMatcher node = this;
+ for (int i = -1; i < numTokens; i++) {
+ String token = i < 0 ? authority : tokens[i];
+ ArrayList<UriMatcher> children = node.mChildren;
+ int numChildren = children.size();
+ UriMatcher child;
+ int j;
+ for (j = 0; j < numChildren; j++) {
+ child = children.get(j);
+ if (token.equals(child.mText)) {
+ node = child;
+ break;
+ }
+ }
+ if (j == numChildren) {
+ // Child not found, create it
+ child = createChild(token);
+ node.mChildren.add(child);
+ node = child;
+ }
+ }
+ node.mCode = code;
+ }
+
+ private static UriMatcher createChild(String token) {
+ switch (token) {
+ case "#":
+ return new UriMatcher(NUMBER, "#");
+ case "*":
+ return new UriMatcher(TEXT, "*");
+ default:
+ return new UriMatcher(EXACT, token);
+ }
+ }
+
+ /**
+ * Try to match against the path in a url.
+ *
+ * @param uri The url whose path we will match against.
+ *
+ * @return The code for the matched node (added using addURI),
+ * or -1 if there is no matched node.
+ */
+ public int match(Uri uri)
+ {
+ final List<String> pathSegments = uri.getPathSegments();
+ final int li = pathSegments.size();
+
+ UriMatcher node = this;
+
+ if (li == 0 && uri.getAuthority() == null) {
+ return this.mCode;
+ }
+
+ for (int i=-1; i<li; i++) {
+ String u = i < 0 ? uri.getAuthority() : pathSegments.get(i);
+ ArrayList<UriMatcher> list = node.mChildren;
+ if (list == null) {
+ break;
+ }
+ node = null;
+ int lj = list.size();
+ for (int j=0; j<lj; j++) {
+ UriMatcher n = list.get(j);
+ which_switch:
+ switch (n.mWhich) {
+ case EXACT:
+ if (n.mText.equals(u)) {
+ node = n;
+ }
+ break;
+ case NUMBER:
+ int lk = u.length();
+ for (int k=0; k<lk; k++) {
+ char c = u.charAt(k);
+ if (c < '0' || c > '9') {
+ break which_switch;
+ }
+ }
+ node = n;
+ break;
+ case TEXT:
+ node = n;
+ break;
+ }
+ if (node != null) {
+ break;
+ }
+ }
+ if (node == null) {
+ return NO_MATCH;
+ }
+ }
+
+ return node.mCode;
+ }
+
+ private static final int EXACT = 0;
+ private static final int NUMBER = 1;
+ private static final int TEXT = 2;
+
+ private int mCode;
+ private final int mWhich;
+ @UnsupportedAppUsage
+ private final String mText;
+ @UnsupportedAppUsage
+ private ArrayList<UriMatcher> mChildren;
+}
diff --git a/android/content/UriPermission.java b/android/content/UriPermission.java
new file mode 100644
index 0000000..d3a9cb8
--- /dev/null
+++ b/android/content/UriPermission.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Description of a single Uri permission grant. This grants may have been
+ * created via {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, etc when sending
+ * an {@link Intent}, or explicitly through
+ * {@link Context#grantUriPermission(String, android.net.Uri, int)}.
+ *
+ * @see ContentResolver#getPersistedUriPermissions()
+ */
+public final class UriPermission implements Parcelable {
+ private final Uri mUri;
+ private final int mModeFlags;
+ private final long mPersistedTime;
+
+ /**
+ * Value returned when a permission has not been persisted.
+ */
+ public static final long INVALID_TIME = Long.MIN_VALUE;
+
+ /** {@hide} */
+ public UriPermission(Uri uri, int modeFlags, long persistedTime) {
+ mUri = uri;
+ mModeFlags = modeFlags;
+ mPersistedTime = persistedTime;
+ }
+
+ /** {@hide} */
+ public UriPermission(Parcel in) {
+ mUri = in.readParcelable(null);
+ mModeFlags = in.readInt();
+ mPersistedTime = in.readLong();
+ }
+
+ /**
+ * Return the Uri this permission pertains to.
+ */
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Returns if this permission offers read access.
+ */
+ public boolean isReadPermission() {
+ return (mModeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
+ }
+
+ /**
+ * Returns if this permission offers write access.
+ */
+ public boolean isWritePermission() {
+ return (mModeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
+ }
+
+ /**
+ * Return the time when this permission was first persisted, in milliseconds
+ * since January 1, 1970 00:00:00.0 UTC. Returns {@link #INVALID_TIME} if
+ * not persisted.
+ *
+ * @see ContentResolver#takePersistableUriPermission(Uri, int)
+ * @see System#currentTimeMillis()
+ */
+ public long getPersistedTime() {
+ return mPersistedTime;
+ }
+
+ @Override
+ public String toString() {
+ return "UriPermission {uri=" + mUri + ", modeFlags=" + mModeFlags + ", persistedTime="
+ + mPersistedTime + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mUri, flags);
+ dest.writeInt(mModeFlags);
+ dest.writeLong(mPersistedTime);
+ }
+
+ public static final @android.annotation.NonNull Creator<UriPermission> CREATOR = new Creator<UriPermission>() {
+ @Override
+ public UriPermission createFromParcel(Parcel source) {
+ return new UriPermission(source);
+ }
+
+ @Override
+ public UriPermission[] newArray(int size) {
+ return new UriPermission[size];
+ }
+ };
+}
diff --git a/android/content/integrity/AppInstallMetadata.java b/android/content/integrity/AppInstallMetadata.java
new file mode 100644
index 0000000..4f38fae
--- /dev/null
+++ b/android/content/integrity/AppInstallMetadata.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2020 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.integrity;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The app install metadata.
+ *
+ * <p>The integrity component retrieves metadata for app installs from package manager, passing it
+ * to the rule evaluation engine to evaluate the metadata against the rules.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+public final class AppInstallMetadata {
+ private final String mPackageName;
+ // Raw string encoding for the SHA-256 hash of the certificate of the app.
+ private final List<String> mAppCertificates;
+ private final String mInstallerName;
+ // Raw string encoding for the SHA-256 hash of the certificate of the installer.
+ private final List<String> mInstallerCertificates;
+ private final long mVersionCode;
+ private final boolean mIsPreInstalled;
+ private final boolean mIsStampPresent;
+ private final boolean mIsStampVerified;
+ private final boolean mIsStampTrusted;
+ // Raw string encoding for the SHA-256 hash of the certificate of the stamp.
+ private final String mStampCertificateHash;
+ private final Map<String, String> mAllowedInstallersAndCertificates;
+
+ private AppInstallMetadata(Builder builder) {
+ this.mPackageName = builder.mPackageName;
+ this.mAppCertificates = builder.mAppCertificates;
+ this.mInstallerName = builder.mInstallerName;
+ this.mInstallerCertificates = builder.mInstallerCertificates;
+ this.mVersionCode = builder.mVersionCode;
+ this.mIsPreInstalled = builder.mIsPreInstalled;
+ this.mIsStampPresent = builder.mIsStampPresent;
+ this.mIsStampVerified = builder.mIsStampVerified;
+ this.mIsStampTrusted = builder.mIsStampTrusted;
+ this.mStampCertificateHash = builder.mStampCertificateHash;
+ this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @NonNull
+ public List<String> getAppCertificates() {
+ return mAppCertificates;
+ }
+
+ @NonNull
+ public String getInstallerName() {
+ return mInstallerName;
+ }
+
+ @NonNull
+ public List<String> getInstallerCertificates() {
+ return mInstallerCertificates;
+ }
+
+ /** @see AppInstallMetadata.Builder#setVersionCode(long) */
+ public long getVersionCode() {
+ return mVersionCode;
+ }
+
+ /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */
+ public boolean isPreInstalled() {
+ return mIsPreInstalled;
+ }
+
+ /** @see AppInstallMetadata.Builder#setIsStampPresent(boolean) */
+ public boolean isStampPresent() {
+ return mIsStampPresent;
+ }
+
+ /** @see AppInstallMetadata.Builder#setIsStampVerified(boolean) */
+ public boolean isStampVerified() {
+ return mIsStampVerified;
+ }
+
+ /** @see AppInstallMetadata.Builder#setIsStampTrusted(boolean) */
+ public boolean isStampTrusted() {
+ return mIsStampTrusted;
+ }
+
+ /** @see AppInstallMetadata.Builder#setStampCertificateHash(String) */
+ public String getStampCertificateHash() {
+ return mStampCertificateHash;
+ }
+
+ /** Get the allowed installers and their corresponding cert. */
+ public Map<String, String> getAllowedInstallersAndCertificates() {
+ return mAllowedInstallersAndCertificates;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s,"
+ + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, StampPresent ="
+ + " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }",
+ mPackageName,
+ mAppCertificates,
+ mInstallerName == null ? "null" : mInstallerName,
+ mInstallerCertificates == null ? "null" : mInstallerCertificates,
+ mVersionCode,
+ mIsPreInstalled,
+ mIsStampPresent,
+ mIsStampVerified,
+ mIsStampTrusted,
+ mStampCertificateHash == null ? "null" : mStampCertificateHash);
+ }
+
+ /** Builder class for constructing {@link AppInstallMetadata} objects. */
+ public static final class Builder {
+ private String mPackageName;
+ private List<String> mAppCertificates;
+ private String mInstallerName;
+ private List<String> mInstallerCertificates;
+ private long mVersionCode;
+ private boolean mIsPreInstalled;
+ private boolean mIsStampPresent;
+ private boolean mIsStampVerified;
+ private boolean mIsStampTrusted;
+ private String mStampCertificateHash;
+ private Map<String, String> mAllowedInstallersAndCertificates;
+
+ public Builder() {
+ mAllowedInstallersAndCertificates = new HashMap<>();
+ }
+
+ /**
+ * Add allowed installers and cert.
+ *
+ * @see AppInstallMetadata#getAllowedInstallersAndCertificates()
+ */
+ @NonNull
+ public Builder setAllowedInstallersAndCert(
+ @NonNull Map<String, String> allowedInstallersAndCertificates) {
+ this.mAllowedInstallersAndCertificates = allowedInstallersAndCertificates;
+ return this;
+ }
+
+ /**
+ * Set package name of the app to be installed.
+ *
+ * @see AppInstallMetadata#getPackageName()
+ */
+ @NonNull
+ public Builder setPackageName(@NonNull String packageName) {
+ this.mPackageName = Objects.requireNonNull(packageName);
+ return this;
+ }
+
+ /**
+ * Set certificate of the app to be installed.
+ *
+ * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+ * of the app.
+ *
+ * @see AppInstallMetadata#getAppCertificates()
+ */
+ @NonNull
+ public Builder setAppCertificates(@NonNull List<String> appCertificates) {
+ this.mAppCertificates = Objects.requireNonNull(appCertificates);
+ return this;
+ }
+
+ /**
+ * Set name of the installer installing the app.
+ *
+ * @see AppInstallMetadata#getInstallerName()
+ */
+ @NonNull
+ public Builder setInstallerName(@NonNull String installerName) {
+ this.mInstallerName = Objects.requireNonNull(installerName);
+ return this;
+ }
+
+ /**
+ * Set certificate of the installer installing the app.
+ *
+ * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+ * of the installer.
+ *
+ * @see AppInstallMetadata#getInstallerCertificates()
+ */
+ @NonNull
+ public Builder setInstallerCertificates(@NonNull List<String> installerCertificates) {
+ this.mInstallerCertificates = Objects.requireNonNull(installerCertificates);
+ return this;
+ }
+
+ /**
+ * Set version code of the app to be installed.
+ *
+ * @see AppInstallMetadata#getVersionCode()
+ */
+ @NonNull
+ public Builder setVersionCode(long versionCode) {
+ this.mVersionCode = versionCode;
+ return this;
+ }
+
+ /**
+ * Set whether the app is pre-installed on the device or not.
+ *
+ * @see AppInstallMetadata#isPreInstalled()
+ */
+ @NonNull
+ public Builder setIsPreInstalled(boolean isPreInstalled) {
+ this.mIsPreInstalled = isPreInstalled;
+ return this;
+ }
+
+ /**
+ * Set whether the stamp embedded in the APK is present or not.
+ *
+ * @see AppInstallMetadata#isStampPresent()
+ */
+ @NonNull
+ public Builder setIsStampPresent(boolean isStampPresent) {
+ this.mIsStampPresent = isStampPresent;
+ return this;
+ }
+
+ /**
+ * Set whether the stamp embedded in the APK is verified or not.
+ *
+ * @see AppInstallMetadata#isStampVerified()
+ */
+ @NonNull
+ public Builder setIsStampVerified(boolean isStampVerified) {
+ this.mIsStampVerified = isStampVerified;
+ return this;
+ }
+
+ /**
+ * Set whether the stamp embedded in the APK is trusted or not.
+ *
+ * @see AppInstallMetadata#isStampTrusted()
+ */
+ @NonNull
+ public Builder setIsStampTrusted(boolean isStampTrusted) {
+ this.mIsStampTrusted = isStampTrusted;
+ return this;
+ }
+
+ /**
+ * Set certificate hash of the stamp embedded in the APK.
+ *
+ * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate
+ * of the stamp.
+ *
+ * @see AppInstallMetadata#getStampCertificateHash()
+ */
+ @NonNull
+ public Builder setStampCertificateHash(@NonNull String stampCertificateHash) {
+ this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash);
+ return this;
+ }
+
+ /**
+ * Build {@link AppInstallMetadata}.
+ *
+ * @throws IllegalArgumentException if package name or app certificate is null
+ */
+ @NonNull
+ public AppInstallMetadata build() {
+ Objects.requireNonNull(mPackageName);
+ Objects.requireNonNull(mAppCertificates);
+ return new AppInstallMetadata(this);
+ }
+ }
+}
diff --git a/android/content/integrity/AppIntegrityManager.java b/android/content/integrity/AppIntegrityManager.java
new file mode 100644
index 0000000..1196064
--- /dev/null
+++ b/android/content/integrity/AppIntegrityManager.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 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.integrity;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Class for pushing rules used to check the integrity of app installs.
+ *
+ * <p>Note: applications using methods of this class must be a system app and have their package
+ * name allowlisted as an integrity rule provider. Otherwise a {@link SecurityException} will be
+ * thrown.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.APP_INTEGRITY_SERVICE)
+public class AppIntegrityManager {
+
+ /** The operation succeeded. */
+ public static final int STATUS_SUCCESS = 0;
+
+ /** The operation failed. */
+ public static final int STATUS_FAILURE = 1;
+
+ /**
+ * Current status of an operation. Will be one of {@link #STATUS_SUCCESS}, {@link
+ * #STATUS_FAILURE}.
+ *
+ * <p>More information about a status may be available through additional extras; see the
+ * individual status documentation for details.
+ *
+ * @see android.content.Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_STATUS = "android.content.integrity.extra.STATUS";
+
+ IAppIntegrityManager mManager;
+
+ /** @hide */
+ public AppIntegrityManager(IAppIntegrityManager manager) {
+ mManager = manager;
+ }
+
+ /**
+ * Update the rules to evaluate during install time.
+ *
+ * @param updateRequest request containing the data of the rule set update
+ * @param statusReceiver Called when the state of the session changes. Intents sent to this
+ * receiver contain {@link #EXTRA_STATUS}. Refer to the individual status codes on how to
+ * handle them.
+ */
+ public void updateRuleSet(
+ @NonNull RuleSet updateRequest, @NonNull IntentSender statusReceiver) {
+ try {
+ mManager.updateRuleSet(
+ updateRequest.getVersion(),
+ new ParceledListSlice<>(updateRequest.getRules()),
+ statusReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** Get the current version of the rule set. */
+ @NonNull
+ public String getCurrentRuleSetVersion() {
+ try {
+ return mManager.getCurrentRuleSetVersion();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** Get the name of the package that provided the current rule set. */
+ @NonNull
+ public String getCurrentRuleSetProvider() {
+ try {
+ return mManager.getCurrentRuleSetProvider();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Get current RuleSet on device.
+ *
+ * <p>Warning: this method is only used for tests.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public RuleSet getCurrentRuleSet() {
+ try {
+ ParceledListSlice<Rule> rules = mManager.getCurrentRules();
+ String version = mManager.getCurrentRuleSetVersion();
+ return new RuleSet.Builder().setVersion(version).addRules(rules.getList()).build();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Get the package names of all allowlisted rule providers.
+ *
+ * <p>Warning: this method is only used for tests.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public List<String> getWhitelistedRuleProviders() {
+ try {
+ return mManager.getWhitelistedRuleProviders();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/android/content/integrity/AtomicFormula.java b/android/content/integrity/AtomicFormula.java
new file mode 100644
index 0000000..e359800
--- /dev/null
+++ b/android/content/integrity/AtomicFormula.java
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2020 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.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a simple formula consisting of an app install metadata field and a value.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public abstract class AtomicFormula extends IntegrityFormula {
+
+ /** @hide */
+ @IntDef(
+ value = {
+ PACKAGE_NAME,
+ APP_CERTIFICATE,
+ INSTALLER_NAME,
+ INSTALLER_CERTIFICATE,
+ VERSION_CODE,
+ PRE_INSTALLED,
+ STAMP_TRUSTED,
+ STAMP_CERTIFICATE_HASH,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Key {}
+
+ /** @hide */
+ @IntDef(value = {EQ, GT, GTE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Operator {}
+
+ /**
+ * Package name of the app.
+ *
+ * <p>Can only be used in {@link StringAtomicFormula}.
+ */
+ public static final int PACKAGE_NAME = 0;
+
+ /**
+ * SHA-256 of the app certificate of the app.
+ *
+ * <p>Can only be used in {@link StringAtomicFormula}.
+ */
+ public static final int APP_CERTIFICATE = 1;
+
+ /**
+ * Package name of the installer. Will be empty string if installed by the system (e.g., adb).
+ *
+ * <p>Can only be used in {@link StringAtomicFormula}.
+ */
+ public static final int INSTALLER_NAME = 2;
+
+ /**
+ * SHA-256 of the cert of the installer. Will be empty string if installed by the system (e.g.,
+ * adb).
+ *
+ * <p>Can only be used in {@link StringAtomicFormula}.
+ */
+ public static final int INSTALLER_CERTIFICATE = 3;
+
+ /**
+ * Version code of the app.
+ *
+ * <p>Can only be used in {@link LongAtomicFormula}.
+ */
+ public static final int VERSION_CODE = 4;
+
+ /**
+ * If the app is pre-installed on the device.
+ *
+ * <p>Can only be used in {@link BooleanAtomicFormula}.
+ */
+ public static final int PRE_INSTALLED = 5;
+
+ /**
+ * If the APK has an embedded trusted stamp.
+ *
+ * <p>Can only be used in {@link BooleanAtomicFormula}.
+ */
+ public static final int STAMP_TRUSTED = 6;
+
+ /**
+ * SHA-256 of the certificate used to sign the stamp embedded in the APK.
+ *
+ * <p>Can only be used in {@link StringAtomicFormula}.
+ */
+ public static final int STAMP_CERTIFICATE_HASH = 7;
+
+ public static final int EQ = 0;
+ public static final int GT = 1;
+ public static final int GTE = 2;
+
+ private final @Key int mKey;
+
+ public AtomicFormula(@Key int key) {
+ checkArgument(isValidKey(key), "Unknown key: %d", key);
+ mKey = key;
+ }
+
+ /** An {@link AtomicFormula} with an key and long value. */
+ public static final class LongAtomicFormula extends AtomicFormula implements Parcelable {
+ private final Long mValue;
+ private final @Operator Integer mOperator;
+
+ /**
+ * Constructs an empty {@link LongAtomicFormula}. This should only be used as a base.
+ *
+ * <p>This formula will always return false.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with long value
+ */
+ public LongAtomicFormula(@Key int key) {
+ super(key);
+ checkArgument(
+ key == VERSION_CODE,
+ "Key %s cannot be used with LongAtomicFormula", keyToString(key));
+ mValue = null;
+ mOperator = null;
+ }
+
+ /**
+ * Constructs a new {@link LongAtomicFormula}.
+ *
+ * <p>This formula will hold if and only if the corresponding information of an install
+ * specified by {@code key} is of the correct relationship to {@code value} as specified by
+ * {@code operator}.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with long value
+ */
+ public LongAtomicFormula(@Key int key, @Operator int operator, long value) {
+ super(key);
+ checkArgument(
+ key == VERSION_CODE,
+ "Key %s cannot be used with LongAtomicFormula", keyToString(key));
+ checkArgument(
+ isValidOperator(operator), "Unknown operator: %d", operator);
+ mOperator = operator;
+ mValue = value;
+ }
+
+ LongAtomicFormula(Parcel in) {
+ super(in.readInt());
+ mValue = in.readLong();
+ mOperator = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<LongAtomicFormula> CREATOR =
+ new Creator<LongAtomicFormula>() {
+ @Override
+ public LongAtomicFormula createFromParcel(Parcel in) {
+ return new LongAtomicFormula(in);
+ }
+
+ @Override
+ public LongAtomicFormula[] newArray(int size) {
+ return new LongAtomicFormula[size];
+ }
+ };
+
+ @Override
+ public int getTag() {
+ return IntegrityFormula.LONG_ATOMIC_FORMULA_TAG;
+ }
+
+ @Override
+ public boolean matches(AppInstallMetadata appInstallMetadata) {
+ if (mValue == null || mOperator == null) {
+ return false;
+ }
+
+ long metadataValue = getLongMetadataValue(appInstallMetadata, getKey());
+ switch (mOperator) {
+ case EQ:
+ return metadataValue == mValue;
+ case GT:
+ return metadataValue > mValue;
+ case GTE:
+ return metadataValue >= mValue;
+ default:
+ throw new IllegalArgumentException(
+ String.format("Unexpected operator %d", mOperator));
+ }
+ }
+
+ @Override
+ public boolean isAppCertificateFormula() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstallerFormula() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ if (mValue == null || mOperator == null) {
+ return String.format("(%s)", keyToString(getKey()));
+ }
+ return String.format(
+ "(%s %s %s)", keyToString(getKey()), operatorToString(mOperator), mValue);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LongAtomicFormula that = (LongAtomicFormula) o;
+ return getKey() == that.getKey()
+ && mValue == that.mValue
+ && mOperator == that.mOperator;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getKey(), mOperator, mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mValue == null || mOperator == null) {
+ throw new IllegalStateException("Cannot write an empty LongAtomicFormula.");
+ }
+ dest.writeInt(getKey());
+ dest.writeLong(mValue);
+ dest.writeInt(mOperator);
+ }
+
+ public Long getValue() {
+ return mValue;
+ }
+
+ public Integer getOperator() {
+ return mOperator;
+ }
+
+ private static boolean isValidOperator(int operator) {
+ return operator == EQ || operator == GT || operator == GTE;
+ }
+
+ private static long getLongMetadataValue(AppInstallMetadata appInstallMetadata, int key) {
+ switch (key) {
+ case AtomicFormula.VERSION_CODE:
+ return appInstallMetadata.getVersionCode();
+ default:
+ throw new IllegalStateException("Unexpected key in IntAtomicFormula" + key);
+ }
+ }
+ }
+
+ /** An {@link AtomicFormula} with a key and string value. */
+ public static final class StringAtomicFormula extends AtomicFormula implements Parcelable {
+ private final String mValue;
+ // Indicates whether the value is the actual value or the hashed value.
+ private final Boolean mIsHashedValue;
+
+ /**
+ * Constructs an empty {@link StringAtomicFormula}. This should only be used as a base.
+ *
+ * <p>An empty formula will always match to false.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with string value
+ */
+ public StringAtomicFormula(@Key int key) {
+ super(key);
+ checkArgument(
+ key == PACKAGE_NAME
+ || key == APP_CERTIFICATE
+ || key == INSTALLER_CERTIFICATE
+ || key == INSTALLER_NAME
+ || key == STAMP_CERTIFICATE_HASH,
+ "Key %s cannot be used with StringAtomicFormula", keyToString(key));
+ mValue = null;
+ mIsHashedValue = null;
+ }
+
+ /**
+ * Constructs a new {@link StringAtomicFormula}.
+ *
+ * <p>This formula will hold if and only if the corresponding information of an install
+ * specified by {@code key} equals {@code value}.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with string value
+ */
+ public StringAtomicFormula(@Key int key, @NonNull String value, boolean isHashed) {
+ super(key);
+ checkArgument(
+ key == PACKAGE_NAME
+ || key == APP_CERTIFICATE
+ || key == INSTALLER_CERTIFICATE
+ || key == INSTALLER_NAME
+ || key == STAMP_CERTIFICATE_HASH,
+ "Key %s cannot be used with StringAtomicFormula", keyToString(key));
+ mValue = value;
+ mIsHashedValue = isHashed;
+ }
+
+ /**
+ * Constructs a new {@link StringAtomicFormula} together with handling the necessary hashing
+ * for the given key.
+ *
+ * <p>The value will be automatically hashed with SHA256 and the hex digest will be computed
+ * when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 characters.
+ *
+ * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, and STAMP_CERTIFICATE_HASH are always
+ * delivered in hashed form. So the isHashedValue is set to true by default.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with string value.
+ */
+ public StringAtomicFormula(@Key int key, @NonNull String value) {
+ super(key);
+ checkArgument(
+ key == PACKAGE_NAME
+ || key == APP_CERTIFICATE
+ || key == INSTALLER_CERTIFICATE
+ || key == INSTALLER_NAME
+ || key == STAMP_CERTIFICATE_HASH,
+ "Key %s cannot be used with StringAtomicFormula", keyToString(key));
+ mValue = hashValue(key, value);
+ mIsHashedValue =
+ (key == APP_CERTIFICATE
+ || key == INSTALLER_CERTIFICATE
+ || key == STAMP_CERTIFICATE_HASH)
+ || !mValue.equals(value);
+ }
+
+ StringAtomicFormula(Parcel in) {
+ super(in.readInt());
+ mValue = in.readStringNoHelper();
+ mIsHashedValue = in.readByte() != 0;
+ }
+
+ @NonNull
+ public static final Creator<StringAtomicFormula> CREATOR =
+ new Creator<StringAtomicFormula>() {
+ @Override
+ public StringAtomicFormula createFromParcel(Parcel in) {
+ return new StringAtomicFormula(in);
+ }
+
+ @Override
+ public StringAtomicFormula[] newArray(int size) {
+ return new StringAtomicFormula[size];
+ }
+ };
+
+ @Override
+ public int getTag() {
+ return IntegrityFormula.STRING_ATOMIC_FORMULA_TAG;
+ }
+
+ @Override
+ public boolean matches(AppInstallMetadata appInstallMetadata) {
+ if (mValue == null || mIsHashedValue == null) {
+ return false;
+ }
+ return getMetadataValue(appInstallMetadata, getKey()).contains(mValue);
+ }
+
+ @Override
+ public boolean isAppCertificateFormula() {
+ return getKey() == APP_CERTIFICATE;
+ }
+
+ @Override
+ public boolean isInstallerFormula() {
+ return getKey() == INSTALLER_NAME || getKey() == INSTALLER_CERTIFICATE;
+ }
+
+ @Override
+ public String toString() {
+ if (mValue == null || mIsHashedValue == null) {
+ return String.format("(%s)", keyToString(getKey()));
+ }
+ return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ StringAtomicFormula that = (StringAtomicFormula) o;
+ return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getKey(), mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mValue == null || mIsHashedValue == null) {
+ throw new IllegalStateException("Cannot write an empty StringAtomicFormula.");
+ }
+ dest.writeInt(getKey());
+ dest.writeStringNoHelper(mValue);
+ dest.writeByte((byte) (mIsHashedValue ? 1 : 0));
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public Boolean getIsHashedValue() {
+ return mIsHashedValue;
+ }
+
+ private static List<String> getMetadataValue(
+ AppInstallMetadata appInstallMetadata, int key) {
+ switch (key) {
+ case AtomicFormula.PACKAGE_NAME:
+ return Collections.singletonList(appInstallMetadata.getPackageName());
+ case AtomicFormula.APP_CERTIFICATE:
+ return appInstallMetadata.getAppCertificates();
+ case AtomicFormula.INSTALLER_CERTIFICATE:
+ return appInstallMetadata.getInstallerCertificates();
+ case AtomicFormula.INSTALLER_NAME:
+ return Collections.singletonList(appInstallMetadata.getInstallerName());
+ case AtomicFormula.STAMP_CERTIFICATE_HASH:
+ return Collections.singletonList(appInstallMetadata.getStampCertificateHash());
+ default:
+ throw new IllegalStateException(
+ "Unexpected key in StringAtomicFormula: " + key);
+ }
+ }
+
+ private static String hashValue(@Key int key, String value) {
+ // Hash the string value if it is a PACKAGE_NAME or INSTALLER_NAME and the value is
+ // greater than 32 characters.
+ if (value.length() > 32) {
+ if (key == PACKAGE_NAME || key == INSTALLER_NAME) {
+ return hash(value);
+ }
+ }
+ return value;
+ }
+
+ private static String hash(String value) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+ byte[] hashBytes = messageDigest.digest(value.getBytes(StandardCharsets.UTF_8));
+ return IntegrityUtils.getHexDigest(hashBytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("SHA-256 algorithm not found", e);
+ }
+ }
+ }
+
+ /** An {@link AtomicFormula} with a key and boolean value. */
+ public static final class BooleanAtomicFormula extends AtomicFormula implements Parcelable {
+ private final Boolean mValue;
+
+ /**
+ * Constructs an empty {@link BooleanAtomicFormula}. This should only be used as a base.
+ *
+ * <p>An empty formula will always match to false.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with boolean value
+ */
+ public BooleanAtomicFormula(@Key int key) {
+ super(key);
+ checkArgument(
+ key == PRE_INSTALLED || key == STAMP_TRUSTED,
+ String.format(
+ "Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
+ mValue = null;
+ }
+
+ /**
+ * Constructs a new {@link BooleanAtomicFormula}.
+ *
+ * <p>This formula will hold if and only if the corresponding information of an install
+ * specified by {@code key} equals {@code value}.
+ *
+ * @throws IllegalArgumentException if {@code key} cannot be used with boolean value
+ */
+ public BooleanAtomicFormula(@Key int key, boolean value) {
+ super(key);
+ checkArgument(
+ key == PRE_INSTALLED || key == STAMP_TRUSTED,
+ String.format(
+ "Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
+ mValue = value;
+ }
+
+ BooleanAtomicFormula(Parcel in) {
+ super(in.readInt());
+ mValue = in.readByte() != 0;
+ }
+
+ @NonNull
+ public static final Creator<BooleanAtomicFormula> CREATOR =
+ new Creator<BooleanAtomicFormula>() {
+ @Override
+ public BooleanAtomicFormula createFromParcel(Parcel in) {
+ return new BooleanAtomicFormula(in);
+ }
+
+ @Override
+ public BooleanAtomicFormula[] newArray(int size) {
+ return new BooleanAtomicFormula[size];
+ }
+ };
+
+ @Override
+ public int getTag() {
+ return IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG;
+ }
+
+ @Override
+ public boolean matches(AppInstallMetadata appInstallMetadata) {
+ if (mValue == null) {
+ return false;
+ }
+ return getBooleanMetadataValue(appInstallMetadata, getKey()) == mValue;
+ }
+
+ @Override
+ public boolean isAppCertificateFormula() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstallerFormula() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ if (mValue == null) {
+ return String.format("(%s)", keyToString(getKey()));
+ }
+ return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BooleanAtomicFormula that = (BooleanAtomicFormula) o;
+ return getKey() == that.getKey() && mValue == that.mValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getKey(), mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mValue == null) {
+ throw new IllegalStateException("Cannot write an empty BooleanAtomicFormula.");
+ }
+ dest.writeInt(getKey());
+ dest.writeByte((byte) (mValue ? 1 : 0));
+ }
+
+ public Boolean getValue() {
+ return mValue;
+ }
+
+ private static boolean getBooleanMetadataValue(
+ AppInstallMetadata appInstallMetadata, int key) {
+ switch (key) {
+ case AtomicFormula.PRE_INSTALLED:
+ return appInstallMetadata.isPreInstalled();
+ case AtomicFormula.STAMP_TRUSTED:
+ return appInstallMetadata.isStampTrusted();
+ default:
+ throw new IllegalStateException(
+ "Unexpected key in BooleanAtomicFormula: " + key);
+ }
+ }
+ }
+
+ public int getKey() {
+ return mKey;
+ }
+
+ static String keyToString(int key) {
+ switch (key) {
+ case PACKAGE_NAME:
+ return "PACKAGE_NAME";
+ case APP_CERTIFICATE:
+ return "APP_CERTIFICATE";
+ case VERSION_CODE:
+ return "VERSION_CODE";
+ case INSTALLER_NAME:
+ return "INSTALLER_NAME";
+ case INSTALLER_CERTIFICATE:
+ return "INSTALLER_CERTIFICATE";
+ case PRE_INSTALLED:
+ return "PRE_INSTALLED";
+ case STAMP_TRUSTED:
+ return "STAMP_TRUSTED";
+ case STAMP_CERTIFICATE_HASH:
+ return "STAMP_CERTIFICATE_HASH";
+ default:
+ throw new IllegalArgumentException("Unknown key " + key);
+ }
+ }
+
+ static String operatorToString(int op) {
+ switch (op) {
+ case EQ:
+ return "EQ";
+ case GT:
+ return "GT";
+ case GTE:
+ return "GTE";
+ default:
+ throw new IllegalArgumentException("Unknown operator " + op);
+ }
+ }
+
+ private static boolean isValidKey(int key) {
+ return key == PACKAGE_NAME
+ || key == APP_CERTIFICATE
+ || key == VERSION_CODE
+ || key == INSTALLER_NAME
+ || key == INSTALLER_CERTIFICATE
+ || key == PRE_INSTALLED
+ || key == STAMP_TRUSTED
+ || key == STAMP_CERTIFICATE_HASH;
+ }
+}
diff --git a/android/content/integrity/CompoundFormula.java b/android/content/integrity/CompoundFormula.java
new file mode 100644
index 0000000..1ffabd0
--- /dev/null
+++ b/android/content/integrity/CompoundFormula.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2019 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.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a compound formula formed by joining other simple and complex formulas with boolean
+ * connectors.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public final class CompoundFormula extends IntegrityFormula implements Parcelable {
+
+ /** @hide */
+ @IntDef(value = {AND, OR, NOT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Connector {}
+
+ /** Boolean AND operator. */
+ public static final int AND = 0;
+
+ /** Boolean OR operator. */
+ public static final int OR = 1;
+
+ /** Boolean NOT operator. */
+ public static final int NOT = 2;
+
+ private final @Connector int mConnector;
+ private final @NonNull List<IntegrityFormula> mFormulas;
+
+ @NonNull
+ public static final Creator<CompoundFormula> CREATOR =
+ new Creator<CompoundFormula>() {
+ @Override
+ public CompoundFormula createFromParcel(Parcel in) {
+ return new CompoundFormula(in);
+ }
+
+ @Override
+ public CompoundFormula[] newArray(int size) {
+ return new CompoundFormula[size];
+ }
+ };
+
+ /**
+ * Create a new formula from operator and operands.
+ *
+ * @throws IllegalArgumentException if the number of operands is not matching the requirements
+ * for that operator (at least 2 for {@link #AND} and {@link
+ * #OR}, 1 for {@link #NOT}).
+ */
+ public CompoundFormula(@Connector int connector, List<IntegrityFormula> formulas) {
+ checkArgument(
+ isValidConnector(connector), "Unknown connector: %d", connector);
+ validateFormulas(connector, formulas);
+ this.mConnector = connector;
+ this.mFormulas = Collections.unmodifiableList(formulas);
+ }
+
+ CompoundFormula(Parcel in) {
+ mConnector = in.readInt();
+ int length = in.readInt();
+ checkArgument(length >= 0, "Must have non-negative length. Got %d", length);
+ mFormulas = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ mFormulas.add(IntegrityFormula.readFromParcel(in));
+ }
+ validateFormulas(mConnector, mFormulas);
+ }
+
+ public @Connector int getConnector() {
+ return mConnector;
+ }
+
+ @NonNull
+ public List<IntegrityFormula> getFormulas() {
+ return mFormulas;
+ }
+
+ @Override
+ public int getTag() {
+ return IntegrityFormula.COMPOUND_FORMULA_TAG;
+ }
+
+ @Override
+ public boolean matches(AppInstallMetadata appInstallMetadata) {
+ switch (getConnector()) {
+ case NOT:
+ return !getFormulas().get(0).matches(appInstallMetadata);
+ case AND:
+ return getFormulas().stream()
+ .allMatch(formula -> formula.matches(appInstallMetadata));
+ case OR:
+ return getFormulas().stream()
+ .anyMatch(formula -> formula.matches(appInstallMetadata));
+ default:
+ throw new IllegalArgumentException("Unknown connector " + getConnector());
+ }
+ }
+
+ @Override
+ public boolean isAppCertificateFormula() {
+ return getFormulas().stream().anyMatch(formula -> formula.isAppCertificateFormula());
+ }
+
+ @Override
+ public boolean isInstallerFormula() {
+ return getFormulas().stream().anyMatch(formula -> formula.isInstallerFormula());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (mFormulas.size() == 1) {
+ sb.append(String.format("%s ", connectorToString(mConnector)));
+ sb.append(mFormulas.get(0).toString());
+ } else {
+ for (int i = 0; i < mFormulas.size(); i++) {
+ if (i > 0) {
+ sb.append(String.format(" %s ", connectorToString(mConnector)));
+ }
+ sb.append(mFormulas.get(i).toString());
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CompoundFormula that = (CompoundFormula) o;
+ return mConnector == that.mConnector && mFormulas.equals(that.mFormulas);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConnector, mFormulas);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mConnector);
+ dest.writeInt(mFormulas.size());
+ for (IntegrityFormula formula : mFormulas) {
+ IntegrityFormula.writeToParcel(formula, dest, flags);
+ }
+ }
+
+ private static void validateFormulas(
+ @Connector int connector, List<IntegrityFormula> formulas) {
+ switch (connector) {
+ case AND:
+ case OR:
+ checkArgument(
+ formulas.size() >= 2,
+ "Connector %s must have at least 2 formulas",
+ connectorToString(connector));
+ break;
+ case NOT:
+ checkArgument(
+ formulas.size() == 1,
+ "Connector %s must have 1 formula only",
+ connectorToString(connector));
+ break;
+ }
+ }
+
+ private static String connectorToString(int connector) {
+ switch (connector) {
+ case AND:
+ return "AND";
+ case OR:
+ return "OR";
+ case NOT:
+ return "NOT";
+ default:
+ throw new IllegalArgumentException("Unknown connector " + connector);
+ }
+ }
+
+ private static boolean isValidConnector(int connector) {
+ return connector == AND || connector == OR || connector == NOT;
+ }
+}
diff --git a/android/content/integrity/InstallerAllowedByManifestFormula.java b/android/content/integrity/InstallerAllowedByManifestFormula.java
new file mode 100644
index 0000000..9d37299
--- /dev/null
+++ b/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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.integrity;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Map;
+
+/**
+ * An atomic formula that evaluates to true if the installer of the current install is specified in
+ * the "allowed installer" field in the android manifest. Note that an empty "allowed installer" by
+ * default means containing all possible installers.
+ *
+ * @hide
+ */
+public class InstallerAllowedByManifestFormula extends IntegrityFormula implements Parcelable {
+
+ public static final String INSTALLER_CERTIFICATE_NOT_EVALUATED = "";
+
+ public InstallerAllowedByManifestFormula() {
+ }
+
+ private InstallerAllowedByManifestFormula(Parcel in) {
+ }
+
+ @NonNull
+ public static final Creator<InstallerAllowedByManifestFormula> CREATOR =
+ new Creator<InstallerAllowedByManifestFormula>() {
+ @Override
+ public InstallerAllowedByManifestFormula createFromParcel(Parcel in) {
+ return new InstallerAllowedByManifestFormula(in);
+ }
+
+ @Override
+ public InstallerAllowedByManifestFormula[] newArray(int size) {
+ return new InstallerAllowedByManifestFormula[size];
+ }
+ };
+
+ @Override
+ public int getTag() {
+ return IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG;
+ }
+
+ @Override
+ public boolean matches(AppInstallMetadata appInstallMetadata) {
+ Map<String, String> allowedInstallersAndCertificates =
+ appInstallMetadata.getAllowedInstallersAndCertificates();
+ return allowedInstallersAndCertificates.isEmpty()
+ || installerInAllowedInstallersFromManifest(
+ appInstallMetadata, allowedInstallersAndCertificates);
+ }
+
+ @Override
+ public boolean isAppCertificateFormula() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstallerFormula() {
+ return true;
+ }
+
+ private static boolean installerInAllowedInstallersFromManifest(
+ AppInstallMetadata appInstallMetadata,
+ Map<String, String> allowedInstallersAndCertificates) {
+ String installerPackage = appInstallMetadata.getInstallerName();
+
+ if (!allowedInstallersAndCertificates.containsKey(installerPackage)) {
+ return false;
+ }
+
+ // If certificate is not specified in the manifest, we do not check it.
+ if (!allowedInstallersAndCertificates.get(installerPackage)
+ .equals(INSTALLER_CERTIFICATE_NOT_EVALUATED)) {
+ return appInstallMetadata.getInstallerCertificates()
+ .contains(
+ allowedInstallersAndCertificates
+ .get(appInstallMetadata.getInstallerName()));
+ }
+
+ return true;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+}
diff --git a/android/content/integrity/IntegrityFormula.java b/android/content/integrity/IntegrityFormula.java
new file mode 100644
index 0000000..d965ef5
--- /dev/null
+++ b/android/content/integrity/IntegrityFormula.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2019 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.integrity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.integrity.AtomicFormula.BooleanAtomicFormula;
+import android.content.integrity.AtomicFormula.LongAtomicFormula;
+import android.content.integrity.AtomicFormula.StringAtomicFormula;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Represents a rule logic/content.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public abstract class IntegrityFormula {
+
+ /** Factory class for creating integrity formulas based on the app being installed. */
+ public static final class Application {
+ /** Returns an integrity formula that checks the equality to a package name. */
+ @NonNull
+ public static IntegrityFormula packageNameEquals(@NonNull String packageName) {
+ return new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, packageName);
+ }
+
+ /**
+ * Returns an integrity formula that checks if the app certificates contain {@code
+ * appCertificate}.
+ */
+ @NonNull
+ public static IntegrityFormula certificatesContain(@NonNull String appCertificate) {
+ return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate);
+ }
+
+ /** Returns an integrity formula that checks the equality to a version code. */
+ @NonNull
+ public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) {
+ return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode);
+ }
+
+ /**
+ * Returns an integrity formula that checks the app's version code is greater than the
+ * provided value.
+ */
+ @NonNull
+ public static IntegrityFormula versionCodeGreaterThan(@NonNull long versionCode) {
+ return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, versionCode);
+ }
+
+ /**
+ * Returns an integrity formula that checks the app's version code is greater than or equal
+ * to the provided value.
+ */
+ @NonNull
+ public static IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long versionCode) {
+ return new LongAtomicFormula(
+ AtomicFormula.VERSION_CODE, AtomicFormula.GTE, versionCode);
+ }
+
+ /** Returns an integrity formula that is valid when app is pre-installed. */
+ @NonNull
+ public static IntegrityFormula isPreInstalled() {
+ return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+ }
+
+ private Application() {}
+ }
+
+ /** Factory class for creating integrity formulas based on installer. */
+ public static final class Installer {
+ /** Returns an integrity formula that checks the equality to an installer name. */
+ @NonNull
+ public static IntegrityFormula packageNameEquals(@NonNull String installerName) {
+ return new StringAtomicFormula(AtomicFormula.INSTALLER_NAME, installerName);
+ }
+
+ /**
+ * An static formula that evaluates to true if the installer is NOT allowed according to the
+ * "allowed installer" field in the android manifest.
+ */
+ @NonNull
+ public static IntegrityFormula notAllowedByManifest() {
+ return not(new InstallerAllowedByManifestFormula());
+ }
+
+ /**
+ * Returns an integrity formula that checks if the installer certificates contain {@code
+ * installerCertificate}.
+ */
+ @NonNull
+ public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) {
+ return new StringAtomicFormula(
+ AtomicFormula.INSTALLER_CERTIFICATE, installerCertificate);
+ }
+
+ private Installer() {}
+ }
+
+ /** Factory class for creating integrity formulas based on source stamp. */
+ public static final class SourceStamp {
+ /** Returns an integrity formula that checks the equality to a stamp certificate hash. */
+ @NonNull
+ public static IntegrityFormula stampCertificateHashEquals(
+ @NonNull String stampCertificateHash) {
+ return new StringAtomicFormula(
+ AtomicFormula.STAMP_CERTIFICATE_HASH, stampCertificateHash);
+ }
+
+ /**
+ * Returns an integrity formula that is valid when stamp embedded in the APK is NOT trusted.
+ */
+ @NonNull
+ public static IntegrityFormula notTrusted() {
+ return new BooleanAtomicFormula(AtomicFormula.STAMP_TRUSTED, /* value= */ false);
+ }
+
+ private SourceStamp() {}
+ }
+
+ /** @hide */
+ @IntDef(
+ value = {
+ COMPOUND_FORMULA_TAG,
+ STRING_ATOMIC_FORMULA_TAG,
+ LONG_ATOMIC_FORMULA_TAG,
+ BOOLEAN_ATOMIC_FORMULA_TAG,
+ INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Tag {}
+
+ /** @hide */
+ public static final int COMPOUND_FORMULA_TAG = 0;
+ /** @hide */
+ public static final int STRING_ATOMIC_FORMULA_TAG = 1;
+ /** @hide */
+ public static final int LONG_ATOMIC_FORMULA_TAG = 2;
+ /** @hide */
+ public static final int BOOLEAN_ATOMIC_FORMULA_TAG = 3;
+ /** @hide */
+ public static final int INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG = 4;
+
+ /**
+ * Returns the tag that identifies the current class.
+ *
+ * @hide
+ */
+ public abstract @Tag int getTag();
+
+ /**
+ * Returns true when the integrity formula is satisfied by the {@code appInstallMetadata}.
+ *
+ * @hide
+ */
+ public abstract boolean matches(AppInstallMetadata appInstallMetadata);
+
+ /**
+ * Returns true when the formula (or one of its atomic formulas) has app certificate as key.
+ *
+ * @hide
+ */
+ public abstract boolean isAppCertificateFormula();
+
+ /**
+ * Returns true when the formula (or one of its atomic formulas) has installer package name or
+ * installer certificate as key.
+ *
+ * @hide
+ */
+ public abstract boolean isInstallerFormula();
+
+ /**
+ * Write an {@link IntegrityFormula} to {@link android.os.Parcel}.
+ *
+ * <p>This helper method is needed because non-final class/interface are not allowed to be
+ * {@link Parcelable}.
+ *
+ * @throws IllegalArgumentException if {@link IntegrityFormula} is not a recognized subclass
+ * @hide
+ */
+ public static void writeToParcel(
+ @NonNull IntegrityFormula formula, @NonNull Parcel dest, int flags) {
+ dest.writeInt(formula.getTag());
+ ((Parcelable) formula).writeToParcel(dest, flags);
+ }
+
+ /**
+ * Read a {@link IntegrityFormula} from a {@link android.os.Parcel}.
+ *
+ * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link
+ * Parcelable} (api lint error).
+ *
+ * @throws IllegalArgumentException if the parcel cannot be parsed
+ * @hide
+ */
+ @NonNull
+ public static IntegrityFormula readFromParcel(@NonNull Parcel in) {
+ int tag = in.readInt();
+ switch (tag) {
+ case COMPOUND_FORMULA_TAG:
+ return CompoundFormula.CREATOR.createFromParcel(in);
+ case STRING_ATOMIC_FORMULA_TAG:
+ return StringAtomicFormula.CREATOR.createFromParcel(in);
+ case LONG_ATOMIC_FORMULA_TAG:
+ return LongAtomicFormula.CREATOR.createFromParcel(in);
+ case BOOLEAN_ATOMIC_FORMULA_TAG:
+ return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+ case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
+ return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
+ default:
+ throw new IllegalArgumentException("Unknown formula tag " + tag);
+ }
+ }
+
+ /**
+ * Returns a formula that evaluates to true when any formula in {@code formulae} evaluates to
+ * true.
+ *
+ * <p>Throws an {@link IllegalArgumentException} if formulae has less than two elements.
+ */
+ @NonNull
+ public static IntegrityFormula any(@NonNull IntegrityFormula... formulae) {
+ return new CompoundFormula(CompoundFormula.OR, Arrays.asList(formulae));
+ }
+
+ /**
+ * Returns a formula that evaluates to true when all formula in {@code formulae} evaluates to
+ * true.
+ *
+ * <p>Throws an {@link IllegalArgumentException} if formulae has less than two elements.
+ */
+ @NonNull
+ public static IntegrityFormula all(@NonNull IntegrityFormula... formulae) {
+ return new CompoundFormula(CompoundFormula.AND, Arrays.asList(formulae));
+ }
+
+ /** Returns a formula that evaluates to true when {@code formula} evaluates to false. */
+ @NonNull
+ public static IntegrityFormula not(@NonNull IntegrityFormula formula) {
+ return new CompoundFormula(CompoundFormula.NOT, Arrays.asList(formula));
+ }
+
+ // Constructor is package private so it cannot be inherited outside of this package.
+ IntegrityFormula() {}
+}
diff --git a/android/content/integrity/IntegrityUtils.java b/android/content/integrity/IntegrityUtils.java
new file mode 100644
index 0000000..c184c69
--- /dev/null
+++ b/android/content/integrity/IntegrityUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 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.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+/**
+ * Utils class for simple operations used in integrity module.
+ *
+ * @hide
+ */
+public class IntegrityUtils {
+
+ private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+
+ /**
+ * Obtain the raw bytes from hex encoded string.
+ *
+ * @throws IllegalArgumentException if {@code hexDigest} is not a valid hex encoding of some
+ * bytes
+ */
+ public static byte[] getBytesFromHexDigest(String hexDigest) {
+ checkArgument(
+ hexDigest.length() % 2 == 0,
+ "Invalid hex encoding %s: must have even length", hexDigest);
+
+ byte[] rawBytes = new byte[hexDigest.length() / 2];
+ for (int i = 0; i < rawBytes.length; i++) {
+ int upperNibble = hexDigest.charAt(2 * i);
+ int lowerNibble = hexDigest.charAt(2 * i + 1);
+ rawBytes[i] = (byte) ((hexToDec(upperNibble) << 4) | hexToDec(lowerNibble));
+ }
+ return rawBytes;
+ }
+
+ /** Obtain hex encoded string from raw bytes. */
+ public static String getHexDigest(byte[] rawBytes) {
+ char[] hexChars = new char[rawBytes.length * 2];
+
+ for (int i = 0; i < rawBytes.length; i++) {
+ int upperNibble = (rawBytes[i] >>> 4) & 0xF;
+ int lowerNibble = rawBytes[i] & 0xF;
+ hexChars[i * 2] = decToHex(upperNibble);
+ hexChars[i * 2 + 1] = decToHex(lowerNibble);
+ }
+ return new String(hexChars);
+ }
+
+ private static int hexToDec(int hexChar) {
+ if (hexChar >= '0' && hexChar <= '9') {
+ return hexChar - '0';
+ }
+ if (hexChar >= 'a' && hexChar <= 'f') {
+ return hexChar - 'a' + 10;
+ }
+ if (hexChar >= 'A' && hexChar <= 'F') {
+ return hexChar - 'A' + 10;
+ }
+ throw new IllegalArgumentException("Invalid hex char " + hexChar);
+ }
+
+ private static char decToHex(int dec) {
+ if (dec >= 0 && dec < HEX_CHARS.length) {
+ return HEX_CHARS[dec];
+ }
+
+ throw new IllegalArgumentException("Invalid dec value to be converted to hex digit " + dec);
+ }
+}
diff --git a/android/content/integrity/Rule.java b/android/content/integrity/Rule.java
new file mode 100644
index 0000000..34eb1e7
--- /dev/null
+++ b/android/content/integrity/Rule.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 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.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represent rules to be used in the rule evaluation engine to match against app installs.
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @hide
+ */
+@SystemApi
+@VisibleForTesting
+public final class Rule implements Parcelable {
+
+ /** @hide */
+ @IntDef(
+ value = {
+ DENY,
+ FORCE_ALLOW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Effect {}
+
+ /** If this rule matches the install, the install should be denied. */
+ public static final int DENY = 0;
+
+ /**
+ * If this rule matches the install, the install will be allowed regardless of other matched
+ * rules.
+ */
+ public static final int FORCE_ALLOW = 1;
+
+ private final @NonNull IntegrityFormula mFormula;
+ private final @Effect int mEffect;
+
+ public Rule(@NonNull IntegrityFormula formula, @Effect int effect) {
+ checkArgument(isValidEffect(effect), "Unknown effect: %d", effect);
+ this.mFormula = Objects.requireNonNull(formula);
+ this.mEffect = effect;
+ }
+
+ Rule(Parcel in) {
+ mFormula = IntegrityFormula.readFromParcel(in);
+ mEffect = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<Rule> CREATOR =
+ new Creator<Rule>() {
+ @Override
+ public Rule createFromParcel(Parcel in) {
+ return new Rule(in);
+ }
+
+ @Override
+ public Rule[] newArray(int size) {
+ return new Rule[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ IntegrityFormula.writeToParcel(mFormula, dest, flags);
+ dest.writeInt(mEffect);
+ }
+
+ @NonNull
+ public IntegrityFormula getFormula() {
+ return mFormula;
+ }
+
+ public @Effect int getEffect() {
+ return mEffect;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Rule: %s, %s", mFormula, effectToString(mEffect));
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Rule that = (Rule) o;
+ return mEffect == that.mEffect && Objects.equals(mFormula, that.mFormula);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFormula, mEffect);
+ }
+
+ private static String effectToString(int effect) {
+ switch (effect) {
+ case DENY:
+ return "DENY";
+ case FORCE_ALLOW:
+ return "FORCE_ALLOW";
+ default:
+ throw new IllegalArgumentException("Unknown effect " + effect);
+ }
+ }
+
+ private static boolean isValidEffect(int effect) {
+ return effect == DENY || effect == FORCE_ALLOW;
+ }
+}
diff --git a/android/content/integrity/RuleSet.java b/android/content/integrity/RuleSet.java
new file mode 100644
index 0000000..b423b54
--- /dev/null
+++ b/android/content/integrity/RuleSet.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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.integrity;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Immutable data class encapsulating all parameters of a rule set.
+ *
+ * @hide
+ */
+@SystemApi
+public class RuleSet {
+ private final String mVersion;
+ private final List<Rule> mRules;
+
+ private RuleSet(String version, List<Rule> rules) {
+ mVersion = version;
+ mRules = Collections.unmodifiableList(rules);
+ }
+
+ /** @see Builder#setVersion(String). */
+ @NonNull
+ public String getVersion() {
+ return mVersion;
+ }
+
+ /** @see Builder#addRules(List). */
+ @NonNull
+ public List<Rule> getRules() {
+ return mRules;
+ }
+
+ /** Builder class for RuleSetUpdateRequest. */
+ public static class Builder {
+ private String mVersion;
+ private List<Rule> mRules;
+
+ public Builder() {
+ mRules = new ArrayList<>();
+ }
+
+ /**
+ * Set a version string to identify this rule set. This can be retrieved by {@link
+ * AppIntegrityManager#getCurrentRuleSetVersion()}.
+ */
+ @NonNull
+ public Builder setVersion(@NonNull String version) {
+ mVersion = version;
+ return this;
+ }
+
+ /** Add the rules to include. */
+ @NonNull
+ public Builder addRules(@NonNull List<Rule> rules) {
+ mRules.addAll(rules);
+ return this;
+ }
+
+ /**
+ * Builds a {@link RuleSet}.
+ *
+ * @throws IllegalArgumentException if version is null
+ */
+ @NonNull
+ public RuleSet build() {
+ Objects.requireNonNull(mVersion);
+ return new RuleSet(mVersion, mRules);
+ }
+ }
+}
diff --git a/android/content/om/CriticalOverlayInfo.java b/android/content/om/CriticalOverlayInfo.java
new file mode 100644
index 0000000..8fbc698
--- /dev/null
+++ b/android/content/om/CriticalOverlayInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * A subset of {@link OverlayInfo} fields that when changed cause the overlay's settings to be
+ * completely reinitialized.
+ *
+ * @hide
+ */
+public interface CriticalOverlayInfo {
+
+ /**
+ * @return the package name of the overlay.
+ */
+ @NonNull
+ String getPackageName();
+
+ /**
+ * @return the unique name of the overlay within its containing package.
+ */
+ @Nullable
+ String getOverlayName();
+
+ /**
+ * @return the target package name of the overlay.
+ */
+ @NonNull
+ String getTargetPackageName();
+
+ /**
+ * @return the name of the target overlayable declaration.
+ */
+ @Nullable
+ String getTargetOverlayableName();
+
+ /**
+ * @return an identifier representing the current overlay.
+ */
+ @NonNull
+ OverlayIdentifier getOverlayIdentifier();
+
+ /**
+ * Returns whether or not the overlay is a {@link FabricatedOverlay}.
+ */
+ boolean isFabricated();
+}
diff --git a/android/content/om/FabricatedOverlay.java b/android/content/om/FabricatedOverlay.java
new file mode 100644
index 0000000..d62b47b
--- /dev/null
+++ b/android/content/om/FabricatedOverlay.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
+ *
+ * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
+ * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
+ * overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
+ * system partition would fulfil the {@code system} and {@code signature} policies.
+ *
+ * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
+ * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
+ *
+ * Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
+ * overlays and fabricated overlays.
+ * @hide
+ */
+public class FabricatedOverlay {
+
+ /** Retrieves the identifier for this fabricated overlay. */
+ public OverlayIdentifier getIdentifier() {
+ return new OverlayIdentifier(
+ mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
+ }
+
+ public static class Builder {
+ private final String mOwningPackage;
+ private final String mName;
+ private final String mTargetPackage;
+ private String mTargetOverlayable = "";
+ private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>();
+
+ /**
+ * Constructs a build for a fabricated overlay.
+ *
+ * @param owningPackage the name of the package that owns the fabricated overlay (must
+ * be a package name of this UID).
+ * @param name a name used to uniquely identify the fabricated overlay owned by
+ * {@param owningPackageName}
+ * @param targetPackage the name of the package to overlay
+ */
+ public Builder(@NonNull String owningPackage, @NonNull String name,
+ @NonNull String targetPackage) {
+ Preconditions.checkStringNotEmpty(owningPackage,
+ "'owningPackage' must not be empty nor null");
+ Preconditions.checkStringNotEmpty(name,
+ "'name'' must not be empty nor null");
+ Preconditions.checkStringNotEmpty(targetPackage,
+ "'targetPackage' must not be empty nor null");
+
+ mOwningPackage = owningPackage;
+ mName = name;
+ mTargetPackage = targetPackage;
+ }
+
+ /**
+ * Sets the name of the overlayable resources to overlay (can be null).
+ */
+ public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
+ mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+ return this;
+ }
+
+ /**
+ * Sets the value of
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the unsigned 32 bit integer representing the new value
+ *
+ * @see android.util.TypedValue#type
+ */
+ public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.dataType = dataType;
+ entry.data = value;
+ mEntries.add(entry);
+ return this;
+ }
+
+ /** Builds an immutable fabricated overlay. */
+ public FabricatedOverlay build() {
+ final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
+ overlay.packageName = mOwningPackage;
+ overlay.overlayName = mName;
+ overlay.targetPackageName = mTargetPackage;
+ overlay.targetOverlayable = mTargetOverlayable;
+ overlay.entries = new ArrayList<>();
+ overlay.entries.addAll(mEntries);
+ return new FabricatedOverlay(overlay);
+ }
+ }
+
+ final FabricatedOverlayInternal mOverlay;
+ private FabricatedOverlay(FabricatedOverlayInternal overlay) {
+ mOverlay = overlay;
+ }
+}
diff --git a/android/content/om/OverlayIdentifier.java b/android/content/om/OverlayIdentifier.java
new file mode 100644
index 0000000..454d0d1
--- /dev/null
+++ b/android/content/om/OverlayIdentifier.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2021 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A key used to uniquely identify a Runtime Resource Overlay (RRO).
+ *
+ * An overlay always belongs to a package and may optionally have a name associated with it.
+ * The name helps uniquely identify a particular overlay within a package.
+ * @hide
+ */
+/** @hide */
+@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
+ genEqualsHashCode = true, genToString = false)
+public class OverlayIdentifier implements Parcelable {
+ /**
+ * The package name containing or owning the overlay.
+ */
+ @Nullable
+ private final String mPackageName;
+
+ /**
+ * The unique name within the package of the overlay.
+ */
+ @Nullable
+ private final String mOverlayName;
+
+ /**
+ * Creates an identifier from a package and unique name within the package.
+ *
+ * @param packageName the package containing or owning the overlay
+ * @param overlayName the unique name of the overlay within the package
+ */
+ public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
+ mPackageName = packageName;
+ mOverlayName = overlayName;
+ }
+
+ /**
+ * Creates an identifier for an overlay without a name.
+ *
+ * @param packageName the package containing or owning the overlay
+ */
+ public OverlayIdentifier(@NonNull String packageName) {
+ mPackageName = packageName;
+ mOverlayName = null;
+ }
+
+ @Override
+ public String toString() {
+ return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
+ }
+
+ /** @hide */
+ public static OverlayIdentifier fromString(@NonNull String text) {
+ final String[] parts = text.split(":", 2);
+ if (parts.length == 2) {
+ return new OverlayIdentifier(parts[0], parts[1]);
+ } else {
+ return new OverlayIdentifier(parts[0]);
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayIdentifier.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Retrieves the package name containing or owning the overlay.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Retrieves the unique name within the package of the overlay.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getOverlayName() {
+ return mOverlayName;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(OverlayIdentifier other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ OverlayIdentifier that = (OverlayIdentifier) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mOverlayName, that.mOverlayName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + Objects.hashCode(mOverlayName);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mPackageName != null) flg |= 0x1;
+ if (mOverlayName != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mPackageName != null) dest.writeString(mPackageName);
+ if (mOverlayName != null) dest.writeString(mOverlayName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected OverlayIdentifier(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String packageName = (flg & 0x1) == 0 ? null : in.readString();
+ String overlayName = (flg & 0x2) == 0 ? null : in.readString();
+
+ this.mPackageName = packageName;
+ this.mOverlayName = overlayName;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<OverlayIdentifier> CREATOR
+ = new Parcelable.Creator<OverlayIdentifier>() {
+ @Override
+ public OverlayIdentifier[] newArray(int size) {
+ return new OverlayIdentifier[size];
+ }
+
+ @Override
+ public OverlayIdentifier createFromParcel(@NonNull Parcel in) {
+ return new OverlayIdentifier(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1612482438728L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\[email protected](genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/om/OverlayInfo.java b/android/content/om/OverlayInfo.java
new file mode 100644
index 0000000..c66f49c
--- /dev/null
+++ b/android/content/om/OverlayInfo.java
@@ -0,0 +1,553 @@
+/*
+ * 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.om;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Immutable overlay information about a package. All PackageInfos that
+ * represent an overlay package will have a corresponding OverlayInfo.
+ *
+ * @hide
+ */
+@SystemApi
+public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {
+ STATE_UNKNOWN,
+ STATE_MISSING_TARGET,
+ STATE_NO_IDMAP,
+ STATE_DISABLED,
+ STATE_ENABLED,
+ STATE_ENABLED_IMMUTABLE,
+ // @Deprecated STATE_TARGET_IS_BEING_REPLACED,
+ STATE_OVERLAY_IS_BEING_REPLACED,
+ })
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
+ /**
+ * An internal state used as the initial state of an overlay. OverlayInfo
+ * objects exposed outside the {@link
+ * com.android.server.om.OverlayManagerService} should never have this
+ * state.
+ *
+ * @hide
+ */
+ public static final int STATE_UNKNOWN = -1;
+
+ /**
+ * The target package of the overlay is not installed. The overlay cannot be enabled.
+ *
+ * @hide
+ */
+ public static final int STATE_MISSING_TARGET = 0;
+
+ /**
+ * Creation of idmap file failed (e.g. no matching resources). The overlay
+ * cannot be enabled.
+ *
+ * @hide
+ */
+ public static final int STATE_NO_IDMAP = 1;
+
+ /**
+ * The overlay is currently disabled. It can be enabled.
+ *
+ * @see IOverlayManager#setEnabled
+ * @hide
+ */
+ public static final int STATE_DISABLED = 2;
+
+ /**
+ * The overlay is currently enabled. It can be disabled.
+ *
+ * @see IOverlayManager#setEnabled
+ * @hide
+ */
+ public static final int STATE_ENABLED = 3;
+
+ /**
+ * The target package is currently being upgraded or downgraded; the state
+ * will change once the package installation has finished.
+ * @hide
+ *
+ * @deprecated No longer used. Caused invalid transitions from enabled -> upgrading -> enabled,
+ * where an update is propagated when nothing has changed. Can occur during --dont-kill
+ * installs when code and resources are hot swapped and the Activity should not be relaunched.
+ * In all other cases, the process and therefore Activity is killed, so the state loop is
+ * irrelevant.
+ */
+ @Deprecated
+ public static final int STATE_TARGET_IS_BEING_REPLACED = 4;
+
+ /**
+ * The overlay package is currently being upgraded or downgraded; the state
+ * will change once the package installation has finished.
+ * @hide
+ */
+ public static final int STATE_OVERLAY_IS_BEING_REPLACED = 5;
+
+ /**
+ * The overlay package is currently enabled because it is marked as
+ * 'immutable'. It cannot be disabled but will change state if for instance
+ * its target is uninstalled.
+ * @hide
+ */
+ @Deprecated
+ public static final int STATE_ENABLED_IMMUTABLE = 6;
+
+ /**
+ * Overlay category: theme.
+ * <p>
+ * Change how Android (including the status bar, dialogs, ...) looks.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_THEME = "android.theme";
+
+ /**
+ * Package name of the overlay package
+ *
+ * @hide
+ */
+ @NonNull
+ public final String packageName;
+
+ /**
+ * The unique name within the package of the overlay.
+ *
+ * @hide
+ */
+ @Nullable
+ public final String overlayName;
+
+ /**
+ * Package name of the target package
+ *
+ * @hide
+ */
+ @NonNull
+ public final String targetPackageName;
+
+ /**
+ * Name of the target overlayable declaration.
+ *
+ * @hide
+ */
+ public final String targetOverlayableName;
+
+ /**
+ * Category of the overlay package
+ *
+ * @hide
+ */
+ public final String category;
+
+ /**
+ * Full path to the base APK for this overlay package
+ * @hide
+ */
+ @NonNull
+ public final String baseCodePath;
+
+ /**
+ * The state of this OverlayInfo as defined by the STATE_* constants in this class.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final @State int state;
+
+ /**
+ * User handle for which this overlay applies
+ * @hide
+ */
+ public final int userId;
+
+ /**
+ * Priority as configured by {@link com.android.internal.content.om.OverlayConfig}.
+ * Not intended to be exposed to 3rd party.
+ *
+ * @hide
+ */
+ public final int priority;
+
+ /**
+ * isMutable as configured by {@link com.android.internal.content.om.OverlayConfig}.
+ * If false, the overlay is unconditionally loaded and cannot be unloaded. Not intended to be
+ * exposed to 3rd party.
+ *
+ * @hide
+ */
+ public final boolean isMutable;
+
+ private OverlayIdentifier mIdentifierCached;
+
+ /**
+ *
+ * @hide
+ */
+ public final boolean isFabricated;
+
+ /**
+ * Create a new OverlayInfo based on source with an updated state.
+ *
+ * @param source the source OverlayInfo to base the new instance on
+ * @param state the new state for the source OverlayInfo
+ *
+ * @hide
+ */
+ public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
+ this(source.packageName, source.overlayName, source.targetPackageName,
+ source.targetOverlayableName, source.category, source.baseCodePath, state,
+ source.userId, source.priority, source.isMutable, source.isFabricated);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
+ @Nullable String targetOverlayableName, @Nullable String category,
+ @NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable) {
+ this(packageName, null /* overlayName */, targetPackageName, targetOverlayableName,
+ category, baseCodePath, state, userId, priority, isMutable,
+ false /* isFabricated */);
+ }
+
+ /** @hide */
+ public OverlayInfo(@NonNull String packageName, @Nullable String overlayName,
+ @NonNull String targetPackageName, @Nullable String targetOverlayableName,
+ @Nullable String category, @NonNull String baseCodePath, int state, int userId,
+ int priority, boolean isMutable, boolean isFabricated) {
+ this.packageName = packageName;
+ this.overlayName = overlayName;
+ this.targetPackageName = targetPackageName;
+ this.targetOverlayableName = targetOverlayableName;
+ this.category = category;
+ this.baseCodePath = baseCodePath;
+ this.state = state;
+ this.userId = userId;
+ this.priority = priority;
+ this.isMutable = isMutable;
+ this.isFabricated = isFabricated;
+ ensureValidState();
+ }
+
+ /** @hide */
+ public OverlayInfo(Parcel source) {
+ packageName = source.readString();
+ overlayName = source.readString();
+ targetPackageName = source.readString();
+ targetOverlayableName = source.readString();
+ category = source.readString();
+ baseCodePath = source.readString();
+ state = source.readInt();
+ userId = source.readInt();
+ priority = source.readInt();
+ isMutable = source.readBoolean();
+ isFabricated = source.readBoolean();
+ ensureValidState();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @SystemApi
+ @NonNull
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @Nullable
+ public String getOverlayName() {
+ return overlayName;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @SystemApi
+ @NonNull
+ public String getTargetPackageName() {
+ return targetPackageName;
+ }
+
+ /**
+ * Returns the category of the current overlay.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * Returns user handle for which this overlay applies to.
+ *
+ * @hide
+ */
+ @SystemApi
+ @UserIdInt
+ public int getUserId() {
+ return userId;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @SystemApi
+ @Nullable
+ public String getTargetOverlayableName() {
+ return targetOverlayableName;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public boolean isFabricated() {
+ return isFabricated;
+ }
+
+ /**
+ * Full path to the base APK or fabricated overlay for this overlay package.
+ *
+ * @hide
+ */
+ public String getBaseCodePath() {
+ return baseCodePath;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @NonNull
+ public OverlayIdentifier getOverlayIdentifier() {
+ if (mIdentifierCached == null) {
+ mIdentifierCached = new OverlayIdentifier(packageName, overlayName);
+ }
+ return mIdentifierCached;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ private void ensureValidState() {
+ if (packageName == null) {
+ throw new IllegalArgumentException("packageName must not be null");
+ }
+ if (targetPackageName == null) {
+ throw new IllegalArgumentException("targetPackageName must not be null");
+ }
+ if (baseCodePath == null) {
+ throw new IllegalArgumentException("baseCodePath must not be null");
+ }
+ switch (state) {
+ case STATE_UNKNOWN:
+ case STATE_MISSING_TARGET:
+ case STATE_NO_IDMAP:
+ case STATE_DISABLED:
+ case STATE_ENABLED:
+ case STATE_ENABLED_IMMUTABLE:
+ case STATE_TARGET_IS_BEING_REPLACED:
+ case STATE_OVERLAY_IS_BEING_REPLACED:
+ break;
+ default:
+ throw new IllegalArgumentException("State " + state + " is not a valid state");
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(packageName);
+ dest.writeString(overlayName);
+ dest.writeString(targetPackageName);
+ dest.writeString(targetOverlayableName);
+ dest.writeString(category);
+ dest.writeString(baseCodePath);
+ dest.writeInt(state);
+ dest.writeInt(userId);
+ dest.writeInt(priority);
+ dest.writeBoolean(isMutable);
+ dest.writeBoolean(isFabricated);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
+ new Parcelable.Creator<OverlayInfo>() {
+ @Override
+ public OverlayInfo createFromParcel(Parcel source) {
+ return new OverlayInfo(source);
+ }
+
+ @Override
+ public OverlayInfo[] newArray(int size) {
+ return new OverlayInfo[size];
+ }
+ };
+
+ /**
+ * Return true if this overlay is enabled, i.e. should be used to overlay
+ * the resources in the target package.
+ *
+ * Disabled overlay packages are installed but are currently not in use.
+ *
+ * @return true if the overlay is enabled, else false.
+ * @hide
+ */
+ @SystemApi
+ public boolean isEnabled() {
+ switch (state) {
+ case STATE_ENABLED:
+ case STATE_ENABLED_IMMUTABLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Translate a state to a human readable string. Only intended for
+ * debugging purposes.
+ *
+ * @return a human readable String representing the state.
+ * @hide
+ */
+ public static String stateToString(@State int state) {
+ switch (state) {
+ case STATE_UNKNOWN:
+ return "STATE_UNKNOWN";
+ case STATE_MISSING_TARGET:
+ return "STATE_MISSING_TARGET";
+ case STATE_NO_IDMAP:
+ return "STATE_NO_IDMAP";
+ case STATE_DISABLED:
+ return "STATE_DISABLED";
+ case STATE_ENABLED:
+ return "STATE_ENABLED";
+ case STATE_ENABLED_IMMUTABLE:
+ return "STATE_ENABLED_IMMUTABLE";
+ case STATE_TARGET_IS_BEING_REPLACED:
+ return "STATE_TARGET_IS_BEING_REPLACED";
+ case STATE_OVERLAY_IS_BEING_REPLACED:
+ return "STATE_OVERLAY_IS_BEING_REPLACED";
+ default:
+ return "<unknown state>";
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + userId;
+ result = prime * result + state;
+ result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
+ result = prime * result + ((overlayName == null) ? 0 : overlayName.hashCode());
+ result = prime * result + ((targetPackageName == null) ? 0 : targetPackageName.hashCode());
+ result = prime * result + ((targetOverlayableName == null) ? 0
+ : targetOverlayableName.hashCode());
+ result = prime * result + ((category == null) ? 0 : category.hashCode());
+ result = prime * result + ((baseCodePath == null) ? 0 : baseCodePath.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ OverlayInfo other = (OverlayInfo) obj;
+ if (userId != other.userId) {
+ return false;
+ }
+ if (state != other.state) {
+ return false;
+ }
+ if (!packageName.equals(other.packageName)) {
+ return false;
+ }
+ if (!Objects.equals(overlayName, other.overlayName)) {
+ return false;
+ }
+ if (!targetPackageName.equals(other.targetPackageName)) {
+ return false;
+ }
+ if (!Objects.equals(targetOverlayableName, other.targetOverlayableName)) {
+ return false;
+ }
+ if (!Objects.equals(category, other.category)) {
+ return false;
+ }
+ if (!baseCodePath.equals(other.baseCodePath)) {
+ return false;
+ }
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "OverlayInfo {"
+ + "packageName=" + packageName
+ + ", overlayName=" + overlayName
+ + ", targetPackage=" + targetPackageName
+ + ", targetOverlayable=" + targetOverlayableName
+ + ", state=" + state + " (" + stateToString(state) + "),"
+ + ", userId=" + userId
+ + " }";
+ }
+}
diff --git a/android/content/om/OverlayManager.java b/android/content/om/OverlayManager.java
new file mode 100644
index 0000000..0f7e01b
--- /dev/null
+++ b/android/content/om/OverlayManager.java
@@ -0,0 +1,322 @@
+/*
+ * 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.Context;
+import android.os.Build;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import com.android.server.SystemConfig;
+
+import java.util.List;
+
+/**
+ * Updates OverlayManager state; gets information about installed overlay packages.
+ *
+ * <p>Users of this API must be actors of any overlays they desire to change the state of.</p>
+ *
+ * <p>An actor is a package responsible for managing the state of overlays targeting overlayables
+ * that specify the actor. For example, an actor may enable or disable an overlay or otherwise
+ * change its state.</p>
+ *
+ * <p>Actors are specified as part of the overlayable definition.
+ *
+ * <pre>{@code
+ * <overlayable name="OverlayableResourcesName" actor="overlay://namespace/actorName">
+ * }</pre></p>
+ *
+ * <p>Actors are defined through {@link SystemConfig}. Only system packages can be used.
+ * The namespace "android" is reserved for use by AOSP and any "android" definitions must
+ * have an implementation on device that fulfill their intended functionality.</p>
+ *
+ * <pre>{@code
+ * <named-actor
+ * namespace="namespace"
+ * name="actorName"
+ * package="com.example.pkg"
+ * />
+ * }</pre></p>
+ *
+ * <p>An actor can manipulate a particular overlay if any of the following is true:
+ * <ul>
+ * <li>its UID is {@link Process#ROOT_UID}, {@link Process#SYSTEM_UID}</li>
+ * <li>it is the target of the overlay package</li>
+ * <li>it has the CHANGE_OVERLAY_PACKAGES permission and the target does not specify an actor</li>
+ * <li>it is the actor specified by the overlayable</li>
+ * </ul></p>
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.OVERLAY_SERVICE)
+public class OverlayManager {
+
+ private final IOverlayManager mService;
+ private final Context mContext;
+
+ /**
+ * Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e
+ * .g. {@link #setEnabled(String, boolean, UserHandle)}) for a permission error.
+ * Since R this no longer holds true, and {@link java.lang.SecurityException} can be
+ * thrown for any number of reasons, none of which are exposed to the caller.
+ *
+ * <p>To maintain existing API behavior, if a legacy permission failure or actor enforcement
+ * failure occurs for an app not yet targeting R, coerce it into an {@link
+ * java.lang.IllegalStateException}, which existed in the source prior to R.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.R)
+ private static final long THROW_SECURITY_EXCEPTIONS = 147340954;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context The current context in which to operate.
+ * @param service The backing system service.
+ *
+ * @hide
+ */
+ public OverlayManager(Context context, IOverlayManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /** @hide */
+ public OverlayManager(Context context) {
+ this(context, IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE)));
+ }
+
+ /**
+ * Request that an overlay package is enabled and any other overlay packages with the same
+ * target package and category are disabled.
+ *
+ * If a set of overlay packages share the same category, single call to this method is
+ * equivalent to multiple calls to {@link #setEnabled(String, boolean, UserHandle)}.
+ *
+ * The caller must pass the actor requirements specified in the class comment.
+ *
+ * @param packageName the name of the overlay package to enable.
+ * @param user The user for which to change the overlay.
+ *
+ * @throws SecurityException when caller is not allowed to enable {@param packageName}
+ * @throws IllegalStateException when enabling fails otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.INTERACT_ACROSS_USERS_FULL"
+ })
+ public void setEnabledExclusiveInCategory(@NonNull final String packageName,
+ @NonNull UserHandle user) throws SecurityException, IllegalStateException {
+ try {
+ if (!mService.setEnabledExclusiveInCategory(packageName, user.getIdentifier())) {
+ throw new IllegalStateException("setEnabledExclusiveInCategory failed");
+ }
+ } catch (SecurityException e) {
+ rethrowSecurityException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request that an overlay package is enabled or disabled.
+ *
+ * While {@link #setEnabledExclusiveInCategory(String, UserHandle)} doesn't support disabling
+ * every overlay in a category, this method allows you to disable everything.
+ *
+ * The caller must pass the actor requirements specified in the class comment.
+ *
+ * @param packageName the name of the overlay package to enable.
+ * @param enable {@code false} if the overlay should be turned off.
+ * @param user The user for which to change the overlay.
+ *
+ * @throws SecurityException when caller is not allowed to enable/disable {@param packageName}
+ * @throws IllegalStateException when enabling/disabling fails otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.INTERACT_ACROSS_USERS_FULL"
+ })
+ public void setEnabled(@NonNull final String packageName, final boolean enable,
+ @NonNull UserHandle user) throws SecurityException, IllegalStateException {
+ try {
+ if (!mService.setEnabled(packageName, enable, user.getIdentifier())) {
+ throw new IllegalStateException("setEnabled failed");
+ }
+ } catch (SecurityException e) {
+ rethrowSecurityException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns information about the overlay with the given package name for
+ * the specified user.
+ *
+ * @param packageName The name of the package.
+ * @param userHandle The user to get the OverlayInfos for.
+ * @return An OverlayInfo object; if no overlays exist with the
+ * requested package name, null is returned.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public OverlayInfo getOverlayInfo(@NonNull final String packageName,
+ @NonNull final UserHandle userHandle) {
+ try {
+ return mService.getOverlayInfo(packageName, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns information about the overlay represented by the identifier for the specified user.
+ *
+ * @param overlay the identifier representing the overlay
+ * @param userHandle the user of which to get overlay state info
+ * @return the overlay info or null if the overlay cannot be found
+ *
+ * @hide
+ */
+ @Nullable
+ public OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay,
+ @NonNull final UserHandle userHandle) {
+ try {
+ return mService.getOverlayInfoByIdentifier(overlay, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns information about all overlays for the given target package for
+ * the specified user. The returned list is ordered according to the
+ * overlay priority with the highest priority at the end of the list.
+ *
+ * @param targetPackageName The name of the target package.
+ * @param user The user to get the OverlayInfos for.
+ * @return A list of OverlayInfo objects; if no overlays exist for the
+ * requested package, an empty list is returned.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.INTERACT_ACROSS_USERS_FULL"
+ })
+ @NonNull
+ public List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName,
+ @NonNull UserHandle user) {
+ try {
+ return mService.getOverlayInfosForTarget(targetPackageName, user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clear part of the overlay manager's internal cache of PackageInfo
+ * objects. Only intended for testing.
+ *
+ * @param targetPackageName The name of the target package.
+ * @param user The user to get the OverlayInfos for.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ })
+ @NonNull
+ public void invalidateCachesForOverlay(@NonNull final String targetPackageName,
+ @NonNull UserHandle user) {
+ try {
+ mService.invalidateCachesForOverlay(targetPackageName, user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Perform a series of requests related to overlay packages. This is an
+ * atomic operation: either all requests were performed successfully and
+ * the changes were propagated to the rest of the system, or at least one
+ * request could not be performed successfully and nothing is changed and
+ * nothing is propagated to the rest of the system.
+ *
+ * @see OverlayManagerTransaction
+ *
+ * @param transaction the series of overlay related requests to perform
+ * @throws Exception if not all the requests could be successfully and
+ * atomically executed
+ *
+ * @hide
+ */
+ public void commit(@NonNull final OverlayManagerTransaction transaction) {
+ try {
+ mService.commit(transaction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starting on R, actor enforcement and app visibility changes introduce additional failure
+ * cases, but the SecurityException thrown with these checks is unexpected for existing
+ * consumers of the API.
+ *
+ * The only prior case it would be thrown is with a permission failure, but the calling
+ * application would be able to verify that themselves, and so they may choose to ignore
+ * catching SecurityException when calling these APIs.
+ *
+ * For R, this no longer holds true, and SecurityExceptions can be thrown for any number of
+ * reasons, none of which are exposed to the caller. So for consumers targeting below R,
+ * transform these SecurityExceptions into IllegalStateExceptions, which are a little more
+ * expected to be thrown by the setEnabled APIs.
+ *
+ * This will mask the prior permission exception if it applies, but it's assumed that apps
+ * wouldn't call the APIs without the permission on prior versions, and so it's safe to ignore.
+ */
+ private void rethrowSecurityException(SecurityException e) {
+ if (!Compatibility.isChangeEnabled(THROW_SECURITY_EXCEPTIONS)) {
+ throw new IllegalStateException(e);
+ } else {
+ throw e;
+ }
+ }
+}
diff --git a/android/content/om/OverlayManagerTransaction.java b/android/content/om/OverlayManagerTransaction.java
new file mode 100644
index 0000000..73be0ff
--- /dev/null
+++ b/android/content/om/OverlayManagerTransaction.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2019 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.om;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Container for a batch of requests to the OverlayManagerService.
+ *
+ * Transactions are created using a builder interface. Example usage:
+ *
+ * final OverlayManager om = ctx.getSystemService(OverlayManager.class);
+ * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
+ * .setEnabled(...)
+ * .setEnabled(...)
+ * .build();
+ * om.commit(t);
+ *
+ * @hide
+ */
+public class OverlayManagerTransaction
+ implements Iterable<OverlayManagerTransaction.Request>, Parcelable {
+ // TODO: remove @hide from this class when OverlayManager is added to the
+ // SDK, but keep OverlayManagerTransaction.Request @hidden
+ private final List<Request> mRequests;
+
+ OverlayManagerTransaction(@NonNull final List<Request> requests) {
+ checkNotNull(requests);
+ if (requests.contains(null)) {
+ throw new IllegalArgumentException("null request");
+ }
+ mRequests = requests;
+ }
+
+ private OverlayManagerTransaction(@NonNull final Parcel source) {
+ final int size = source.readInt();
+ mRequests = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ final int request = source.readInt();
+ final OverlayIdentifier overlay = source.readParcelable(null);
+ final int userId = source.readInt();
+ final Bundle extras = source.readBundle(null);
+ mRequests.add(new Request(request, overlay, userId, extras));
+ }
+ }
+
+ @Override
+ public Iterator<Request> iterator() {
+ return mRequests.iterator();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OverlayManagerTransaction { mRequests = %s }", mRequests);
+ }
+
+ /**
+ * A single unit of the transaction, such as a request to enable an
+ * overlay, or to disable an overlay.
+ *
+ * @hide
+ */
+ public static class Request {
+ @IntDef(prefix = "TYPE_", value = {
+ TYPE_SET_ENABLED,
+ TYPE_SET_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RequestType {}
+
+ public static final int TYPE_SET_ENABLED = 0;
+ public static final int TYPE_SET_DISABLED = 1;
+ public static final int TYPE_REGISTER_FABRICATED = 2;
+ public static final int TYPE_UNREGISTER_FABRICATED = 3;
+
+ public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
+
+ @RequestType
+ public final int type;
+ @NonNull
+ public final OverlayIdentifier overlay;
+ public final int userId;
+ @Nullable
+ public final Bundle extras;
+
+ public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
+ final int userId) {
+ this(type, overlay, userId, null /* extras */);
+ }
+
+ public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
+ final int userId, @Nullable Bundle extras) {
+ this.type = type;
+ this.overlay = overlay;
+ this.userId = userId;
+ this.extras = extras;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
+ type, typeToString(), overlay, userId);
+ }
+
+ /**
+ * Translate the request type into a human readable string. Only
+ * intended for debugging.
+ *
+ * @hide
+ */
+ public String typeToString() {
+ switch (type) {
+ case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
+ case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
+ case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
+ case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
+ default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
+ }
+ }
+ }
+
+ /**
+ * Builder class for OverlayManagerTransaction objects.
+ *
+ * @hide
+ */
+ public static class Builder {
+ private final List<Request> mRequests = new ArrayList<>();
+
+ /**
+ * Request that an overlay package be enabled and change its loading
+ * order to the last package to be loaded, or disabled
+ *
+ * If the caller has the correct permissions, it is always possible to
+ * disable an overlay. Due to technical and security reasons it may not
+ * always be possible to enable an overlay, for instance if the overlay
+ * does not successfully overlay any target resources due to
+ * overlayable policy restrictions.
+ *
+ * An enabled overlay is a part of target package's resources, i.e. it will
+ * be part of any lookups performed via {@link android.content.res.Resources}
+ * and {@link android.content.res.AssetManager}. A disabled overlay will no
+ * longer affect the resources of the target package. If the target is
+ * currently running, its outdated resources will be replaced by new ones.
+ *
+ * @param overlay The name of the overlay package.
+ * @param enable true to enable the overlay, false to disable it.
+ * @return this Builder object, so you can chain additional requests
+ */
+ public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable) {
+ return setEnabled(overlay, enable, UserHandle.myUserId());
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) {
+ checkNotNull(overlay);
+ @Request.RequestType final int type =
+ enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
+ mRequests.add(new Request(type, overlay, userId));
+ return this;
+ }
+
+ /**
+ * Registers the fabricated overlay with the overlay manager so it can be enabled and
+ * disabled for any user.
+ *
+ * The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
+ * the existing overlay will be replaced by the newly registered overlay and the enabled
+ * state of the overlay will be left unchanged if the target package and target overlayable
+ * have not changed.
+ *
+ * @param overlay the overlay to register with the overlay manager
+ *
+ * @hide
+ */
+ public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
+ mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
+ UserHandle.USER_ALL, extras));
+ return this;
+ }
+
+ /**
+ * Disables and removes the overlay from the overlay manager for all users.
+ *
+ * @param overlay the overlay to disable and remove
+ *
+ * @hide
+ */
+ public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+ mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
+ UserHandle.USER_ALL));
+ return this;
+ }
+
+ /**
+ * Create a new transaction out of the requests added so far. Execute
+ * the transaction by calling OverlayManager#commit.
+ *
+ * @see OverlayManager#commit
+ * @return a new transaction
+ */
+ public OverlayManagerTransaction build() {
+ return new OverlayManagerTransaction(mRequests);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int size = mRequests.size();
+ dest.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ final Request req = mRequests.get(i);
+ dest.writeInt(req.type);
+ dest.writeParcelable(req.overlay, flags);
+ dest.writeInt(req.userId);
+ dest.writeBundle(req.extras);
+ }
+ }
+
+ public static final Parcelable.Creator<OverlayManagerTransaction> CREATOR =
+ new Parcelable.Creator<OverlayManagerTransaction>() {
+
+ @Override
+ public OverlayManagerTransaction createFromParcel(Parcel source) {
+ return new OverlayManagerTransaction(source);
+ }
+
+ @Override
+ public OverlayManagerTransaction[] newArray(int size) {
+ return new OverlayManagerTransaction[size];
+ }
+ };
+}
diff --git a/android/content/om/OverlayableInfo.java b/android/content/om/OverlayableInfo.java
new file mode 100644
index 0000000..6bd42d9
--- /dev/null
+++ b/android/content/om/OverlayableInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * Immutable info on an overlayable defined inside a target package.
+ *
+ * @hide
+ */
+@DataClass(genSetters = false, genEqualsHashCode = true, genHiddenConstructor = true)
+public final class OverlayableInfo {
+
+ /**
+ * The "name" attribute of the overlayable tag. Used to identify the set of resources overlaid.
+ */
+ @NonNull
+ public final String name;
+
+ /**
+ * The "actor" attribute of the overlayable tag. Used to signal which apps are allowed to
+ * modify overlay state for this overlayable.
+ */
+ @Nullable
+ public final String actor;
+
+ // CHECKSTYLE:OFF Generated code
+ //
+
+
+
+ // Code below generated by codegen v1.0.3.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayableInfo.java
+
+
+ /**
+ * Creates a new OverlayableInfo.
+ *
+ * @param name
+ * The "name" attribute of the overlayable tag. Used to identify the set of resources overlaid.
+ * @param actor
+ * The "actor" attribute of the overlayable tag. Used to signal which apps are allowed to
+ * modify overlay state for this overlayable.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public OverlayableInfo(
+ @NonNull String name,
+ @Nullable String actor) {
+ this.name = name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.actor = actor;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(OverlayableInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ OverlayableInfo that = (OverlayableInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(name, that.name)
+ && Objects.equals(actor, that.actor);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(name);
+ _hash = 31 * _hash + Objects.hashCode(actor);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1570059850579L,
+ codegenVersion = "1.0.3",
+ sourceFile = "frameworks/base/core/java/android/content/om/OverlayableInfo.java",
+ inputSignatures = "public final @android.annotation.NonNull java.lang.String name\npublic final @android.annotation.Nullable java.lang.String actor\nclass OverlayableInfo extends java.lang.Object implements []\[email protected](genSetters=false, genEqualsHashCode=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
new file mode 100644
index 0000000..95c5612
--- /dev/null
+++ b/android/content/pm/ActivityInfo.java
@@ -0,0 +1,1836 @@
+/*
+ * Copyright (C) 2007 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.IntDef;
+import android.annotation.TestApi;
+import android.app.Activity;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Configuration.NativeConfig;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.Printer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information you can retrieve about a particular application
+ * activity or receiver. This corresponds to information collected
+ * from the AndroidManifest.xml's <activity> and
+ * <receiver> tags.
+ */
+public class ActivityInfo extends ComponentInfo implements Parcelable {
+
+ // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
+ // constructor, and writeToParcel.
+
+ /**
+ * A style resource identifier (in the package's resources) of this
+ * activity's theme. From the "theme" attribute or, if not set, 0.
+ */
+ public int theme;
+
+ /**
+ * Constant corresponding to <code>standard</code> in
+ * the {@link android.R.attr#launchMode} attribute.
+ */
+ public static final int LAUNCH_MULTIPLE = 0;
+ /**
+ * Constant corresponding to <code>singleTop</code> in
+ * the {@link android.R.attr#launchMode} attribute.
+ */
+ public static final int LAUNCH_SINGLE_TOP = 1;
+ /**
+ * Constant corresponding to <code>singleTask</code> in
+ * the {@link android.R.attr#launchMode} attribute.
+ */
+ public static final int LAUNCH_SINGLE_TASK = 2;
+ /**
+ * Constant corresponding to <code>singleInstance</code> in
+ * the {@link android.R.attr#launchMode} attribute.
+ */
+ public static final int LAUNCH_SINGLE_INSTANCE = 3;
+ /**
+ * Constant corresponding to <code>singleInstancePerTask</code> in
+ * the {@link android.R.attr#launchMode} attribute.
+ */
+ public static final int LAUNCH_SINGLE_INSTANCE_PER_TASK = 4;
+
+ /** @hide */
+ @IntDef(prefix = "LAUNCH_", value = {
+ LAUNCH_MULTIPLE,
+ LAUNCH_SINGLE_TOP,
+ LAUNCH_SINGLE_TASK,
+ LAUNCH_SINGLE_INSTANCE,
+ LAUNCH_SINGLE_INSTANCE_PER_TASK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LaunchMode {
+ }
+
+ /**
+ * The launch mode style requested by the activity. From the
+ * {@link android.R.attr#launchMode} attribute.
+ */
+ @LaunchMode
+ public int launchMode;
+
+ /**
+ * Constant corresponding to <code>none</code> in
+ * the {@link android.R.attr#documentLaunchMode} attribute.
+ */
+ public static final int DOCUMENT_LAUNCH_NONE = 0;
+ /**
+ * Constant corresponding to <code>intoExisting</code> in
+ * the {@link android.R.attr#documentLaunchMode} attribute.
+ */
+ public static final int DOCUMENT_LAUNCH_INTO_EXISTING = 1;
+ /**
+ * Constant corresponding to <code>always</code> in
+ * the {@link android.R.attr#documentLaunchMode} attribute.
+ */
+ public static final int DOCUMENT_LAUNCH_ALWAYS = 2;
+ /**
+ * Constant corresponding to <code>never</code> in
+ * the {@link android.R.attr#documentLaunchMode} attribute.
+ */
+ public static final int DOCUMENT_LAUNCH_NEVER = 3;
+ /**
+ * The document launch mode style requested by the activity. From the
+ * {@link android.R.attr#documentLaunchMode} attribute, one of
+ * {@link #DOCUMENT_LAUNCH_NONE}, {@link #DOCUMENT_LAUNCH_INTO_EXISTING},
+ * {@link #DOCUMENT_LAUNCH_ALWAYS}.
+ *
+ * <p>Modes DOCUMENT_LAUNCH_ALWAYS
+ * and DOCUMENT_LAUNCH_INTO_EXISTING are equivalent to {@link
+ * android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT
+ * Intent.FLAG_ACTIVITY_NEW_DOCUMENT} with and without {@link
+ * android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK
+ * Intent.FLAG_ACTIVITY_MULTIPLE_TASK} respectively.
+ */
+ public int documentLaunchMode;
+
+ /**
+ * Constant corresponding to <code>persistRootOnly</code> in
+ * the {@link android.R.attr#persistableMode} attribute.
+ */
+ public static final int PERSIST_ROOT_ONLY = 0;
+ /**
+ * Constant corresponding to <code>doNotPersist</code> in
+ * the {@link android.R.attr#persistableMode} attribute.
+ */
+ public static final int PERSIST_NEVER = 1;
+ /**
+ * Constant corresponding to <code>persistAcrossReboots</code> in
+ * the {@link android.R.attr#persistableMode} attribute.
+ */
+ public static final int PERSIST_ACROSS_REBOOTS = 2;
+ /**
+ * Value indicating how this activity is to be persisted across
+ * reboots for restoring in the Recents list.
+ * {@link android.R.attr#persistableMode}
+ */
+ public int persistableMode;
+
+ /**
+ * The maximum number of tasks rooted at this activity that can be in the recent task list.
+ * Refer to {@link android.R.attr#maxRecents}.
+ */
+ public int maxRecents;
+
+ /**
+ * Optional name of a permission required to be able to access this
+ * Activity. From the "permission" attribute.
+ */
+ public String permission;
+
+ /**
+ * The affinity this activity has for another task in the system. The
+ * string here is the name of the task, often the package name of the
+ * overall package. If null, the activity has no affinity. Set from the
+ * {@link android.R.attr#taskAffinity} attribute.
+ */
+ public String taskAffinity;
+
+ /**
+ * If this is an activity alias, this is the real activity class to run
+ * for it. Otherwise, this is null.
+ */
+ public String targetActivity;
+
+ /**
+ * Token used to string together multiple events within a single launch action.
+ * @hide
+ */
+ public String launchToken;
+
+ /**
+ * Activity can not be resized and always occupies the fullscreen area with all windows fully
+ * visible.
+ * @hide
+ */
+ public static final int RESIZE_MODE_UNRESIZEABLE = 0;
+ /**
+ * Activity didn't explicitly request to be resizeable, but we are making it resizeable because
+ * of the SDK version it targets. Only affects apps with target SDK >= N where the app is
+ * implied to be resizeable if it doesn't explicitly set the attribute to any value.
+ * @hide
+ */
+ public static final int RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION = 1;
+ /**
+ * Activity explicitly requested to be resizeable.
+ * @hide
+ */
+ @TestApi
+ public static final int RESIZE_MODE_RESIZEABLE = 2;
+ /**
+ * Activity is resizeable and supported picture-in-picture mode. This flag is now deprecated
+ * since activities do not need to be resizeable to support picture-in-picture.
+ * See {@link #FLAG_SUPPORTS_PICTURE_IN_PICTURE}.
+ *
+ * @hide
+ * @deprecated
+ */
+ public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED = 3;
+ /**
+ * Activity does not support resizing, but we are forcing it to be resizeable. Only affects
+ * certain pre-N apps where we force them to be resizeable.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZEABLE = 4;
+ /**
+ * Activity does not support resizing, but we are forcing it to be resizeable as long
+ * as the size remains landscape.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY = 5;
+ /**
+ * Activity does not support resizing, but we are forcing it to be resizeable as long
+ * as the size remains portrait.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY = 6;
+ /**
+ * Activity does not support resizing, but we are forcing it to be resizeable as long
+ * as the bounds remain in the same orientation as they are.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION = 7;
+ /**
+ * Value indicating if the resizing mode the activity supports.
+ * See {@link android.R.attr#resizeableActivity}.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int resizeMode = RESIZE_MODE_RESIZEABLE;
+
+ /**
+ * Value indicating the maximum aspect ratio the activity supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#maxAspectRatio}.
+ * @hide
+ */
+ private float mMaxAspectRatio;
+
+ /**
+ * Value indicating the minimum aspect ratio the activity supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#minAspectRatio}.
+ * @hide
+ */
+ private float mMinAspectRatio;
+
+ /**
+ * Indicates that the activity works well with size changes like display changing size.
+ *
+ * @hide
+ */
+ public boolean supportsSizeChanges;
+
+ /**
+ * Name of the VrListenerService component to run for this activity.
+ * @see android.R.attr#enableVrMode
+ * @hide
+ */
+ public String requestedVrComponent;
+
+ /**
+ * Value for {@link #colorMode} indicating that the activity should use the
+ * default color mode (sRGB, low dynamic range).
+ *
+ * @see android.R.attr#colorMode
+ */
+ public static final int COLOR_MODE_DEFAULT = 0;
+ /**
+ * Value of {@link #colorMode} indicating that the activity should use a
+ * wide color gamut if the presentation display supports it.
+ *
+ * @see android.R.attr#colorMode
+ */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1;
+ /**
+ * Value of {@link #colorMode} indicating that the activity should use a
+ * high dynamic range if the presentation display supports it.
+ *
+ * @see android.R.attr#colorMode
+ */
+ public static final int COLOR_MODE_HDR = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "COLOR_MODE_" }, value = {
+ COLOR_MODE_DEFAULT,
+ COLOR_MODE_WIDE_COLOR_GAMUT,
+ COLOR_MODE_HDR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorMode {}
+
+ /**
+ * The color mode requested by this activity. The target display may not be
+ * able to honor the request.
+ */
+ @ColorMode
+ public int colorMode = COLOR_MODE_DEFAULT;
+
+ /**
+ * Bit in {@link #flags} indicating whether this activity is able to
+ * run in multiple processes. If
+ * true, the system may instantiate it in the some process as the
+ * process starting it in order to conserve resources. If false, the
+ * default, it always runs in {@link #processName}. Set from the
+ * {@link android.R.attr#multiprocess} attribute.
+ */
+ public static final int FLAG_MULTIPROCESS = 0x0001;
+ /**
+ * Bit in {@link #flags} indicating that, when the activity's task is
+ * relaunched from home, this activity should be finished.
+ * Set from the
+ * {@link android.R.attr#finishOnTaskLaunch} attribute.
+ */
+ public static final int FLAG_FINISH_ON_TASK_LAUNCH = 0x0002;
+ /**
+ * Bit in {@link #flags} indicating that, when the activity is the root
+ * of a task, that task's stack should be cleared each time the user
+ * re-launches it from home. As a result, the user will always
+ * return to the original activity at the top of the task.
+ * This flag only applies to activities that
+ * are used to start the root of a new task. Set from the
+ * {@link android.R.attr#clearTaskOnLaunch} attribute.
+ */
+ public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 0x0004;
+ /**
+ * Bit in {@link #flags} indicating that, when the activity is the root
+ * of a task, that task's stack should never be cleared when it is
+ * relaunched from home. Set from the
+ * {@link android.R.attr#alwaysRetainTaskState} attribute.
+ */
+ public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 0x0008;
+ /**
+ * Bit in {@link #flags} indicating that the activity's state
+ * is not required to be saved, so that if there is a failure the
+ * activity will not be removed from the activity stack. Set from the
+ * {@link android.R.attr#stateNotNeeded} attribute.
+ */
+ public static final int FLAG_STATE_NOT_NEEDED = 0x0010;
+ /**
+ * Bit in {@link #flags} that indicates that the activity should not
+ * appear in the list of recently launched activities. Set from the
+ * {@link android.R.attr#excludeFromRecents} attribute.
+ */
+ public static final int FLAG_EXCLUDE_FROM_RECENTS = 0x0020;
+ /**
+ * Bit in {@link #flags} that indicates that the activity can be moved
+ * between tasks based on its task affinity. Set from the
+ * {@link android.R.attr#allowTaskReparenting} attribute.
+ */
+ public static final int FLAG_ALLOW_TASK_REPARENTING = 0x0040;
+ /**
+ * Bit in {@link #flags} indicating that, when the user navigates away
+ * from an activity, it should be finished.
+ * Set from the
+ * {@link android.R.attr#noHistory} attribute.
+ */
+ public static final int FLAG_NO_HISTORY = 0x0080;
+ /**
+ * Bit in {@link #flags} indicating that, when a request to close system
+ * windows happens, this activity is finished.
+ * Set from the
+ * {@link android.R.attr#finishOnCloseSystemDialogs} attribute.
+ */
+ public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 0x0100;
+ /**
+ * Value for {@link #flags}: true when the application's rendering should
+ * be hardware accelerated.
+ */
+ public static final int FLAG_HARDWARE_ACCELERATED = 0x0200;
+ /**
+ * Value for {@link #flags}: true when the application can be displayed for all users
+ * regardless of if the user of the application is the current user. Set from the
+ * {@link android.R.attr#showForAllUsers} attribute.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400;
+ /**
+ * Bit in {@link #flags} corresponding to an immersive activity
+ * that wishes not to be interrupted by notifications.
+ * Applications that hide the system notification bar with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN}
+ * may still be interrupted by high-priority notifications; for example, an
+ * incoming phone call may use
+ * {@link android.app.Notification#fullScreenIntent fullScreenIntent}
+ * to present a full-screen in-call activity to the user, pausing the
+ * current activity as a side-effect. An activity with
+ * {@link #FLAG_IMMERSIVE} set, however, will not be interrupted; the
+ * notification may be shown in some other way (such as a small floating
+ * "toast" window).
+ *
+ * Note that this flag will always reflect the Activity's
+ * <code>android:immersive</code> manifest definition, even if the Activity's
+ * immersive state is changed at runtime via
+ * {@link android.app.Activity#setImmersive(boolean)}.
+ *
+ * @see android.app.Notification#FLAG_HIGH_PRIORITY
+ * @see android.app.Activity#setImmersive(boolean)
+ */
+ public static final int FLAG_IMMERSIVE = 0x0800;
+ /**
+ * Bit in {@link #flags}: If set, a task rooted at this activity will have its
+ * baseIntent replaced by the activity immediately above this. Each activity may further
+ * relinquish its identity to the activity above it using this flag. Set from the
+ * {@link android.R.attr#relinquishTaskIdentity} attribute.
+ */
+ public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000;
+ /**
+ * Bit in {@link #flags} indicating that tasks started with this activity are to be
+ * removed from the recent list of tasks when the last activity in the task is finished.
+ * Corresponds to {@link android.R.attr#autoRemoveFromRecents}
+ */
+ public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 0x2000;
+ /**
+ * Bit in {@link #flags} indicating that this activity can start is creation/resume
+ * while the previous activity is still pausing. Corresponds to
+ * {@link android.R.attr#resumeWhilePausing}
+ */
+ public static final int FLAG_RESUME_WHILE_PAUSING = 0x4000;
+ /**
+ * Bit in {@link #flags} indicating that this activity should be run with VR mode enabled.
+ *
+ * @see android.app.Activity#setVrModeEnabled(boolean, ComponentName)
+ */
+ public static final int FLAG_ENABLE_VR_MODE = 0x8000;
+
+ /**
+ * Bit in {@link #flags} indicating if the activity is always focusable regardless of if it is
+ * in a task/stack whose activities are normally not focusable.
+ * See android.R.attr#alwaysFocusable.
+ * @hide
+ */
+ public static final int FLAG_ALWAYS_FOCUSABLE = 0x40000;
+
+ /**
+ * Bit in {@link #flags} indicating if the activity is visible to instant
+ * applications. The activity is visible if it's either implicitly or
+ * explicitly exposed.
+ * @hide
+ */
+ public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
+
+ /**
+ * Bit in {@link #flags} indicating if the activity is implicitly visible
+ * to instant applications. Implicitly visible activities are those that
+ * implement certain intent-filters:
+ * <ul>
+ * <li>action {@link Intent#CATEGORY_BROWSABLE}</li>
+ * <li>action {@link Intent#ACTION_SEND}</li>
+ * <li>action {@link Intent#ACTION_SENDTO}</li>
+ * <li>action {@link Intent#ACTION_SEND_MULTIPLE}</li>
+ * </ul>
+ * @hide
+ */
+ public static final int FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP = 0x200000;
+
+ /**
+ * Bit in {@link #flags} indicating if the activity supports picture-in-picture mode.
+ * See {@link android.R.attr#supportsPictureInPicture}.
+ * @hide
+ */
+ public static final int FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x400000;
+
+ /**
+ * Bit in {@link #flags} indicating if the activity should be shown when locked.
+ * See {@link android.R.attr#showWhenLocked}
+ * @hide
+ */
+ public static final int FLAG_SHOW_WHEN_LOCKED = 0x800000;
+
+ /**
+ * Bit in {@link #flags} indicating if the screen should turn on when starting the activity.
+ * See {@link android.R.attr#turnScreenOn}
+ * @hide
+ */
+ public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
+
+ /**
+ * Bit in {@link #flags} indicating whether the display should preferably be switched to a
+ * minimal post processing mode.
+ * See {@link android.R.attr#preferMinimalPostProcessing}
+ */
+ public static final int FLAG_PREFER_MINIMAL_POST_PROCESSING = 0x2000000;
+
+ /**
+ * @hide Bit in {@link #flags}: If set, this component will only be seen
+ * by the system user. Only works with broadcast receivers. Set from the
+ * android.R.attr#systemUserOnly attribute.
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = 0x20000000;
+ /**
+ * Bit in {@link #flags}: If set, a single instance of the receiver will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute. Note that this flag is
+ * only relevant for ActivityInfo structures that are describing receiver
+ * components; it is not applied to activities.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+ /**
+ * @hide Bit in {@link #flags}: If set, this activity may be launched into an
+ * owned ActivityContainer such as that within an ActivityView. If not set and
+ * this activity is launched into such a container a SecurityException will be
+ * thrown. Set from the {@link android.R.attr#allowEmbedded} attribute.
+ *
+ * @deprecated this flag is no longer needed since ActivityView is now fully removed
+ * TODO(b/191165536): delete this flag since is no longer used
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @Deprecated
+ public static final int FLAG_ALLOW_EMBEDDED = 0x80000000;
+
+ /**
+ * Options that have been set in the activity declaration in the
+ * manifest.
+ * These include:
+ * {@link #FLAG_MULTIPROCESS},
+ * {@link #FLAG_FINISH_ON_TASK_LAUNCH}, {@link #FLAG_CLEAR_TASK_ON_LAUNCH},
+ * {@link #FLAG_ALWAYS_RETAIN_TASK_STATE},
+ * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
+ * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
+ * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
+ * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}.
+ */
+ public int flags;
+
+ /**
+ * Bit in {@link #privateFlags} indicating if the activity should be shown when locked in case
+ * an activity behind this can also be shown when locked.
+ * See {@link android.R.attr#inheritShowWhenLocked}.
+ * @hide
+ */
+ public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
+
+ /**
+ * Bit in {@link #privateFlags} indicating whether a home sound effect should be played if the
+ * home app moves to front after the activity with this flag set.
+ * Set from the {@link android.R.attr#playHomeTransitionSound} attribute.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_HOME_TRANSITION_SOUND = 0x2;
+
+ /**
+ * Options that have been set in the activity declaration in the manifest.
+ * These include:
+ * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED},
+ * {@link #PRIVATE_FLAG_HOME_TRANSITION_SOUND}.
+ * @hide
+ */
+ public int privateFlags;
+
+ /** @hide */
+ @IntDef(prefix = { "SCREEN_ORIENTATION_" }, value = {
+ SCREEN_ORIENTATION_UNSET,
+ SCREEN_ORIENTATION_UNSPECIFIED,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ SCREEN_ORIENTATION_PORTRAIT,
+ SCREEN_ORIENTATION_USER,
+ SCREEN_ORIENTATION_BEHIND,
+ SCREEN_ORIENTATION_SENSOR,
+ SCREEN_ORIENTATION_NOSENSOR,
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
+ SCREEN_ORIENTATION_SENSOR_PORTRAIT,
+ SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
+ SCREEN_ORIENTATION_REVERSE_PORTRAIT,
+ SCREEN_ORIENTATION_FULL_SENSOR,
+ SCREEN_ORIENTATION_USER_LANDSCAPE,
+ SCREEN_ORIENTATION_USER_PORTRAIT,
+ SCREEN_ORIENTATION_FULL_USER,
+ SCREEN_ORIENTATION_LOCKED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScreenOrientation {}
+
+ /**
+ * Internal constant used to indicate that the app didn't set a specific orientation value.
+ * Different from {@link #SCREEN_ORIENTATION_UNSPECIFIED} below as the app can set its
+ * orientation to {@link #SCREEN_ORIENTATION_UNSPECIFIED} while this means that the app didn't
+ * set anything. The system will mostly treat this similar to
+ * {@link #SCREEN_ORIENTATION_UNSPECIFIED}.
+ * @hide
+ */
+ public static final int SCREEN_ORIENTATION_UNSET = -2;
+ /**
+ * Constant corresponding to <code>unspecified</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_UNSPECIFIED = -1;
+ /**
+ * Constant corresponding to <code>landscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_LANDSCAPE = 0;
+ /**
+ * Constant corresponding to <code>portrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_PORTRAIT = 1;
+ /**
+ * Constant corresponding to <code>user</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_USER = 2;
+ /**
+ * Constant corresponding to <code>behind</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_BEHIND = 3;
+ /**
+ * Constant corresponding to <code>sensor</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR = 4;
+
+ /**
+ * Constant corresponding to <code>nosensor</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_NOSENSOR = 5;
+
+ /**
+ * Constant corresponding to <code>sensorLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
+
+ /**
+ * Constant corresponding to <code>sensorPortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7;
+
+ /**
+ * Constant corresponding to <code>reverseLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
+
+ /**
+ * Constant corresponding to <code>reversePortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
+
+ /**
+ * Constant corresponding to <code>fullSensor</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_FULL_SENSOR = 10;
+
+ /**
+ * Constant corresponding to <code>userLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11;
+
+ /**
+ * Constant corresponding to <code>userPortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12;
+
+ /**
+ * Constant corresponding to <code>fullUser</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_FULL_USER = 13;
+
+ /**
+ * Constant corresponding to <code>locked</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_LOCKED = 14;
+
+ /**
+ * The preferred screen orientation this activity would like to run in.
+ * From the {@link android.R.attr#screenOrientation} attribute, one of
+ * {@link #SCREEN_ORIENTATION_UNSPECIFIED},
+ * {@link #SCREEN_ORIENTATION_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_USER},
+ * {@link #SCREEN_ORIENTATION_BEHIND},
+ * {@link #SCREEN_ORIENTATION_SENSOR},
+ * {@link #SCREEN_ORIENTATION_NOSENSOR},
+ * {@link #SCREEN_ORIENTATION_SENSOR_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_SENSOR_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_REVERSE_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_FULL_SENSOR},
+ * {@link #SCREEN_ORIENTATION_USER_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_USER_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_FULL_USER},
+ * {@link #SCREEN_ORIENTATION_LOCKED},
+ */
+ @ScreenOrientation
+ public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "CONFIG_" }, value = {
+ CONFIG_MCC,
+ CONFIG_MNC,
+ CONFIG_LOCALE,
+ CONFIG_TOUCHSCREEN,
+ CONFIG_KEYBOARD,
+ CONFIG_KEYBOARD_HIDDEN,
+ CONFIG_NAVIGATION,
+ CONFIG_ORIENTATION,
+ CONFIG_SCREEN_LAYOUT,
+ CONFIG_UI_MODE,
+ CONFIG_SCREEN_SIZE,
+ CONFIG_SMALLEST_SCREEN_SIZE,
+ CONFIG_DENSITY,
+ CONFIG_LAYOUT_DIRECTION,
+ CONFIG_COLOR_MODE,
+ CONFIG_FONT_SCALE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Config {}
+
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the IMSI MCC. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_MCC = 0x0001;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the IMSI MNC. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_MNC = 0x0002;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the locale. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_LOCALE = 0x0004;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the touchscreen type. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_TOUCHSCREEN = 0x0008;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the keyboard type. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_KEYBOARD = 0x0010;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the keyboard or navigation being hidden/exposed.
+ * Note that inspite of the name, this applies to the changes to any
+ * hidden states: keyboard or navigation.
+ * Set from the {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_KEYBOARD_HIDDEN = 0x0020;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the navigation type. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_NAVIGATION = 0x0040;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the screen orientation. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_ORIENTATION = 0x0080;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the screen layout. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_SCREEN_LAYOUT = 0x0100;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the ui mode. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_UI_MODE = 0x0200;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the screen size. Set from the
+ * {@link android.R.attr#configChanges} attribute. This will be
+ * set by default for applications that target an earlier version
+ * than {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}...
+ * <b>however</b>, you will not see the bit set here becomes some
+ * applications incorrectly compare {@link #configChanges} against
+ * an absolute value rather than correctly masking out the bits
+ * they are interested in. Please don't do that, thanks.
+ */
+ public static final int CONFIG_SCREEN_SIZE = 0x0400;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the smallest screen size. Set from the
+ * {@link android.R.attr#configChanges} attribute. This will be
+ * set by default for applications that target an earlier version
+ * than {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}...
+ * <b>however</b>, you will not see the bit set here becomes some
+ * applications incorrectly compare {@link #configChanges} against
+ * an absolute value rather than correctly masking out the bits
+ * they are interested in. Please don't do that, thanks.
+ */
+ public static final int CONFIG_SMALLEST_SCREEN_SIZE = 0x0800;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle density changes. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_DENSITY = 0x1000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the change to layout direction. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_LAYOUT_DIRECTION = 0x2000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the change to the display color gamut or dynamic
+ * range. Set from the {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_COLOR_MODE = 0x4000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle asset path changes. Set from the {@link android.R.attr#configChanges}
+ * attribute. This is not a core resource configuration, but a higher-level value, so its
+ * constant starts at the high bits.
+ * @hide We do not want apps handling this yet, but we do need some kind of bit for diffs.
+ */
+ public static final int CONFIG_ASSETS_PATHS = 0x80000000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the font scaling factor. Set from the
+ * {@link android.R.attr#configChanges} attribute. This is
+ * not a core resource configuration, but a higher-level value, so its
+ * constant starts at the high bits.
+ */
+ public static final int CONFIG_FONT_SCALE = 0x40000000;
+ /**
+ * Bit indicating changes to window configuration that isn't exposed to apps.
+ * This is for internal use only and apps don't handle it.
+ * @hide
+ * {@link Configuration}.
+ */
+ public static final int CONFIG_WINDOW_CONFIGURATION = 0x20000000;
+
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to font weight. Set from the
+ * {@link android.R.attr#configChanges} attribute. This is
+ * not a core resource configuration, but a higher-level value, so its
+ * constant starts at the high bits.
+ */
+
+ public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000;
+
+ /** @hide
+ * Unfortunately the constants for config changes in native code are
+ * different from ActivityInfo. :( Here are the values we should use for the
+ * native side given the bit we have assigned in ActivityInfo.
+ */
+ public static int[] CONFIG_NATIVE_BITS = new int[] {
+ Configuration.NATIVE_CONFIG_MNC, // MNC
+ Configuration.NATIVE_CONFIG_MCC, // MCC
+ Configuration.NATIVE_CONFIG_LOCALE, // LOCALE
+ Configuration.NATIVE_CONFIG_TOUCHSCREEN, // TOUCH SCREEN
+ Configuration.NATIVE_CONFIG_KEYBOARD, // KEYBOARD
+ Configuration.NATIVE_CONFIG_KEYBOARD_HIDDEN, // KEYBOARD HIDDEN
+ Configuration.NATIVE_CONFIG_NAVIGATION, // NAVIGATION
+ Configuration.NATIVE_CONFIG_ORIENTATION, // ORIENTATION
+ Configuration.NATIVE_CONFIG_SCREEN_LAYOUT, // SCREEN LAYOUT
+ Configuration.NATIVE_CONFIG_UI_MODE, // UI MODE
+ Configuration.NATIVE_CONFIG_SCREEN_SIZE, // SCREEN SIZE
+ Configuration.NATIVE_CONFIG_SMALLEST_SCREEN_SIZE, // SMALLEST SCREEN SIZE
+ Configuration.NATIVE_CONFIG_DENSITY, // DENSITY
+ Configuration.NATIVE_CONFIG_LAYOUTDIR, // LAYOUT DIRECTION
+ Configuration.NATIVE_CONFIG_COLOR_MODE, // COLOR_MODE
+ };
+
+ /**
+ * This change id forces the packages it is applied to be resizable. It won't change whether
+ * the app can be put into multi-windowing mode, but allow the app to resize when the window
+ * container resizes, such as display size change.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long FORCE_RESIZE_APP = 174042936L; // buganizer id
+
+ /**
+ * This change id forces the packages it is applied to to be non-resizable.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long FORCE_NON_RESIZE_APP = 181136395L; // buganizer id
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity does not
+ * support size changes due to the android.supports_size_changes metadata flag either being
+ * unset or set to {@code false} on application or activity level.
+ *
+ * @hide
+ */
+ public static final int SIZE_CHANGES_UNSUPPORTED_METADATA = 0;
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity has been
+ * overridden to not support size changes through the compat framework change id
+ * {@link #FORCE_NON_RESIZE_APP}.
+ * @hide
+ */
+ public static final int SIZE_CHANGES_UNSUPPORTED_OVERRIDE = 1;
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity supports size
+ * changes due to the android.supports_size_changes metadata flag being set to {@code true}
+ * either on application or activity level.
+ * @hide
+ */
+ public static final int SIZE_CHANGES_SUPPORTED_METADATA = 2;
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity has been
+ * overridden to support size changes through the compat framework change id
+ * {@link #FORCE_RESIZE_APP}.
+ * @hide
+ */
+ public static final int SIZE_CHANGES_SUPPORTED_OVERRIDE = 3;
+
+ /** @hide */
+ @IntDef(prefix = { "SIZE_CHANGES_" }, value = {
+ SIZE_CHANGES_UNSUPPORTED_METADATA,
+ SIZE_CHANGES_UNSUPPORTED_OVERRIDE,
+ SIZE_CHANGES_SUPPORTED_METADATA,
+ SIZE_CHANGES_SUPPORTED_OVERRIDE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SizeChangesSupportMode {}
+
+ /**
+ * This change id forces the packages it is applied to never have Display API sandboxing
+ * applied for a letterbox or SCM activity. The Display APIs will continue to provide
+ * DisplayArea bounds.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long NEVER_SANDBOX_DISPLAY_APIS = 184838306L; // buganizer id
+
+ /**
+ * This change id forces the packages it is applied to always have Display API sandboxing
+ * applied, regardless of windowing mode. The Display APIs will always provide the app bounds.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
+
+ /**
+ * This change id is the gatekeeper for all treatments that force a given min aspect ratio.
+ * Enabling this change will allow the following min aspect ratio treatments to be applied:
+ * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
+ * OVERRIDE_MIN_ASPECT_RATIO_LARGE
+ *
+ * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's
+ * manifest will be overridden to the largest enabled aspect ratio treatment unless the app's
+ * manifest value is higher.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
+
+ /**
+ * This change id sets the activity's min aspect ratio to a medium value as defined by
+ * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE.
+ *
+ * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // buganizer id
+
+ /** @hide Medium override aspect ratio, currently 3:2. */
+ @TestApi
+ public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 3 / 2f;
+
+ /**
+ * This change id sets the activity's min aspect ratio to a large value as defined by
+ * OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE.
+ *
+ * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_LARGE = 180326787L; // buganizer id
+
+ /** @hide Large override aspect ratio, currently 16:9 */
+ @TestApi
+ public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 16 / 9f;
+
+ /**
+ * Convert Java change bits to native.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static @NativeConfig int activityInfoConfigJavaToNative(@Config int input) {
+ int output = 0;
+ for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) {
+ if ((input & (1 << i)) != 0) {
+ output |= CONFIG_NATIVE_BITS[i];
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Convert native change bits to Java.
+ *
+ * @hide
+ */
+ public static @Config int activityInfoConfigNativeToJava(@NativeConfig int input) {
+ int output = 0;
+ for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) {
+ if ((input & CONFIG_NATIVE_BITS[i]) != 0) {
+ output |= (1 << i);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * @hide
+ * Unfortunately some developers (OpenFeint I am looking at you) have
+ * compared the configChanges bit field against absolute values, so if we
+ * introduce a new bit they break. To deal with that, we will make sure
+ * the public field will not have a value that breaks them, and let the
+ * framework call here to get the real value.
+ */
+ public int getRealConfigChanged() {
+ return applicationInfo.targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB_MR2
+ ? (configChanges | ActivityInfo.CONFIG_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE)
+ : configChanges;
+ }
+
+ /**
+ * Bit mask of kinds of configuration changes that this activity
+ * can handle itself (without being restarted by the system).
+ * Contains any combination of {@link #CONFIG_FONT_SCALE},
+ * {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
+ * {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
+ * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
+ * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
+ * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION} and
+ * {@link #CONFIG_COLOR_MODE}.
+ * Set from the {@link android.R.attr#configChanges} attribute.
+ */
+ public int configChanges;
+
+ /**
+ * The desired soft input mode for this activity's main window.
+ * Set from the {@link android.R.attr#windowSoftInputMode} attribute
+ * in the activity's manifest. May be any of the same values allowed
+ * for {@link android.view.WindowManager.LayoutParams#softInputMode
+ * WindowManager.LayoutParams.softInputMode}. If 0 (unspecified),
+ * the mode from the theme will be used.
+ */
+ @android.view.WindowManager.LayoutParams.SoftInputModeFlags
+ public int softInputMode;
+
+ /**
+ * The desired extra UI options for this activity and its main window.
+ * Set from the {@link android.R.attr#uiOptions} attribute in the
+ * activity's manifest.
+ */
+ public int uiOptions = 0;
+
+ /**
+ * Flag for use with {@link #uiOptions}.
+ * Indicates that the action bar should put all action items in a separate bar when
+ * the screen is narrow.
+ * <p>This value corresponds to "splitActionBarWhenNarrow" for the {@link #uiOptions} XML
+ * attribute.
+ */
+ public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1;
+
+ /**
+ * If defined, the activity named here is the logical parent of this activity.
+ */
+ public String parentActivityName;
+
+ /**
+ * Screen rotation animation desired by the activity, with values as defined
+ * for {@link android.view.WindowManager.LayoutParams#rotationAnimation}.
+ *
+ * -1 means to use the system default.
+ *
+ * @hide
+ */
+ public int rotationAnimation = -1;
+
+ /** @hide */
+ public static final int LOCK_TASK_LAUNCH_MODE_DEFAULT = 0;
+ /** @hide */
+ public static final int LOCK_TASK_LAUNCH_MODE_NEVER = 1;
+ /** @hide */
+ public static final int LOCK_TASK_LAUNCH_MODE_ALWAYS = 2;
+ /** @hide */
+ public static final int LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED = 3;
+
+ /** @hide */
+ public static final String lockTaskLaunchModeToString(int lockTaskLaunchMode) {
+ switch (lockTaskLaunchMode) {
+ case LOCK_TASK_LAUNCH_MODE_DEFAULT:
+ return "LOCK_TASK_LAUNCH_MODE_DEFAULT";
+ case LOCK_TASK_LAUNCH_MODE_NEVER:
+ return "LOCK_TASK_LAUNCH_MODE_NEVER";
+ case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+ return "LOCK_TASK_LAUNCH_MODE_ALWAYS";
+ case LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED:
+ return "LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED";
+ default:
+ return "unknown=" + lockTaskLaunchMode;
+ }
+ }
+ /**
+ * Value indicating if the activity is to be locked at startup. Takes on the values from
+ * {@link android.R.attr#lockTaskMode}.
+ * @hide
+ */
+ public int lockTaskLaunchMode;
+
+ /**
+ * Information about desired position and size of activity on the display when
+ * it is first started.
+ */
+ public WindowLayout windowLayout;
+
+ public ActivityInfo() {
+ }
+
+ public ActivityInfo(ActivityInfo orig) {
+ super(orig);
+ theme = orig.theme;
+ launchMode = orig.launchMode;
+ documentLaunchMode = orig.documentLaunchMode;
+ permission = orig.permission;
+ taskAffinity = orig.taskAffinity;
+ targetActivity = orig.targetActivity;
+ flags = orig.flags;
+ privateFlags = orig.privateFlags;
+ screenOrientation = orig.screenOrientation;
+ configChanges = orig.configChanges;
+ softInputMode = orig.softInputMode;
+ uiOptions = orig.uiOptions;
+ parentActivityName = orig.parentActivityName;
+ maxRecents = orig.maxRecents;
+ lockTaskLaunchMode = orig.lockTaskLaunchMode;
+ windowLayout = orig.windowLayout;
+ resizeMode = orig.resizeMode;
+ requestedVrComponent = orig.requestedVrComponent;
+ rotationAnimation = orig.rotationAnimation;
+ colorMode = orig.colorMode;
+ mMaxAspectRatio = orig.mMaxAspectRatio;
+ mMinAspectRatio = orig.mMinAspectRatio;
+ supportsSizeChanges = orig.supportsSizeChanges;
+ }
+
+ /**
+ * Return the theme resource identifier to use for this activity. If
+ * the activity defines a theme, that is used; else, the application
+ * theme is used.
+ *
+ * @return The theme associated with this activity.
+ */
+ public final int getThemeResource() {
+ return theme != 0 ? theme : applicationInfo.theme;
+ }
+
+ private String persistableModeToString() {
+ switch(persistableMode) {
+ case PERSIST_ROOT_ONLY: return "PERSIST_ROOT_ONLY";
+ case PERSIST_NEVER: return "PERSIST_NEVER";
+ case PERSIST_ACROSS_REBOOTS: return "PERSIST_ACROSS_REBOOTS";
+ default: return "UNKNOWN=" + persistableMode;
+ }
+ }
+
+ /**
+ * Returns true if the activity has maximum or minimum aspect ratio.
+ * @hide
+ */
+ public boolean hasFixedAspectRatio() {
+ return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+ }
+
+ /**
+ * Returns true if the activity's orientation is fixed.
+ * @hide
+ */
+ public boolean isFixedOrientation() {
+ return isFixedOrientationLandscape() || isFixedOrientationPortrait()
+ || screenOrientation == SCREEN_ORIENTATION_LOCKED;
+ }
+
+ /**
+ * Returns true if the activity's orientation is fixed to landscape.
+ * @hide
+ */
+ boolean isFixedOrientationLandscape() {
+ return isFixedOrientationLandscape(screenOrientation);
+ }
+
+ /**
+ * Returns true if the activity's orientation is fixed to landscape.
+ * @hide
+ */
+ public static boolean isFixedOrientationLandscape(@ScreenOrientation int orientation) {
+ return orientation == SCREEN_ORIENTATION_LANDSCAPE
+ || orientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ || orientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+ || orientation == SCREEN_ORIENTATION_USER_LANDSCAPE;
+ }
+
+ /**
+ * Returns true if the activity's orientation is fixed to portrait.
+ * @hide
+ */
+ boolean isFixedOrientationPortrait() {
+ return isFixedOrientationPortrait(screenOrientation);
+ }
+
+ /**
+ * Returns true if the activity's orientation is fixed to portrait.
+ * @hide
+ */
+ public static boolean isFixedOrientationPortrait(@ScreenOrientation int orientation) {
+ return orientation == SCREEN_ORIENTATION_PORTRAIT
+ || orientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ || orientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT
+ || orientation == SCREEN_ORIENTATION_USER_PORTRAIT;
+ }
+
+ /**
+ * Returns the reversed orientation.
+ * @hide
+ */
+ @ActivityInfo.ScreenOrientation
+ public static int reverseOrientation(@ActivityInfo.ScreenOrientation int orientation) {
+ switch (orientation) {
+ case SCREEN_ORIENTATION_LANDSCAPE:
+ return SCREEN_ORIENTATION_PORTRAIT;
+ case SCREEN_ORIENTATION_PORTRAIT:
+ return SCREEN_ORIENTATION_LANDSCAPE;
+ case SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ return SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ case SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ return SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ return SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ return SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ case SCREEN_ORIENTATION_USER_LANDSCAPE:
+ return SCREEN_ORIENTATION_USER_PORTRAIT;
+ case SCREEN_ORIENTATION_USER_PORTRAIT:
+ return SCREEN_ORIENTATION_USER_LANDSCAPE;
+ default:
+ return orientation;
+ }
+ }
+
+ /**
+ * Returns true if the activity supports picture-in-picture.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean supportsPictureInPicture() {
+ return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
+ }
+
+ /**
+ * Returns whether the activity supports size changes.
+ * @hide
+ */
+ @SizeChangesSupportMode
+ public int supportsSizeChanges() {
+ if (CompatChanges.isChangeEnabled(FORCE_NON_RESIZE_APP,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
+ }
+
+ if (supportsSizeChanges) {
+ return SIZE_CHANGES_SUPPORTED_METADATA;
+ }
+
+ if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return SIZE_CHANGES_SUPPORTED_OVERRIDE;
+ }
+
+ return SIZE_CHANGES_UNSUPPORTED_METADATA;
+ }
+
+ /**
+ * Returns if the activity should never be sandboxed to the activity window bounds.
+ * @hide
+ */
+ public boolean neverSandboxDisplayApis() {
+ return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))
+ || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo);
+ }
+
+ /**
+ * Returns if the activity should always be sandboxed to the activity window bounds.
+ * @hide
+ */
+ public boolean alwaysSandboxDisplayApis() {
+ return CompatChanges.isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))
+ || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo);
+ }
+
+ /** @hide */
+ public void setMaxAspectRatio(float maxAspectRatio) {
+ this.mMaxAspectRatio = maxAspectRatio;
+ }
+
+ /** @hide */
+ public float getMaxAspectRatio() {
+ return mMaxAspectRatio;
+ }
+
+ /** @hide */
+ public void setMinAspectRatio(float minAspectRatio) {
+ this.mMinAspectRatio = minAspectRatio;
+ }
+
+ /**
+ * Returns the min aspect ratio of this activity.
+ *
+ * This takes into account the minimum aspect ratio as defined in the app's manifest and
+ * possible overrides as per OVERRIDE_MIN_ASPECT_RATIO.
+ *
+ * In the rare cases where the manifest minimum aspect ratio is required, use
+ * {@code getManifestMinAspectRatio}.
+ * @hide
+ */
+ public float getMinAspectRatio() {
+ if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return mMinAspectRatio;
+ }
+
+ if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio);
+ }
+
+ if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio);
+ }
+
+ return mMinAspectRatio;
+ }
+
+ /** @hide */
+ public float getManifestMinAspectRatio() {
+ return mMinAspectRatio;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static boolean isResizeableMode(int mode) {
+ return mode == RESIZE_MODE_RESIZEABLE
+ || mode == RESIZE_MODE_FORCE_RESIZEABLE
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION
+ || mode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+ }
+
+ /** @hide */
+ public static boolean isPreserveOrientationMode(int mode) {
+ return mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ }
+
+ /** @hide */
+ public static String resizeModeToString(int mode) {
+ switch (mode) {
+ case RESIZE_MODE_UNRESIZEABLE:
+ return "RESIZE_MODE_UNRESIZEABLE";
+ case RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
+ return "RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION";
+ case RESIZE_MODE_RESIZEABLE:
+ return "RESIZE_MODE_RESIZEABLE";
+ case RESIZE_MODE_FORCE_RESIZEABLE:
+ return "RESIZE_MODE_FORCE_RESIZEABLE";
+ case RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY:
+ return "RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY";
+ case RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY:
+ return "RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY";
+ case RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION:
+ return "RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION";
+ default:
+ return "unknown=" + mode;
+ }
+ }
+
+ /** @hide */
+ public static String sizeChangesSupportModeToString(@SizeChangesSupportMode int mode) {
+ switch (mode) {
+ case SIZE_CHANGES_UNSUPPORTED_METADATA:
+ return "SIZE_CHANGES_UNSUPPORTED_METADATA";
+ case SIZE_CHANGES_UNSUPPORTED_OVERRIDE:
+ return "SIZE_CHANGES_UNSUPPORTED_OVERRIDE";
+ case SIZE_CHANGES_SUPPORTED_METADATA:
+ return "SIZE_CHANGES_SUPPORTED_METADATA";
+ case SIZE_CHANGES_SUPPORTED_OVERRIDE:
+ return "SIZE_CHANGES_SUPPORTED_OVERRIDE";
+ default:
+ return "unknown=" + mode;
+ }
+ }
+
+ public void dump(Printer pw, String prefix) {
+ dump(pw, prefix, DUMP_FLAG_ALL);
+ }
+
+ /** @hide */
+ public void dump(Printer pw, String prefix, int dumpFlags) {
+ super.dumpFront(pw, prefix);
+ if (permission != null) {
+ pw.println(prefix + "permission=" + permission);
+ }
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ pw.println(prefix + "taskAffinity=" + taskAffinity
+ + " targetActivity=" + targetActivity
+ + " persistableMode=" + persistableModeToString());
+ }
+ if (launchMode != 0 || flags != 0 || privateFlags != 0 || theme != 0) {
+ pw.println(prefix + "launchMode=" + launchMode
+ + " flags=0x" + Integer.toHexString(flags)
+ + " privateFlags=0x" + Integer.toHexString(privateFlags)
+ + " theme=0x" + Integer.toHexString(theme));
+ }
+ if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED
+ || configChanges != 0 || softInputMode != 0) {
+ pw.println(prefix + "screenOrientation=" + screenOrientation
+ + " configChanges=0x" + Integer.toHexString(configChanges)
+ + " softInputMode=0x" + Integer.toHexString(softInputMode));
+ }
+ if (uiOptions != 0) {
+ pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions));
+ }
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ pw.println(prefix + "lockTaskLaunchMode="
+ + lockTaskLaunchModeToString(lockTaskLaunchMode));
+ }
+ if (windowLayout != null) {
+ pw.println(prefix + "windowLayout=" + windowLayout.width + "|"
+ + windowLayout.widthFraction + ", " + windowLayout.height + "|"
+ + windowLayout.heightFraction + ", " + windowLayout.gravity);
+ }
+ pw.println(prefix + "resizeMode=" + resizeModeToString(resizeMode));
+ if (requestedVrComponent != null) {
+ pw.println(prefix + "requestedVrComponent=" + requestedVrComponent);
+ }
+ if (getMaxAspectRatio() != 0) {
+ pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
+ }
+ if (getMinAspectRatio() != 0) {
+ pw.println(prefix + "minAspectRatio=" + getMinAspectRatio());
+ if (getManifestMinAspectRatio() != getMinAspectRatio()) {
+ pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
+ }
+ }
+ if (supportsSizeChanges) {
+ pw.println(prefix + "supportsSizeChanges=true");
+ }
+ super.dumpBack(pw, prefix, dumpFlags);
+ }
+
+ public String toString() {
+ return "ActivityInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeInt(theme);
+ dest.writeInt(launchMode);
+ dest.writeInt(documentLaunchMode);
+ dest.writeString8(permission);
+ dest.writeString8(taskAffinity);
+ dest.writeString8(targetActivity);
+ dest.writeString8(launchToken);
+ dest.writeInt(flags);
+ dest.writeInt(privateFlags);
+ dest.writeInt(screenOrientation);
+ dest.writeInt(configChanges);
+ dest.writeInt(softInputMode);
+ dest.writeInt(uiOptions);
+ dest.writeString8(parentActivityName);
+ dest.writeInt(persistableMode);
+ dest.writeInt(maxRecents);
+ dest.writeInt(lockTaskLaunchMode);
+ if (windowLayout != null) {
+ dest.writeInt(1);
+ windowLayout.writeToParcel(dest);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(resizeMode);
+ dest.writeString8(requestedVrComponent);
+ dest.writeInt(rotationAnimation);
+ dest.writeInt(colorMode);
+ dest.writeFloat(mMaxAspectRatio);
+ dest.writeFloat(mMinAspectRatio);
+ dest.writeBoolean(supportsSizeChanges);
+ }
+
+ /**
+ * Determines whether the {@link Activity} is considered translucent or floating.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public static boolean isTranslucentOrFloating(TypedArray attributes) {
+ final boolean isTranslucent =
+ attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent,
+ false);
+ final boolean isFloating =
+ attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating,
+ false);
+
+ return isFloating || isTranslucent;
+ }
+
+ /**
+ * Convert the screen orientation constant to a human readable format.
+ * @hide
+ */
+ public static String screenOrientationToString(int orientation) {
+ switch (orientation) {
+ case SCREEN_ORIENTATION_UNSET:
+ return "SCREEN_ORIENTATION_UNSET";
+ case SCREEN_ORIENTATION_UNSPECIFIED:
+ return "SCREEN_ORIENTATION_UNSPECIFIED";
+ case SCREEN_ORIENTATION_LANDSCAPE:
+ return "SCREEN_ORIENTATION_LANDSCAPE";
+ case SCREEN_ORIENTATION_PORTRAIT:
+ return "SCREEN_ORIENTATION_PORTRAIT";
+ case SCREEN_ORIENTATION_USER:
+ return "SCREEN_ORIENTATION_USER";
+ case SCREEN_ORIENTATION_BEHIND:
+ return "SCREEN_ORIENTATION_BEHIND";
+ case SCREEN_ORIENTATION_SENSOR:
+ return "SCREEN_ORIENTATION_SENSOR";
+ case SCREEN_ORIENTATION_NOSENSOR:
+ return "SCREEN_ORIENTATION_NOSENSOR";
+ case SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ return "SCREEN_ORIENTATION_SENSOR_LANDSCAPE";
+ case SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ return "SCREEN_ORIENTATION_SENSOR_PORTRAIT";
+ case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ return "SCREEN_ORIENTATION_REVERSE_LANDSCAPE";
+ case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ return "SCREEN_ORIENTATION_REVERSE_PORTRAIT";
+ case SCREEN_ORIENTATION_FULL_SENSOR:
+ return "SCREEN_ORIENTATION_FULL_SENSOR";
+ case SCREEN_ORIENTATION_USER_LANDSCAPE:
+ return "SCREEN_ORIENTATION_USER_LANDSCAPE";
+ case SCREEN_ORIENTATION_USER_PORTRAIT:
+ return "SCREEN_ORIENTATION_USER_PORTRAIT";
+ case SCREEN_ORIENTATION_FULL_USER:
+ return "SCREEN_ORIENTATION_FULL_USER";
+ case SCREEN_ORIENTATION_LOCKED:
+ return "SCREEN_ORIENTATION_LOCKED";
+ default:
+ return Integer.toString(orientation);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String colorModeToString(@ColorMode int colorMode) {
+ switch (colorMode) {
+ case COLOR_MODE_DEFAULT:
+ return "COLOR_MODE_DEFAULT";
+ case COLOR_MODE_WIDE_COLOR_GAMUT:
+ return "COLOR_MODE_WIDE_COLOR_GAMUT";
+ case COLOR_MODE_HDR:
+ return "COLOR_MODE_HDR";
+ default:
+ return Integer.toString(colorMode);
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ActivityInfo> CREATOR
+ = new Parcelable.Creator<ActivityInfo>() {
+ public ActivityInfo createFromParcel(Parcel source) {
+ return new ActivityInfo(source);
+ }
+ public ActivityInfo[] newArray(int size) {
+ return new ActivityInfo[size];
+ }
+ };
+
+ private ActivityInfo(Parcel source) {
+ super(source);
+ theme = source.readInt();
+ launchMode = source.readInt();
+ documentLaunchMode = source.readInt();
+ permission = source.readString8();
+ taskAffinity = source.readString8();
+ targetActivity = source.readString8();
+ launchToken = source.readString8();
+ flags = source.readInt();
+ privateFlags = source.readInt();
+ screenOrientation = source.readInt();
+ configChanges = source.readInt();
+ softInputMode = source.readInt();
+ uiOptions = source.readInt();
+ parentActivityName = source.readString8();
+ persistableMode = source.readInt();
+ maxRecents = source.readInt();
+ lockTaskLaunchMode = source.readInt();
+ if (source.readInt() == 1) {
+ windowLayout = new WindowLayout(source);
+ }
+ resizeMode = source.readInt();
+ requestedVrComponent = source.readString8();
+ rotationAnimation = source.readInt();
+ colorMode = source.readInt();
+ mMaxAspectRatio = source.readFloat();
+ mMinAspectRatio = source.readFloat();
+ supportsSizeChanges = source.readBoolean();
+ }
+
+ /**
+ * Contains information about position and size of the activity on the display.
+ *
+ * Used in freeform mode to set desired position when activity is first launched.
+ * It describes how big the activity wants to be in both width and height,
+ * the minimal allowed size, and the gravity to be applied.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_defaultWidth
+ * @attr ref android.R.styleable#AndroidManifestLayout_defaultHeight
+ * @attr ref android.R.styleable#AndroidManifestLayout_gravity
+ * @attr ref android.R.styleable#AndroidManifestLayout_minWidth
+ * @attr ref android.R.styleable#AndroidManifestLayout_minHeight
+ */
+ public static final class WindowLayout {
+ public WindowLayout(int width, float widthFraction, int height, float heightFraction,
+ int gravity, int minWidth, int minHeight) {
+ this(width, widthFraction, height, heightFraction, gravity, minWidth, minHeight,
+ null /* windowLayoutAffinity */);
+ }
+
+ /** @hide */
+ public WindowLayout(int width, float widthFraction, int height, float heightFraction,
+ int gravity, int minWidth, int minHeight, String windowLayoutAffinity) {
+ this.width = width;
+ this.widthFraction = widthFraction;
+ this.height = height;
+ this.heightFraction = heightFraction;
+ this.gravity = gravity;
+ this.minWidth = minWidth;
+ this.minHeight = minHeight;
+ this.windowLayoutAffinity = windowLayoutAffinity;
+ }
+
+ /** @hide */
+ public WindowLayout(Parcel source) {
+ width = source.readInt();
+ widthFraction = source.readFloat();
+ height = source.readInt();
+ heightFraction = source.readFloat();
+ gravity = source.readInt();
+ minWidth = source.readInt();
+ minHeight = source.readInt();
+ windowLayoutAffinity = source.readString8();
+ }
+
+ /**
+ * Width of activity in pixels.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_defaultWidth
+ */
+ public final int width;
+
+ /**
+ * Width of activity as a fraction of available display width.
+ * If both {@link #width} and this value are set this one will be preferred.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_defaultWidth
+ */
+ public final float widthFraction;
+
+ /**
+ * Height of activity in pixels.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_defaultHeight
+ */
+ public final int height;
+
+ /**
+ * Height of activity as a fraction of available display height.
+ * If both {@link #height} and this value are set this one will be preferred.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_defaultHeight
+ */
+ public final float heightFraction;
+
+ /**
+ * Gravity of activity.
+ * Currently {@link android.view.Gravity#TOP}, {@link android.view.Gravity#BOTTOM},
+ * {@link android.view.Gravity#LEFT} and {@link android.view.Gravity#RIGHT} are supported.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_gravity
+ */
+ public final int gravity;
+
+ /**
+ * Minimal width of activity in pixels to be able to display its content.
+ *
+ * <p><strong>NOTE:</strong> A task's root activity value is applied to all additional
+ * activities launched in the task. That is if the root activity of a task set minimal
+ * width, then the system will set the same minimal width on all other activities in the
+ * task. It will also ignore any other minimal width attributes of non-root activities.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_minWidth
+ */
+ public final int minWidth;
+
+ /**
+ * Minimal height of activity in pixels to be able to display its content.
+ *
+ * <p><strong>NOTE:</strong> A task's root activity value is applied to all additional
+ * activities launched in the task. That is if the root activity of a task set minimal
+ * height, then the system will set the same minimal height on all other activities in the
+ * task. It will also ignore any other minimal height attributes of non-root activities.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_minHeight
+ */
+ public final int minHeight;
+
+ /**
+ * Affinity of window layout parameters. Activities with the same UID and window layout
+ * affinity will share the same window dimension record.
+ *
+ * @attr ref android.R.styleable#AndroidManifestLayout_windowLayoutAffinity
+ * @hide
+ */
+ public String windowLayoutAffinity;
+
+ /**
+ * Returns if this {@link WindowLayout} has specified bounds.
+ * @hide
+ */
+ public boolean hasSpecifiedSize() {
+ return width >= 0 || height >= 0 || widthFraction >= 0 || heightFraction >= 0;
+ }
+
+ /** @hide */
+ public void writeToParcel(Parcel dest) {
+ dest.writeInt(width);
+ dest.writeFloat(widthFraction);
+ dest.writeInt(height);
+ dest.writeFloat(heightFraction);
+ dest.writeInt(gravity);
+ dest.writeInt(minWidth);
+ dest.writeInt(minHeight);
+ dest.writeString8(windowLayoutAffinity);
+ }
+ }
+}
diff --git a/android/content/pm/ActivityPresentationInfo.java b/android/content/pm/ActivityPresentationInfo.java
new file mode 100644
index 0000000..ccc61dc
--- /dev/null
+++ b/android/content/pm/ActivityPresentationInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.content.ComponentName;
+
+/**
+ * Holds basic information about an activity.
+ *
+ * @hide
+ */
+public final class ActivityPresentationInfo {
+ public final int taskId;
+ public final int displayId;
+
+ @NonNull
+ public final ComponentName componentName;
+
+ public ActivityPresentationInfo(int taskId, int displayId,
+ @NonNull ComponentName componentName) {
+ this.taskId = taskId;
+ this.displayId = displayId;
+ this.componentName = componentName;
+ }
+}
diff --git a/android/content/pm/AndroidTestBaseUpdater.java b/android/content/pm/AndroidTestBaseUpdater.java
new file mode 100644
index 0000000..b6bee42
--- /dev/null
+++ b/android/content/pm/AndroidTestBaseUpdater.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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;
+
+/**
+ * Placeholder class to maintain legacy behavior of including a class in core source to toggle
+ * whether or not a shared library is stripped at build time.
+ *
+ * @hide
+ */
+public class AndroidTestBaseUpdater {
+}
diff --git a/android/content/pm/ApkChecksum.java b/android/content/pm/ApkChecksum.java
new file mode 100644
index 0000000..d550f41
--- /dev/null
+++ b/android/content/pm/ApkChecksum.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2020 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.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+/**
+ * A typed checksum of an APK.
+ *
+ * @see PackageManager#requestChecksums
+ */
+@DataClass(genHiddenConstructor = true)
[email protected]({"getChecksum"})
+public final class ApkChecksum implements Parcelable {
+ /**
+ * Checksum for which split. Null indicates base.apk.
+ */
+ private final @Nullable String mSplitName;
+ /**
+ * Checksum.
+ */
+ private final @NonNull Checksum mChecksum;
+ /**
+ * For Installer-provided checksums, package name of the Installer.
+ */
+ private final @Nullable String mInstallerPackageName;
+ /**
+ * For Installer-provided checksums, certificate of the Installer.
+ */
+ private final @Nullable byte[] mInstallerCertificate;
+
+ /**
+ * Constructor, internal use only.
+ *
+ * @hide
+ */
+ public ApkChecksum(@Nullable String splitName, @Checksum.Type int type,
+ @NonNull byte[] value) {
+ this(splitName, new Checksum(type, value), (String) null, (byte[]) null);
+ }
+
+ /**
+ * Constructor, internal use only.
+ *
+ * @hide
+ */
+ public ApkChecksum(@Nullable String splitName, @Checksum.Type int type, @NonNull byte[] value,
+ @Nullable String sourcePackageName, @Nullable Certificate sourceCertificate)
+ throws CertificateEncodingException {
+ this(splitName, new Checksum(type, value), sourcePackageName,
+ (sourceCertificate != null) ? sourceCertificate.getEncoded() : null);
+ }
+
+
+ /**
+ * Checksum type.
+ */
+ public @Checksum.Type int getType() {
+ return mChecksum.getType();
+ }
+
+ /**
+ * Checksum value.
+ */
+ public @NonNull byte[] getValue() {
+ return mChecksum.getValue();
+ }
+
+ /**
+ * Returns raw bytes representing encoded certificate of the Installer.
+ * @hide
+ */
+ public @Nullable byte[] getInstallerCertificateBytes() {
+ return mInstallerCertificate;
+ }
+
+ /**
+ * For Installer-provided checksums, certificate of the Installer.
+ * @throws CertificateException in case when certificate can't be re-created from serialized
+ * data.
+ */
+ public @Nullable Certificate getInstallerCertificate() throws CertificateException {
+ if (mInstallerCertificate == null) {
+ return null;
+ }
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ final InputStream is = new ByteArrayInputStream(mInstallerCertificate);
+ final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+ return cert;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ApkChecksum.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ApkChecksum.
+ *
+ * @param splitName
+ * Checksum for which split. Null indicates base.apk.
+ * @param checksum
+ * Checksum.
+ * @param installerPackageName
+ * For Installer-provided checksums, package name of the Installer.
+ * @param installerCertificate
+ * For Installer-provided checksums, certificate of the Installer.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public ApkChecksum(
+ @Nullable String splitName,
+ @NonNull Checksum checksum,
+ @Nullable String installerPackageName,
+ @Nullable byte[] installerCertificate) {
+ this.mSplitName = splitName;
+ this.mChecksum = checksum;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mChecksum);
+ this.mInstallerPackageName = installerPackageName;
+ this.mInstallerCertificate = installerCertificate;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Checksum for which split. Null indicates base.apk.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getSplitName() {
+ return mSplitName;
+ }
+
+ /**
+ * For Installer-provided checksums, package name of the Installer.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getInstallerPackageName() {
+ return mInstallerPackageName;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mSplitName != null) flg |= 0x1;
+ if (mInstallerPackageName != null) flg |= 0x4;
+ if (mInstallerCertificate != null) flg |= 0x8;
+ dest.writeByte(flg);
+ if (mSplitName != null) dest.writeString(mSplitName);
+ dest.writeTypedObject(mChecksum, flags);
+ if (mInstallerPackageName != null) dest.writeString(mInstallerPackageName);
+ if (mInstallerCertificate != null) dest.writeByteArray(mInstallerCertificate);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ApkChecksum(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String splitName = (flg & 0x1) == 0 ? null : in.readString();
+ Checksum checksum = (Checksum) in.readTypedObject(Checksum.CREATOR);
+ String installerPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ byte[] installerCertificate = (flg & 0x8) == 0 ? null : in.createByteArray();
+
+ this.mSplitName = splitName;
+ this.mChecksum = checksum;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mChecksum);
+ this.mInstallerPackageName = installerPackageName;
+ this.mInstallerCertificate = installerCertificate;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ApkChecksum> CREATOR
+ = new Parcelable.Creator<ApkChecksum>() {
+ @Override
+ public ApkChecksum[] newArray(int size) {
+ return new ApkChecksum[size];
+ }
+
+ @Override
+ public ApkChecksum createFromParcel(@NonNull Parcel in) {
+ return new ApkChecksum(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1619810171079L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/ApkChecksum.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/AppSearchPerson.java b/android/content/pm/AppSearchPerson.java
new file mode 100644
index 0000000..98d150b
--- /dev/null
+++ b/android/content/pm/AppSearchPerson.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 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.app.Person;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.net.UriCodec;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+public class AppSearchPerson extends GenericDocument {
+
+ /** The name of the schema type for {@link Person} documents.*/
+ public static final String SCHEMA_TYPE = "Person";
+
+ public static final String KEY_NAME = "name";
+ public static final String KEY_KEY = "key";
+ public static final String KEY_IS_BOT = "isBot";
+ public static final String KEY_IS_IMPORTANT = "isImportant";
+
+ public AppSearchPerson(@NonNull GenericDocument document) {
+ super(document);
+ }
+
+ public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_NAME)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_KEY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(KEY_IS_BOT)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .build()
+
+ ).addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(KEY_IS_IMPORTANT)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .build()
+
+ ).build();
+
+ /** hide */
+ @NonNull
+ public static AppSearchPerson instance(@NonNull final Person person) {
+ Objects.requireNonNull(person);
+ final String id;
+ if (person.getUri() != null) {
+ id = person.getUri();
+ } else {
+ // NOTE: an identifier is required even when uri is null.
+ id = UUID.randomUUID().toString();
+ }
+ return new Builder(id).setName(person.getName())
+ .setKey(person.getKey()).setIsBot(person.isBot())
+ .setIsImportant(person.isImportant()).build();
+ }
+
+ /** hide */
+ @NonNull
+ public Person toPerson() {
+ String uri;
+ try {
+ uri = UriCodec.decode(
+ getId(), false /* convertPlus */, StandardCharsets.UTF_8,
+ true /* throwOnFailure */);
+ } catch (IllegalArgumentException e) {
+ uri = null;
+ }
+ return new Person.Builder().setName(getPropertyString(KEY_NAME))
+ .setUri(uri).setKey(getPropertyString(KEY_KEY))
+ .setBot(getPropertyBoolean(KEY_IS_BOT))
+ .setImportant(getPropertyBoolean(KEY_IS_IMPORTANT)).build();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static class Builder extends GenericDocument.Builder<Builder> {
+
+ public Builder(@NonNull final String id) {
+ super(/*namespace=*/ "", id, SCHEMA_TYPE);
+ }
+
+ /** @hide */
+ @NonNull
+ public Builder setName(@Nullable final CharSequence name) {
+ if (name != null) {
+ setPropertyString(KEY_NAME, name.toString());
+ }
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ public Builder setKey(@Nullable final String key) {
+ if (key != null) {
+ setPropertyString(KEY_KEY, key);
+ }
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ public Builder setIsBot(final boolean isBot) {
+ setPropertyBoolean(KEY_IS_BOT, isBot);
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ public Builder setIsImportant(final boolean isImportant) {
+ setPropertyBoolean(KEY_IS_IMPORTANT, isImportant);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public AppSearchPerson build() {
+ return new AppSearchPerson(super.build());
+ }
+ }
+}
diff --git a/android/content/pm/AppSearchShortcutInfo.java b/android/content/pm/AppSearchShortcutInfo.java
new file mode 100644
index 0000000..806091e
--- /dev/null
+++ b/android/content/pm/AppSearchShortcutInfo.java
@@ -0,0 +1,935 @@
+/*
+ * Copyright (C) 2021 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.UserIdInt;
+import android.app.Person;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.LocusId;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class AppSearchShortcutInfo extends GenericDocument {
+
+ /** The name of the schema type for {@link ShortcutInfo} documents.*/
+ public static final String SCHEMA_TYPE = "Shortcut";
+ public static final int SCHEMA_VERSION = 2;
+
+ public static final String KEY_ACTIVITY = "activity";
+ public static final String KEY_SHORT_LABEL = "shortLabel";
+ public static final String KEY_SHORT_LABEL_RES_ID = "shortLabelResId";
+ public static final String KEY_SHORT_LABEL_RES_NAME = "shortLabelResName";
+ public static final String KEY_LONG_LABEL = "longLabel";
+ public static final String KEY_LONG_LABEL_RES_ID = "longLabelResId";
+ public static final String KEY_LONG_LABEL_RES_NAME = "longLabelResName";
+ public static final String KEY_DISABLED_MESSAGE = "disabledMessage";
+ public static final String KEY_DISABLED_MESSAGE_RES_ID = "disabledMessageResId";
+ public static final String KEY_DISABLED_MESSAGE_RES_NAME = "disabledMessageResName";
+ public static final String KEY_CATEGORIES = "categories";
+ public static final String KEY_INTENTS = "intents";
+ public static final String KEY_INTENT_PERSISTABLE_EXTRAS = "intentPersistableExtras";
+ public static final String KEY_PERSON = "person";
+ public static final String KEY_LOCUS_ID = "locusId";
+ public static final String KEY_RANK = "rank";
+ public static final String KEY_IMPLICIT_RANK = "implicitRank";
+ public static final String KEY_EXTRAS = "extras";
+ public static final String KEY_FLAGS = "flags";
+ public static final String KEY_ICON_RES_ID = "iconResId";
+ public static final String KEY_ICON_RES_NAME = "iconResName";
+ public static final String KEY_ICON_URI = "iconUri";
+ public static final String KEY_BITMAP_PATH = "bitmapPath";
+ public static final String KEY_DISABLED_REASON = "disabledReason";
+
+ public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ACTIVITY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_SHORT_LABEL)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build()
+
+ ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_SHORT_LABEL_RES_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_SHORT_LABEL_RES_NAME)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LONG_LABEL)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build()
+
+ ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_LONG_LABEL_RES_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LONG_LABEL_RES_NAME)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_MESSAGE)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(
+ KEY_DISABLED_MESSAGE_RES_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ KEY_DISABLED_MESSAGE_RES_NAME)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_CATEGORIES)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_INTENTS)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
+ KEY_INTENT_PERSISTABLE_EXTRAS)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build()
+
+ ).addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
+ KEY_PERSON, AppSearchPerson.SCHEMA_TYPE)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_LOCUS_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_RANK)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+ .build()
+
+ ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_IMPLICIT_RANK)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build()
+
+ ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(KEY_EXTRAS)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_FLAGS)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+ .build()
+
+ ).addProperty(new AppSearchSchema.LongPropertyConfig.Builder(KEY_ICON_RES_ID)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ICON_RES_NAME)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_ICON_URI)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_BITMAP_PATH)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+ .build()
+
+ ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_DISABLED_REASON)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+ .build()
+
+ ).build();
+
+ /**
+ * The string representation of every flag within {@link ShortcutInfo}. Note that its value
+ * needs to be camelCase since AppSearch's tokenizer will break the word when it sees
+ * underscore.
+ */
+ private static final String IS_DYNAMIC = "Dyn";
+ private static final String NOT_DYNAMIC = "nDyn";
+ private static final String IS_PINNED = "Pin";
+ private static final String NOT_PINNED = "nPin";
+ private static final String HAS_ICON_RES = "IcR";
+ private static final String NO_ICON_RES = "nIcR";
+ private static final String HAS_ICON_FILE = "IcF";
+ private static final String NO_ICON_FILE = "nIcF";
+ private static final String IS_KEY_FIELD_ONLY = "Key";
+ private static final String NOT_KEY_FIELD_ONLY = "nKey";
+ private static final String IS_MANIFEST = "Man";
+ private static final String NOT_MANIFEST = "nMan";
+ private static final String IS_DISABLED = "Dis";
+ private static final String NOT_DISABLED = "nDis";
+ private static final String ARE_STRINGS_RESOLVED = "Str";
+ private static final String NOT_STRINGS_RESOLVED = "nStr";
+ private static final String IS_IMMUTABLE = "Im";
+ private static final String NOT_IMMUTABLE = "nIm";
+ private static final String HAS_ADAPTIVE_BITMAP = "IcA";
+ private static final String NO_ADAPTIVE_BITMAP = "nIcA";
+ private static final String IS_RETURNED_BY_SERVICE = "Rets";
+ private static final String NOT_RETURNED_BY_SERVICE = "nRets";
+ private static final String HAS_ICON_FILE_PENDING_SAVE = "Pens";
+ private static final String NO_ICON_FILE_PENDING_SAVE = "nPens";
+ private static final String IS_SHADOW = "Sdw";
+ private static final String NOT_SHADOW = "nSdw";
+ private static final String IS_LONG_LIVED = "Liv";
+ private static final String NOT_LONG_LIVED = "nLiv";
+ private static final String HAS_ICON_URI = "IcU";
+ private static final String NO_ICON_URI = "nIcU";
+ private static final String IS_CACHED_NOTIFICATION = "CaN";
+ private static final String NOT_CACHED_NOTIFICATION = "nCaN";
+ private static final String IS_CACHED_BUBBLE = "CaB";
+ private static final String NOT_CACHED_BUBBLE = "nCaB";
+ private static final String IS_CACHED_PEOPLE_TITLE = "CaPT";
+ private static final String NOT_CACHED_PEOPLE_TITLE = "nCaPT";
+
+ /**
+ * Following flags are not store within ShortcutInfo, but book-keeping states to reduce search
+ * space when performing queries against AppSearch.
+ */
+ private static final String HAS_BITMAP_PATH = "hBiP";
+ private static final String HAS_STRING_RESOURCE = "hStr";
+ private static final String HAS_NON_ZERO_RANK = "hRan";
+
+ public static final String QUERY_IS_DYNAMIC = KEY_FLAGS + ":" + IS_DYNAMIC;
+ public static final String QUERY_IS_NOT_DYNAMIC = KEY_FLAGS + ":" + NOT_DYNAMIC;
+ public static final String QUERY_IS_PINNED = KEY_FLAGS + ":" + IS_PINNED;
+ public static final String QUERY_IS_NOT_PINNED = KEY_FLAGS + ":" + NOT_PINNED;
+ public static final String QUERY_IS_MANIFEST = KEY_FLAGS + ":" + IS_MANIFEST;
+ public static final String QUERY_IS_NOT_MANIFEST = KEY_FLAGS + ":" + NOT_MANIFEST;
+ public static final String QUERY_IS_PINNED_AND_ENABLED =
+ "(" + KEY_FLAGS + ":" + IS_PINNED + " " + KEY_FLAGS + ":" + NOT_DISABLED + ")";
+ public static final String QUERY_IS_CACHED =
+ "(" + KEY_FLAGS + ":" + IS_CACHED_NOTIFICATION + " OR "
+ + KEY_FLAGS + ":" + IS_CACHED_BUBBLE + " OR "
+ + KEY_FLAGS + ":" + IS_CACHED_PEOPLE_TITLE + ")";
+ public static final String QUERY_IS_NOT_CACHED =
+ "(" + KEY_FLAGS + ":" + NOT_CACHED_NOTIFICATION + " "
+ + KEY_FLAGS + ":" + NOT_CACHED_BUBBLE + " "
+ + KEY_FLAGS + ":" + NOT_CACHED_PEOPLE_TITLE + ")";
+ public static final String QUERY_IS_FLOATING =
+ "((" + IS_PINNED + " OR " + QUERY_IS_CACHED + ") "
+ + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
+ public static final String QUERY_IS_NOT_FLOATING =
+ "((" + QUERY_IS_NOT_PINNED + " " + QUERY_IS_NOT_CACHED + ") OR "
+ + QUERY_IS_DYNAMIC + " OR " + QUERY_IS_MANIFEST + ")";
+ public static final String QUERY_IS_VISIBLE_TO_PUBLISHER =
+ "(" + KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+ + " OR " + KEY_DISABLED_REASON + ":"
+ + ShortcutInfo.DISABLED_REASON_BY_APP
+ + " OR " + KEY_DISABLED_REASON + ":"
+ + ShortcutInfo.DISABLED_REASON_APP_CHANGED
+ + " OR " + KEY_DISABLED_REASON + ":"
+ + ShortcutInfo.DISABLED_REASON_UNKNOWN + ")";
+ public static final String QUERY_DISABLED_REASON_VERSION_LOWER =
+ KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
+ public static final String QUERY_IS_NON_MANIFEST_VISIBLE =
+ "(" + QUERY_IS_NOT_MANIFEST + " " + QUERY_IS_VISIBLE_TO_PUBLISHER + " ("
+ + QUERY_IS_PINNED + " OR " + QUERY_IS_CACHED + " OR " + QUERY_IS_DYNAMIC + "))";
+ public static final String QUERY_IS_VISIBLE_CACHED_OR_PINNED =
+ "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_DYNAMIC
+ + " (" + QUERY_IS_CACHED + " OR " + QUERY_IS_PINNED + "))";
+ public static final String QUERY_IS_VISIBLE_PINNED_ONLY =
+ "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_PINNED + " " + QUERY_IS_NOT_CACHED
+ + " " + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
+ public static final String QUERY_HAS_BITMAP_PATH = KEY_FLAGS + ":" + HAS_BITMAP_PATH;
+ public static final String QUERY_HAS_STRING_RESOURCE = KEY_FLAGS + ":" + HAS_STRING_RESOURCE;
+ public static final String QUERY_HAS_NON_ZERO_RANK = KEY_FLAGS + ":" + HAS_NON_ZERO_RANK;
+ public static final String QUERY_IS_FLOATING_AND_HAS_RANK =
+ "(" + QUERY_IS_FLOATING + " " + QUERY_HAS_NON_ZERO_RANK + ")";
+
+ public AppSearchShortcutInfo(@NonNull GenericDocument document) {
+ super(document);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static AppSearchShortcutInfo instance(@NonNull final ShortcutInfo shortcutInfo) {
+ Objects.requireNonNull(shortcutInfo);
+ return new Builder(shortcutInfo.getPackage(), shortcutInfo.getId())
+ .setActivity(shortcutInfo.getActivity())
+ .setShortLabel(shortcutInfo.getShortLabel())
+ .setShortLabelResId(shortcutInfo.getShortLabelResourceId())
+ .setShortLabelResName(shortcutInfo.getTitleResName())
+ .setLongLabel(shortcutInfo.getLongLabel())
+ .setLongLabelResId(shortcutInfo.getLongLabelResourceId())
+ .setLongLabelResName(shortcutInfo.getTextResName())
+ .setDisabledMessage(shortcutInfo.getDisabledMessage())
+ .setDisabledMessageResId(shortcutInfo.getDisabledMessageResourceId())
+ .setDisabledMessageResName(shortcutInfo.getDisabledMessageResName())
+ .setCategories(shortcutInfo.getCategories())
+ .setIntents(shortcutInfo.getIntents())
+ .setRank(shortcutInfo.getRank())
+ .setImplicitRank(shortcutInfo.getImplicitRank()
+ | (shortcutInfo.isRankChanged() ? ShortcutInfo.RANK_CHANGED_BIT : 0))
+ .setExtras(shortcutInfo.getExtras())
+ .setCreationTimestampMillis(shortcutInfo.getLastChangedTimestamp())
+ .setFlags(shortcutInfo.getFlags())
+ .setIconResId(shortcutInfo.getIconResourceId())
+ .setIconResName(shortcutInfo.getIconResName())
+ .setBitmapPath(shortcutInfo.getBitmapPath())
+ .setIconUri(shortcutInfo.getIconUri())
+ .setDisabledReason(shortcutInfo.getDisabledReason())
+ .setPersons(shortcutInfo.getPersons())
+ .setLocusId(shortcutInfo.getLocusId())
+ .build();
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public ShortcutInfo toShortcutInfo(@UserIdInt int userId) {
+ final String packageName = getNamespace();
+ final String activityString = getPropertyString(KEY_ACTIVITY);
+ final ComponentName activity = activityString == null
+ ? null : ComponentName.unflattenFromString(activityString);
+ // TODO: proper icon handling
+ // NOTE: bitmap based icons are currently saved in side-channel (see ShortcutBitmapSaver),
+ // re-creating Icon object at creation time implies turning this function into async since
+ // loading bitmap is I/O bound. Since ShortcutInfo#getIcon is already annotated with
+ // @hide and @UnsupportedAppUsage, we could migrate existing usage in platform with
+ // LauncherApps#getShortcutIconDrawable instead.
+ final Icon icon = null;
+ final String shortLabel = getPropertyString(KEY_SHORT_LABEL);
+ final int shortLabelResId = (int) getPropertyLong(KEY_SHORT_LABEL_RES_ID);
+ final String shortLabelResName = getPropertyString(KEY_SHORT_LABEL_RES_NAME);
+ final String longLabel = getPropertyString(KEY_LONG_LABEL);
+ final int longLabelResId = (int) getPropertyLong(KEY_LONG_LABEL_RES_ID);
+ final String longLabelResName = getPropertyString(KEY_LONG_LABEL_RES_NAME);
+ final String disabledMessage = getPropertyString(KEY_DISABLED_MESSAGE);
+ final int disabledMessageResId = (int) getPropertyLong(KEY_DISABLED_MESSAGE_RES_ID);
+ final String disabledMessageResName = getPropertyString(KEY_DISABLED_MESSAGE_RES_NAME);
+ final String[] categories = getPropertyStringArray(KEY_CATEGORIES);
+ final Set<String> categoriesSet = categories == null
+ ? null : new ArraySet<>(Arrays.asList(categories));
+ final String[] intentsStrings = getPropertyStringArray(KEY_INTENTS);
+ final Intent[] intents = intentsStrings == null
+ ? new Intent[0] : Arrays.stream(intentsStrings).map(uri -> {
+ if (TextUtils.isEmpty(uri)) {
+ return new Intent(Intent.ACTION_VIEW);
+ }
+ try {
+ return Intent.parseUri(uri, /* flags =*/ 0);
+ } catch (URISyntaxException e) {
+ // ignore malformed entry
+ }
+ return null;
+ }).toArray(Intent[]::new);
+ final byte[][] intentExtrasesBytes = getPropertyBytesArray(KEY_INTENT_PERSISTABLE_EXTRAS);
+ final Bundle[] intentExtrases = intentExtrasesBytes == null
+ ? null : Arrays.stream(intentExtrasesBytes)
+ .map(this::transformToBundle).toArray(Bundle[]::new);
+ if (intents != null) {
+ for (int i = 0; i < intents.length; i++) {
+ final Intent intent = intents[i];
+ if (intent == null || intentExtrases == null || intentExtrases.length <= i
+ || intentExtrases[i] == null || intentExtrases[i].size() == 0) {
+ continue;
+ }
+ intent.replaceExtras(intentExtrases[i]);
+ }
+ }
+ final Person[] persons = parsePerson(getPropertyDocumentArray(KEY_PERSON));
+ final String locusIdString = getPropertyString(KEY_LOCUS_ID);
+ final LocusId locusId = locusIdString == null ? null : new LocusId(locusIdString);
+ final int rank = Integer.parseInt(getPropertyString(KEY_RANK));
+ final int implicitRank = (int) getPropertyLong(KEY_IMPLICIT_RANK);
+ final byte[] extrasByte = getPropertyBytes(KEY_EXTRAS);
+ final PersistableBundle extras = transformToPersistableBundle(extrasByte);
+ final int flags = parseFlags(getPropertyStringArray(KEY_FLAGS));
+ final int iconResId = (int) getPropertyLong(KEY_ICON_RES_ID);
+ final String iconResName = getPropertyString(KEY_ICON_RES_NAME);
+ final String iconUri = getPropertyString(KEY_ICON_URI);
+ final String bitmapPath = getPropertyString(KEY_BITMAP_PATH);
+ final int disabledReason = Integer.parseInt(getPropertyString(KEY_DISABLED_REASON));
+ final ShortcutInfo si = new ShortcutInfo(
+ userId, getId(), packageName, activity, icon, shortLabel, shortLabelResId,
+ shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage,
+ disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras,
+ getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
+ disabledReason, persons, locusId, null);
+ si.setImplicitRank(implicitRank);
+ if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) {
+ si.setRankChanged();
+ }
+ return si;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static List<GenericDocument> toGenericDocuments(
+ @NonNull final Collection<ShortcutInfo> shortcuts) {
+ final List<GenericDocument> docs = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfo si : shortcuts) {
+ docs.add(AppSearchShortcutInfo.instance(si));
+ }
+ return docs;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static class Builder extends GenericDocument.Builder<Builder> {
+
+ private List<String> mFlags = new ArrayList<>(1);
+ private boolean mHasStringResource = false;
+
+ public Builder(String packageName, String id) {
+ super(/*namespace=*/ packageName, id, SCHEMA_TYPE);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setLocusId(@Nullable final LocusId locusId) {
+ if (locusId != null) {
+ setPropertyString(KEY_LOCUS_ID, locusId.getId());
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setActivity(@Nullable final ComponentName activity) {
+ if (activity != null) {
+ setPropertyString(KEY_ACTIVITY, activity.flattenToShortString());
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setShortLabel(@Nullable final CharSequence shortLabel) {
+ if (!TextUtils.isEmpty(shortLabel)) {
+ setPropertyString(KEY_SHORT_LABEL, Preconditions.checkStringNotEmpty(
+ shortLabel, "shortLabel cannot be empty").toString());
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setShortLabelResId(final int shortLabelResId) {
+ setPropertyLong(KEY_SHORT_LABEL_RES_ID, shortLabelResId);
+ if (shortLabelResId != 0) {
+ mHasStringResource = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setShortLabelResName(@Nullable final String shortLabelResName) {
+ if (!TextUtils.isEmpty(shortLabelResName)) {
+ setPropertyString(KEY_SHORT_LABEL_RES_NAME, shortLabelResName);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setLongLabel(@Nullable final CharSequence longLabel) {
+ if (!TextUtils.isEmpty(longLabel)) {
+ setPropertyString(KEY_LONG_LABEL, Preconditions.checkStringNotEmpty(
+ longLabel, "longLabel cannot be empty").toString());
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setLongLabelResId(final int longLabelResId) {
+ setPropertyLong(KEY_LONG_LABEL_RES_ID, longLabelResId);
+ if (longLabelResId != 0) {
+ mHasStringResource = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setLongLabelResName(@Nullable final String longLabelResName) {
+ if (!TextUtils.isEmpty(longLabelResName)) {
+ setPropertyString(KEY_LONG_LABEL_RES_NAME, longLabelResName);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setDisabledMessage(@Nullable final CharSequence disabledMessage) {
+ if (!TextUtils.isEmpty(disabledMessage)) {
+ setPropertyString(KEY_DISABLED_MESSAGE, Preconditions.checkStringNotEmpty(
+ disabledMessage, "disabledMessage cannot be empty").toString());
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setDisabledMessageResId(final int disabledMessageResId) {
+ setPropertyLong(KEY_DISABLED_MESSAGE_RES_ID, disabledMessageResId);
+ if (disabledMessageResId != 0) {
+ mHasStringResource = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setDisabledMessageResName(@Nullable final String disabledMessageResName) {
+ if (!TextUtils.isEmpty(disabledMessageResName)) {
+ setPropertyString(KEY_DISABLED_MESSAGE_RES_NAME, disabledMessageResName);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setCategories(@Nullable final Set<String> categories) {
+ if (categories != null && !categories.isEmpty()) {
+ setPropertyString(KEY_CATEGORIES, categories.stream().toArray(String[]::new));
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setIntent(@Nullable final Intent intent) {
+ if (intent == null) {
+ return this;
+ }
+ return setIntents(new Intent[]{intent});
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setIntents(@Nullable final Intent[] intents) {
+ if (intents == null || intents.length == 0) {
+ return this;
+ }
+ for (Intent intent : intents) {
+ Objects.requireNonNull(intent, "intents cannot contain null");
+ Objects.requireNonNull(intent.getAction(), "intent's action must be set");
+ }
+ final byte[][] intentExtrases = new byte[intents.length][];
+ for (int i = 0; i < intents.length; i++) {
+ final Intent intent = intents[i];
+ final Bundle extras = intent.getExtras();
+ intentExtrases[i] = extras == null
+ ? new byte[0] : transformToByteArray(new PersistableBundle(extras));
+ }
+ setPropertyString(KEY_INTENTS, Arrays.stream(intents).map(it -> it.toUri(0))
+ .toArray(String[]::new));
+ setPropertyBytes(KEY_INTENT_PERSISTABLE_EXTRAS, intentExtrases);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setPerson(@Nullable final Person person) {
+ if (person == null) {
+ return this;
+ }
+ return setPersons(new Person[]{person});
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setPersons(@Nullable final Person[] persons) {
+ if (persons == null || persons.length == 0) {
+ return this;
+ }
+ final GenericDocument[] documents = new GenericDocument[persons.length];
+ for (int i = 0; i < persons.length; i++) {
+ final Person person = persons[i];
+ if (person == null) continue;
+ final AppSearchPerson appSearchPerson = AppSearchPerson.instance(person);
+ documents[i] = appSearchPerson;
+ }
+ setPropertyDocument(KEY_PERSON, documents);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setRank(final int rank) {
+ Preconditions.checkArgument((0 <= rank), "Rank cannot be negative");
+ setPropertyString(KEY_RANK, String.valueOf(rank));
+ if (rank != 0) {
+ mFlags.add(HAS_NON_ZERO_RANK);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setImplicitRank(final int rank) {
+ setPropertyLong(KEY_IMPLICIT_RANK, rank);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setExtras(@Nullable final PersistableBundle extras) {
+ if (extras != null) {
+ setPropertyBytes(KEY_EXTRAS, transformToByteArray(extras));
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setFlags(@ShortcutInfo.ShortcutFlags final int flags) {
+ final String[] flagArray = flattenFlags(flags);
+ if (flagArray != null && flagArray.length > 0) {
+ mFlags.addAll(Arrays.asList(flagArray));
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Builder setIconResId(@Nullable final int iconResId) {
+ setPropertyLong(KEY_ICON_RES_ID, iconResId);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setIconResName(@Nullable final String iconResName) {
+ if (!TextUtils.isEmpty(iconResName)) {
+ setPropertyString(KEY_ICON_RES_NAME, iconResName);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setBitmapPath(@Nullable final String bitmapPath) {
+ if (!TextUtils.isEmpty(bitmapPath)) {
+ setPropertyString(KEY_BITMAP_PATH, bitmapPath);
+ mFlags.add(HAS_BITMAP_PATH);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setIconUri(@Nullable final String iconUri) {
+ if (!TextUtils.isEmpty(iconUri)) {
+ setPropertyString(KEY_ICON_URI, iconUri);
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setDisabledReason(@ShortcutInfo.DisabledReason final int disabledReason) {
+ setPropertyString(KEY_DISABLED_REASON, String.valueOf(disabledReason));
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ @Override
+ public AppSearchShortcutInfo build() {
+ if (mHasStringResource) {
+ mFlags.add(HAS_STRING_RESOURCE);
+ }
+ setPropertyString(KEY_FLAGS, mFlags.toArray(new String[0]));
+ return new AppSearchShortcutInfo(super.build());
+ }
+ }
+
+ /**
+ * Convert PersistableBundle into byte[] for persistence.
+ */
+ @Nullable
+ private static byte[] transformToByteArray(@NonNull final PersistableBundle extras) {
+ Objects.requireNonNull(extras);
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ new PersistableBundle(extras).writeToStream(baos);
+ return baos.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Convert byte[] into Bundle.
+ */
+ @Nullable
+ private Bundle transformToBundle(@Nullable final byte[] extras) {
+ if (extras == null) {
+ return null;
+ }
+ Objects.requireNonNull(extras);
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(extras)) {
+ final Bundle ret = new Bundle();
+ ret.putAll(PersistableBundle.readFromStream(bais));
+ return ret;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Convert byte[] into PersistableBundle.
+ */
+ @Nullable
+ private PersistableBundle transformToPersistableBundle(@Nullable final byte[] extras) {
+ if (extras == null) {
+ return null;
+ }
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(extras)) {
+ return PersistableBundle.readFromStream(bais);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private static String[] flattenFlags(@ShortcutInfo.ShortcutFlags final int flags) {
+ final List<String> flattenedFlags = new ArrayList<>();
+ for (int i = 0; i < 31; i++) {
+ final int mask = 1 << i;
+ final String value = flagToString(flags, mask);
+ if (value != null) {
+ flattenedFlags.add(value);
+ }
+ }
+ return flattenedFlags.toArray(new String[0]);
+ }
+
+ @Nullable
+ private static String flagToString(
+ @ShortcutInfo.ShortcutFlags final int flags, final int mask) {
+ switch (mask) {
+ case ShortcutInfo.FLAG_DYNAMIC:
+ return (flags & mask) != 0 ? IS_DYNAMIC : NOT_DYNAMIC;
+ case ShortcutInfo.FLAG_PINNED:
+ return (flags & mask) != 0 ? IS_PINNED : NOT_PINNED;
+ case ShortcutInfo.FLAG_HAS_ICON_RES:
+ return (flags & mask) != 0 ? HAS_ICON_RES : NO_ICON_RES;
+ case ShortcutInfo.FLAG_HAS_ICON_FILE:
+ return (flags & mask) != 0 ? HAS_ICON_FILE : NO_ICON_FILE;
+ case ShortcutInfo.FLAG_KEY_FIELDS_ONLY:
+ return (flags & mask) != 0 ? IS_KEY_FIELD_ONLY : NOT_KEY_FIELD_ONLY;
+ case ShortcutInfo.FLAG_MANIFEST:
+ return (flags & mask) != 0 ? IS_MANIFEST : NOT_MANIFEST;
+ case ShortcutInfo.FLAG_DISABLED:
+ return (flags & mask) != 0 ? IS_DISABLED : NOT_DISABLED;
+ case ShortcutInfo.FLAG_STRINGS_RESOLVED:
+ return (flags & mask) != 0 ? ARE_STRINGS_RESOLVED : NOT_STRINGS_RESOLVED;
+ case ShortcutInfo.FLAG_IMMUTABLE:
+ return (flags & mask) != 0 ? IS_IMMUTABLE : NOT_IMMUTABLE;
+ case ShortcutInfo.FLAG_ADAPTIVE_BITMAP:
+ return (flags & mask) != 0 ? HAS_ADAPTIVE_BITMAP : NO_ADAPTIVE_BITMAP;
+ case ShortcutInfo.FLAG_RETURNED_BY_SERVICE:
+ return (flags & mask) != 0 ? IS_RETURNED_BY_SERVICE : NOT_RETURNED_BY_SERVICE;
+ case ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE:
+ return (flags & mask) != 0 ? HAS_ICON_FILE_PENDING_SAVE : NO_ICON_FILE_PENDING_SAVE;
+ case ShortcutInfo.FLAG_SHADOW:
+ return (flags & mask) != 0 ? IS_SHADOW : NOT_SHADOW;
+ case ShortcutInfo.FLAG_LONG_LIVED:
+ return (flags & mask) != 0 ? IS_LONG_LIVED : NOT_LONG_LIVED;
+ case ShortcutInfo.FLAG_HAS_ICON_URI:
+ return (flags & mask) != 0 ? HAS_ICON_URI : NO_ICON_URI;
+ case ShortcutInfo.FLAG_CACHED_NOTIFICATIONS:
+ return (flags & mask) != 0 ? IS_CACHED_NOTIFICATION : NOT_CACHED_NOTIFICATION;
+ case ShortcutInfo.FLAG_CACHED_BUBBLES:
+ return (flags & mask) != 0 ? IS_CACHED_BUBBLE : NOT_CACHED_BUBBLE;
+ case ShortcutInfo.FLAG_CACHED_PEOPLE_TILE:
+ return (flags & mask) != 0 ? IS_CACHED_PEOPLE_TITLE : NOT_CACHED_PEOPLE_TITLE;
+ default:
+ return null;
+ }
+ }
+
+ private static int parseFlags(@Nullable final String[] flags) {
+ if (flags == null) {
+ return 0;
+ }
+ int ret = 0;
+ for (int i = 0; i < flags.length; i++) {
+ ret = ret | parseFlag(flags[i]);
+ }
+ return ret;
+ }
+
+ private static int parseFlag(final String value) {
+ switch (value) {
+ case IS_DYNAMIC:
+ return ShortcutInfo.FLAG_DYNAMIC;
+ case IS_PINNED:
+ return ShortcutInfo.FLAG_PINNED;
+ case HAS_ICON_RES:
+ return ShortcutInfo.FLAG_HAS_ICON_RES;
+ case HAS_ICON_FILE:
+ return ShortcutInfo.FLAG_HAS_ICON_FILE;
+ case IS_KEY_FIELD_ONLY:
+ return ShortcutInfo.FLAG_KEY_FIELDS_ONLY;
+ case IS_MANIFEST:
+ return ShortcutInfo.FLAG_MANIFEST;
+ case IS_DISABLED:
+ return ShortcutInfo.FLAG_DISABLED;
+ case ARE_STRINGS_RESOLVED:
+ return ShortcutInfo.FLAG_STRINGS_RESOLVED;
+ case IS_IMMUTABLE:
+ return ShortcutInfo.FLAG_IMMUTABLE;
+ case HAS_ADAPTIVE_BITMAP:
+ return ShortcutInfo.FLAG_ADAPTIVE_BITMAP;
+ case IS_RETURNED_BY_SERVICE:
+ return ShortcutInfo.FLAG_RETURNED_BY_SERVICE;
+ case HAS_ICON_FILE_PENDING_SAVE:
+ return ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE;
+ case IS_SHADOW:
+ return ShortcutInfo.FLAG_SHADOW;
+ case IS_LONG_LIVED:
+ return ShortcutInfo.FLAG_LONG_LIVED;
+ case HAS_ICON_URI:
+ return ShortcutInfo.FLAG_HAS_ICON_URI;
+ case IS_CACHED_NOTIFICATION:
+ return ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
+ case IS_CACHED_BUBBLE:
+ return ShortcutInfo.FLAG_CACHED_BUBBLES;
+ case IS_CACHED_PEOPLE_TITLE:
+ return ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
+ default:
+ return 0;
+ }
+ }
+
+ @NonNull
+ private static Person[] parsePerson(@Nullable final GenericDocument[] persons) {
+ if (persons == null) return new Person[0];
+ final Person[] ret = new Person[persons.length];
+ for (int i = 0; i < persons.length; i++) {
+ final GenericDocument document = persons[i];
+ if (document == null) continue;
+ final AppSearchPerson person = new AppSearchPerson(document);
+ ret[i] = person.toPerson();
+ }
+ return ret;
+ }
+}
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
new file mode 100644
index 0000000..2c4ff58
--- /dev/null
+++ b/android/content/pm/ApplicationInfo.java
@@ -0,0 +1,2560 @@
+/*
+ * Copyright (C) 2007 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.os.Build.VERSION_CODES.DONUT;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForBoolean;
+import com.android.server.SystemConfig;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Information you can retrieve about a particular application. This
+ * corresponds to information collected from the AndroidManifest.xml's
+ * <application> tag.
+ */
+public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+ private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+
+ /**
+ * Default task affinity of all activities in this application. See
+ * {@link ActivityInfo#taskAffinity} for more information. This comes
+ * from the "taskAffinity" attribute.
+ */
+ public String taskAffinity;
+
+ /**
+ * Optional name of a permission required to be able to access this
+ * application's components. From the "permission" attribute.
+ */
+ public String permission;
+
+ /**
+ * The name of the process this application should run in. From the
+ * "process" attribute or, if not set, the same as
+ * <var>packageName</var>.
+ */
+ public String processName;
+
+ /**
+ * Class implementing the Application object. From the "class"
+ * attribute.
+ */
+ public String className;
+
+ /**
+ * A style resource identifier (in the package's resources) of the
+ * description of an application. From the "description" attribute
+ * or, if not set, 0.
+ */
+ public int descriptionRes;
+
+ /**
+ * A style resource identifier (in the package's resources) of the
+ * default visual theme of the application. From the "theme" attribute
+ * or, if not set, 0.
+ */
+ public int theme;
+
+ /**
+ * Class implementing the Application's manage space
+ * functionality. From the "manageSpaceActivity"
+ * attribute. This is an optional attribute and will be null if
+ * applications don't specify it in their manifest
+ */
+ public String manageSpaceActivityName;
+
+ /**
+ * Class implementing the Application's backup functionality. From
+ * the "backupAgent" attribute. This is an optional attribute and
+ * will be null if the application does not specify it in its manifest.
+ *
+ * <p>If android:allowBackup is set to false, this attribute is ignored.
+ */
+ public String backupAgentName;
+
+ /**
+ * An optional attribute that indicates the app supports automatic backup of app data.
+ * <p>0 is the default and means the app's entire data folder + managed external storage will
+ * be backed up;
+ * Any negative value indicates the app does not support full-data backup, though it may still
+ * want to participate via the traditional key/value backup API;
+ * A positive number specifies an xml resource in which the application has defined its backup
+ * include/exclude criteria.
+ * <p>If android:allowBackup is set to false, this attribute is ignored.
+ *
+ * @see android.content.Context#getNoBackupFilesDir()
+ * @see #FLAG_ALLOW_BACKUP
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int fullBackupContent = 0;
+
+ /**
+ * Applications can set this attribute to an xml resource within their app where they specified
+ * the rules determining which files and directories can be copied from the device as part of
+ * backup or transfer operations.
+ *<p>
+ * Set from the {@link android.R.styleable#AndroidManifestApplication_dataExtractionRules}
+ * attribute in the manifest.
+ *
+ * @hide
+ */
+ public int dataExtractionRulesRes = 0;
+
+ /**
+ * <code>true</code> if the package is capable of presenting a unified interface representing
+ * multiple profiles.
+ * @hide
+ */
+ public boolean crossProfile;
+
+ /**
+ * The default extra UI options for activities in this application.
+ * Set from the {@link android.R.attr#uiOptions} attribute in the
+ * activity's manifest.
+ */
+ public int uiOptions = 0;
+
+ /**
+ * Value for {@link #flags}: if set, this application is installed in the device's system image.
+ * This should not be used to make security decisions. Instead, rely on
+ * {@linkplain android.content.pm.PackageManager#checkSignatures(java.lang.String,java.lang.String)
+ * signature checks} or
+ * <a href="https://developer.android.com/training/articles/security-tips#Permissions">permissions</a>.
+ *
+ * <p><b>Warning:</b> Note that does flag not behave the same as
+ * {@link android.R.attr#protectionLevel android:protectionLevel} {@code system} or
+ * {@code signatureOrSystem}.
+ */
+ public static final int FLAG_SYSTEM = 1<<0;
+
+ /**
+ * Value for {@link #flags}: set to true if this application would like to
+ * allow debugging of its
+ * code, even when installed on a non-development system. Comes
+ * from {@link android.R.styleable#AndroidManifestApplication_debuggable
+ * android:debuggable} of the <application> tag.
+ */
+ public static final int FLAG_DEBUGGABLE = 1<<1;
+
+ /**
+ * Value for {@link #flags}: set to true if this application has code
+ * associated with it. Comes
+ * from {@link android.R.styleable#AndroidManifestApplication_hasCode
+ * android:hasCode} of the <application> tag.
+ */
+ public static final int FLAG_HAS_CODE = 1<<2;
+
+ /**
+ * Value for {@link #flags}: set to true if this application is persistent.
+ * Comes from {@link android.R.styleable#AndroidManifestApplication_persistent
+ * android:persistent} of the <application> tag.
+ */
+ public static final int FLAG_PERSISTENT = 1<<3;
+
+ /**
+ * Value for {@link #flags}: set to true if this application holds the
+ * {@link android.Manifest.permission#FACTORY_TEST} permission and the
+ * device is running in factory test mode.
+ */
+ public static final int FLAG_FACTORY_TEST = 1<<4;
+
+ /**
+ * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+ * Comes from {@link android.R.styleable#AndroidManifestApplication_allowTaskReparenting
+ * android:allowTaskReparenting} of the <application> tag.
+ */
+ public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5;
+
+ /**
+ * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+ * Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData
+ * android:allowClearUserData} of the <application> tag.
+ */
+ public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6;
+
+ /**
+ * Value for {@link #flags}: this is set if this application has been
+ * installed as an update to a built-in system application.
+ */
+ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7;
+
+ /**
+ * Value for {@link #flags}: this is set if the application has specified
+ * {@link android.R.styleable#AndroidManifestApplication_testOnly
+ * android:testOnly} to be true.
+ */
+ public static final int FLAG_TEST_ONLY = 1<<8;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * reduced in size for smaller screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_smallScreens
+ * android:smallScreens}.
+ */
+ public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * displayed on normal screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens
+ * android:normalScreens}.
+ */
+ public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * increased in size for larger screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens
+ * android:largeScreens}.
+ */
+ public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11;
+
+ /**
+ * Value for {@link #flags}: true when the application knows how to adjust
+ * its UI for different screen sizes. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_resizeable
+ * android:resizeable}.
+ */
+ public static final int FLAG_RESIZEABLE_FOR_SCREENS = 1<<12;
+
+ /**
+ * Value for {@link #flags}: true when the application knows how to
+ * accommodate different screen densities. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_anyDensity
+ * android:anyDensity}.
+ *
+ * @deprecated Set by default when targeting API 4 or higher and apps
+ * should not set this to false.
+ */
+ @Deprecated
+ public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 1<<13;
+
+ /**
+ * Value for {@link #flags}: set to true if this application would like to
+ * request the VM to operate under the safe mode. Comes from
+ * {@link android.R.styleable#AndroidManifestApplication_vmSafeMode
+ * android:vmSafeMode} of the <application> tag.
+ */
+ public static final int FLAG_VM_SAFE_MODE = 1<<14;
+
+ /**
+ * Value for {@link #flags}: set to <code>false</code> if the application does not wish
+ * to permit any OS-driven backups of its data; <code>true</code> otherwise.
+ *
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
+ * attribute of the <application> tag.
+ */
+ public static final int FLAG_ALLOW_BACKUP = 1<<15;
+
+ /**
+ * Value for {@link #flags}: set to <code>false</code> if the application must be kept
+ * in memory following a full-system restore operation; <code>true</code> otherwise.
+ * Ordinarily, during a full system restore operation each application is shut down
+ * following execution of its agent's onRestore() method. Setting this attribute to
+ * <code>false</code> prevents this. Most applications will not need to set this attribute.
+ *
+ * <p>If
+ * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
+ * is set to <code>false</code> or no
+ * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent}
+ * is specified, this flag will be ignored.
+ *
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_killAfterRestore android:killAfterRestore}
+ * attribute of the <application> tag.
+ */
+ public static final int FLAG_KILL_AFTER_RESTORE = 1<<16;
+
+ /**
+ * Value for {@link #flags}: Set to <code>true</code> if the application's backup
+ * agent claims to be able to handle restore data even "from the future,"
+ * i.e. from versions of the application with a versionCode greater than
+ * the one currently installed on the device. <i>Use with caution!</i> By default
+ * this attribute is <code>false</code> and the Backup Manager will ensure that data
+ * from "future" versions of the application are never supplied during a restore operation.
+ *
+ * <p>If
+ * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
+ * is set to <code>false</code> or no
+ * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent}
+ * is specified, this flag will be ignored.
+ *
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_restoreAnyVersion android:restoreAnyVersion}
+ * attribute of the <application> tag.
+ */
+ public static final int FLAG_RESTORE_ANY_VERSION = 1<<17;
+
+ /**
+ * Value for {@link #flags}: Set to true if the application is
+ * currently installed on external/removable/unprotected storage. Such
+ * applications may not be available if their storage is not currently
+ * mounted. When the storage it is on is not available, it will look like
+ * the application has been uninstalled (its .apk is no longer available)
+ * but its persistent data is not removed.
+ */
+ public static final int FLAG_EXTERNAL_STORAGE = 1<<18;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * increased in size for extra large screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens
+ * android:xlargeScreens}.
+ */
+ public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19;
+
+ /**
+ * Value for {@link #flags}: true when the application has requested a
+ * large heap for its processes. Corresponds to
+ * {@link android.R.styleable#AndroidManifestApplication_largeHeap
+ * android:largeHeap}.
+ */
+ public static final int FLAG_LARGE_HEAP = 1<<20;
+
+ /**
+ * Value for {@link #flags}: true if this application's package is in
+ * the stopped state.
+ */
+ public static final int FLAG_STOPPED = 1<<21;
+
+ /**
+ * Value for {@link #flags}: true when the application is willing to support
+ * RTL (right to left). All activities will inherit this value.
+ *
+ * Set from the {@link android.R.attr#supportsRtl} attribute in the
+ * activity's manifest.
+ *
+ * Default value is false (no support for RTL).
+ */
+ public static final int FLAG_SUPPORTS_RTL = 1<<22;
+
+ /**
+ * Value for {@link #flags}: true if the application is currently
+ * installed for the calling user.
+ */
+ public static final int FLAG_INSTALLED = 1<<23;
+
+ /**
+ * Value for {@link #flags}: true if the application only has its
+ * data installed; the application package itself does not currently
+ * exist on the device.
+ */
+ public static final int FLAG_IS_DATA_ONLY = 1<<24;
+
+ /**
+ * Value for {@link #flags}: true if the application was declared to be a
+ * game, or false if it is a non-game application.
+ *
+ * @deprecated use {@link #CATEGORY_GAME} instead.
+ */
+ @Deprecated
+ public static final int FLAG_IS_GAME = 1<<25;
+
+ /**
+ * Value for {@link #flags}: {@code true} if the application asks that only
+ * full-data streaming backups of its data be performed even though it defines
+ * a {@link android.app.backup.BackupAgent BackupAgent}, which normally
+ * indicates that the app will manage its backed-up data via incremental
+ * key/value updates.
+ */
+ public static final int FLAG_FULL_BACKUP_ONLY = 1<<26;
+
+ /**
+ * Value for {@link #flags}: {@code true} if the application may use cleartext network traffic
+ * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP
+ * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use
+ * cleartext network traffic, in which case platform components (e.g., HTTP stacks,
+ * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
+ * traffic. Third-party libraries are encouraged to honor this flag as well.
+ *
+ * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up.
+ *
+ * <p>This flag is ignored on Android N and above if an Android Network Security Config is
+ * present.
+ *
+ * <p>This flag comes from
+ * {@link android.R.styleable#AndroidManifestApplication_usesCleartextTraffic
+ * android:usesCleartextTraffic} of the <application> tag.
+ */
+ public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27;
+
+ /**
+ * When set installer extracts native libs from .apk files.
+ */
+ public static final int FLAG_EXTRACT_NATIVE_LIBS = 1<<28;
+
+ /**
+ * Value for {@link #flags}: {@code true} when the application's rendering
+ * should be hardware accelerated.
+ */
+ public static final int FLAG_HARDWARE_ACCELERATED = 1<<29;
+
+ /**
+ * Value for {@link #flags}: true if this application's package is in
+ * the suspended state.
+ */
+ public static final int FLAG_SUSPENDED = 1<<30;
+
+ /**
+ * Value for {@link #flags}: true if code from this application will need to be
+ * loaded into other applications' processes. On devices that support multiple
+ * instruction sets, this implies the code might be loaded into a process that's
+ * using any of the devices supported instruction sets.
+ *
+ * <p> The system might treat such applications specially, for eg., by
+ * extracting the application's native libraries for all supported instruction
+ * sets or by compiling the application's dex code for all supported instruction
+ * sets.
+ */
+ public static final int FLAG_MULTIARCH = 1 << 31;
+
+ /**
+ * Flags associated with the application. Any combination of
+ * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
+ * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
+ * {@link #FLAG_ALLOW_TASK_REPARENTING}
+ * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP},
+ * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
+ * {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
+ * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS},
+ * {@link #FLAG_RESIZEABLE_FOR_SCREENS},
+ * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE},
+ * {@link #FLAG_ALLOW_BACKUP}, {@link #FLAG_KILL_AFTER_RESTORE},
+ * {@link #FLAG_RESTORE_ANY_VERSION}, {@link #FLAG_EXTERNAL_STORAGE},
+ * {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED},
+ * {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED},
+ * {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME},
+ * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC},
+ * {@link #FLAG_MULTIARCH}.
+ */
+ public int flags = 0;
+
+ /**
+ * Value for {@link #privateFlags}: true if the application is hidden via restrictions and for
+ * most purposes is considered as not installed.
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_HIDDEN = 1<<0;
+
+ /**
+ * Value for {@link #privateFlags}: set to <code>true</code> if the application
+ * has reported that it is heavy-weight, and thus can not participate in
+ * the normal application lifecycle.
+ *
+ * <p>Comes from the
+ * android.R.styleable#AndroidManifestApplication_cantSaveState
+ * attribute of the <application> tag.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_CANT_SAVE_STATE = 1<<1;
+
+ /**
+ * Value for {@link #privateFlags}: set to {@code true} if the application
+ * is permitted to hold privileged permissions.
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3;
+
+ /**
+ * Value for {@link #privateFlags}: {@code true} if the application has any IntentFiler
+ * with some data URI using HTTP or HTTPS with an associated VIEW action.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_HAS_DOMAIN_URLS = 1<<4;
+
+ /**
+ * When set, the default data storage directory for this app is pointed at
+ * the device-protected location.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 5;
+
+ /**
+ * When set, assume that all components under the given app are direct boot
+ * aware, unless otherwise specified.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_DIRECT_BOOT_AWARE = 1 << 6;
+
+ /**
+ * Value for {@link #privateFlags}: {@code true} if the application is installed
+ * as instant app.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_INSTANT = 1 << 7;
+
+ /**
+ * When set, at least one component inside this application is direct boot
+ * aware.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE = 1 << 8;
+
+
+ /**
+ * When set, signals that the application is required for the system user and should not be
+ * uninstalled.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER = 1 << 9;
+
+ /**
+ * When set, the application explicitly requested that its activities be resizeable by default.
+ * @see android.R.styleable#AndroidManifestActivity_resizeableActivity
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE = 1 << 10;
+
+ /**
+ * When set, the application explicitly requested that its activities *not* be resizeable by
+ * default.
+ * @see android.R.styleable#AndroidManifestActivity_resizeableActivity
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE = 1 << 11;
+
+ /**
+ * The application isn't requesting explicitly requesting for its activities to be resizeable or
+ * non-resizeable by default. So, we are making it activities resizeable by default based on the
+ * target SDK version of the app.
+ * @see android.R.styleable#AndroidManifestActivity_resizeableActivity
+ *
+ * NOTE: This only affects apps with target SDK >= N where the resizeableActivity attribute was
+ * introduced. It shouldn't be confused with {@link ActivityInfo#RESIZE_MODE_FORCE_RESIZEABLE}
+ * where certain pre-N apps are forced to the resizeable.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION =
+ 1 << 12;
+
+ /**
+ * Value for {@link #privateFlags}: {@code true} means the OS should go ahead and
+ * run full-data backup operations for the app even when it is in a
+ * foreground-equivalent run state. Defaults to {@code false} if unspecified.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_BACKUP_IN_FOREGROUND = 1 << 13;
+
+ /**
+ * Value for {@link #privateFlags}: {@code true} means this application
+ * contains a static shared library. Defaults to {@code false} if unspecified.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_STATIC_SHARED_LIBRARY = 1 << 14;
+
+ /**
+ * Value for {@link #privateFlags}: When set, the application will only have its splits loaded
+ * if they are required to load a component. Splits can be loaded on demand using the
+ * {@link Context#createContextForSplit(String)} API.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ISOLATED_SPLIT_LOADING = 1 << 15;
+
+ /**
+ * Value for {@link #privateFlags}: When set, the application was installed as
+ * a virtual preload.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_VIRTUAL_PRELOAD = 1 << 16;
+
+ /**
+ * Value for {@link #privateFlags}: whether this app is pre-installed on the
+ * OEM partition of the system image.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_OEM = 1 << 17;
+
+ /**
+ * Value for {@link #privateFlags}: whether this app is pre-installed on the
+ * vendor partition of the system image.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_VENDOR = 1 << 18;
+
+ /**
+ * Value for {@link #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;
+
+ /**
+ * Value for {@link #privateFlags}: whether this app is signed with the
+ * platform key.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY = 1 << 20;
+
+ /**
+ * Value for {@link #privateFlags}: whether this app is pre-installed on the
+ * system_ext partition of the system image.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_SYSTEM_EXT = 1 << 21;
+
+ /**
+ * Indicates whether this package requires access to non-SDK APIs.
+ * Only system apps and tests are allowed to use this property.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_USES_NON_SDK_API = 1 << 22;
+
+ /**
+ * Indicates whether this application can be profiled by the shell user,
+ * even when running on a device that is running in user mode.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_PROFILEABLE_BY_SHELL = 1 << 23;
+
+ /**
+ * Indicates whether this package requires access to non-SDK APIs.
+ * Only system apps and tests are allowed to use this property.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_HAS_FRAGILE_USER_DATA = 1 << 24;
+
+ /**
+ * Indicates whether this application wants to use the embedded dex in the APK, rather than
+ * extracted or locally compiled variants. This keeps the dex code protected by the APK
+ * signature. Such apps will always run in JIT mode (same when they are first installed), and
+ * the system will never generate ahead-of-time compiled code for them. Depending on the app's
+ * workload, there may be some run time performance change, noteably the cold start time.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_USE_EMBEDDED_DEX = 1 << 25;
+
+ /**
+ * Value for {@link #privateFlags}: indicates whether this application's data will be cleared
+ * on a failed restore.
+ *
+ * <p>Comes from the
+ * android.R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore attribute
+ * of the <application> tag.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE = 1 << 26;
+
+ /**
+ * Value for {@link #privateFlags}: true if the application allows its audio playback
+ * to be captured by other apps.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE = 1 << 27;
+
+ /**
+ * Indicates whether this package is in fact a runtime resource overlay.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_IS_RESOURCE_OVERLAY = 1 << 28;
+
+ /**
+ * Value for {@link #privateFlags}: If {@code true} this app requests
+ * full external storage access. The request may not be honored due to
+ * policy or other reasons.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE = 1 << 29;
+
+ /**
+ * Value for {@link #privateFlags}: whether this app is pre-installed on the
+ * ODM partition of the system image.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ODM = 1 << 30;
+
+ /**
+ * Value for {@link #privateFlags}: If {@code true} this app allows heap tagging.
+ * {@link com.android.server.am.ProcessList#NATIVE_HEAP_POINTER_TAGGING}
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING = 1 << 31;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE,
+ PRIVATE_FLAG_BACKUP_IN_FOREGROUND,
+ PRIVATE_FLAG_CANT_SAVE_STATE,
+ PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
+ PRIVATE_FLAG_DIRECT_BOOT_AWARE,
+ PRIVATE_FLAG_HAS_DOMAIN_URLS,
+ PRIVATE_FLAG_HIDDEN,
+ PRIVATE_FLAG_INSTANT,
+ PRIVATE_FLAG_IS_RESOURCE_OVERLAY,
+ PRIVATE_FLAG_ISOLATED_SPLIT_LOADING,
+ PRIVATE_FLAG_OEM,
+ PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE,
+ PRIVATE_FLAG_USE_EMBEDDED_DEX,
+ PRIVATE_FLAG_PRIVILEGED,
+ PRIVATE_FLAG_PRODUCT,
+ PRIVATE_FLAG_SYSTEM_EXT,
+ PRIVATE_FLAG_PROFILEABLE_BY_SHELL,
+ PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER,
+ PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY,
+ PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
+ PRIVATE_FLAG_VENDOR,
+ PRIVATE_FLAG_VIRTUAL_PRELOAD,
+ PRIVATE_FLAG_HAS_FRAGILE_USER_DATA,
+ PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE,
+ PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE,
+ PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE,
+ PRIVATE_FLAG_ODM,
+ PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationInfoPrivateFlags {}
+
+ /**
+ * Value for {@link #privateFlagsExt}: whether this application can be profiled, either by the
+ * shell user or the system.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_PROFILEABLE = 1 << 0;
+
+ /**
+ * Value for {@link #privateFlagsExt}: whether this application has requested
+ * exemption from the foreground service restriction introduced in S
+ * (https://developer.android.com/about/versions/12/foreground-services).
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION = 1 << 1;
+
+ /**
+ * Value for {@link #privateFlagsExt}: whether attributions provided by the application are
+ * meant to be user-visible.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE = 1 << 2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
+ PRIVATE_FLAG_EXT_PROFILEABLE,
+ PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION,
+ PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationInfoPrivateFlagsExt {}
+ /**
+ * Constant corresponding to <code>allowed</code> in the
+ * {@link android.R.attr#autoRevokePermissions} attribute.
+ *
+ * @hide
+ */
+ public static final int AUTO_REVOKE_ALLOWED = 0;
+
+ /**
+ * Constant corresponding to <code>discouraged</code> in the
+ * {@link android.R.attr#autoRevokePermissions} attribute.
+ *
+ * @hide
+ */
+ public static final int AUTO_REVOKE_DISCOURAGED = 1;
+
+ /**
+ * Constant corresponding to <code>disallowed</code> in the
+ * {@link android.R.attr#autoRevokePermissions} attribute.
+ *
+ * @hide
+ */
+ public static final int AUTO_REVOKE_DISALLOWED = 2;
+
+ /**
+ * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public @ApplicationInfoPrivateFlags int privateFlags;
+
+ /**
+ * More private/hidden flags. See {@code PRIVATE_FLAG_EXT_...} constants.
+ * @hide
+ */
+ public @ApplicationInfoPrivateFlagsExt int privateFlagsExt;
+
+ /**
+ * @hide
+ */
+ public static final String METADATA_PRELOADED_FONTS = "preloaded_fonts";
+
+ /**
+ * The required smallest screen width the application can run on. If 0,
+ * nothing has been specified. Comes from
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
+ * android:requiresSmallestWidthDp} attribute of the <supports-screens> tag.
+ */
+ public int requiresSmallestWidthDp = 0;
+
+ /**
+ * The maximum smallest screen width the application is designed for. If 0,
+ * nothing has been specified. Comes from
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
+ * android:compatibleWidthLimitDp} attribute of the <supports-screens> tag.
+ */
+ public int compatibleWidthLimitDp = 0;
+
+ /**
+ * The maximum smallest screen width the application will work on. If 0,
+ * nothing has been specified. Comes from
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
+ * android:largestWidthLimitDp} attribute of the <supports-screens> tag.
+ */
+ public int largestWidthLimitDp = 0;
+
+ /**
+ * Value indicating the maximum aspect ratio the application supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#maxAspectRatio}.
+ * @hide
+ */
+ public float maxAspectRatio;
+
+ /**
+ * Value indicating the minimum aspect ratio the application supports.
+ * <p>
+ * 0 means unset.
+ * @see {@link android.R.attr#minAspectRatio}.
+ * @hide
+ */
+ public float minAspectRatio;
+
+ /** @hide */
+ public String volumeUuid;
+
+ /**
+ * UUID of the storage volume on which this application is being hosted. For
+ * apps hosted on the default internal storage at
+ * {@link Environment#getDataDirectory()}, the UUID value is
+ * {@link StorageManager#UUID_DEFAULT}.
+ */
+ public UUID storageUuid;
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String scanSourceDir;
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String scanPublicSourceDir;
+
+ /**
+ * Full path to the base APK for this application.
+ */
+ public String sourceDir;
+
+ /**
+ * Full path to the publicly available parts of {@link #sourceDir},
+ * including resources and manifest. This may be different from
+ * {@link #sourceDir} if an application is forward locked.
+ */
+ public String publicSourceDir;
+
+ /**
+ * The names of all installed split APKs, ordered lexicographically.
+ */
+ public String[] splitNames;
+
+ /**
+ * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
+ */
+ public String[] splitSourceDirs;
+
+ /**
+ * Full path to the publicly available parts of {@link #splitSourceDirs},
+ * including resources and manifest. This may be different from
+ * {@link #splitSourceDirs} if an application is forward locked.
+ *
+ * @see #splitSourceDirs
+ */
+ public String[] splitPublicSourceDirs;
+
+ /**
+ * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+ *
+ * Available since platform version O.
+ *
+ * Only populated if the application opts in to isolated split loading via the
+ * {@link android.R.attr.isolatedSplits} attribute in the <manifest> tag of the app's
+ * AndroidManifest.xml.
+ *
+ * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
+ * and {@link #splitPublicSourceDirs} arrays.
+ * Each key represents a split and its value is an array of splits. The first element of this
+ * array is the parent split, and the rest are configuration splits. These configuration splits
+ * have no dependencies themselves.
+ * Cycles do not exist because they are illegal and screened for during installation.
+ *
+ * May be null if no splits are installed, or if no dependencies exist between them.
+ *
+ * NOTE: Any change to the way split dependencies are stored must update the logic that
+ * creates the class loader context for dexopt (DexoptUtils#getClassLoaderContexts).
+ *
+ * @hide
+ */
+ public SparseArray<int[]> splitDependencies;
+
+ /**
+ * Full paths to the locations of extra resource packages (runtime overlays)
+ * this application uses. This field is only used if there are extra resource
+ * packages, otherwise it is null.
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public String[] resourceDirs;
+
+ /**
+ * Contains the contents of {@link #resourceDirs} and along with paths for overlays that may or
+ * may not be APK packages.
+ *
+ * {@hide}
+ */
+ public String[] overlayPaths;
+
+ /**
+ * 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;
+
+ /**
+ * The seinfo tag generated per-user. This value may change based upon the
+ * user's configuration. For example, when an instant app is installed for
+ * a user. It is an error if this field is ever {@code null} when trying to
+ * start a new process.
+ * <p>NOTE: We need to separate this out because we modify per-user values
+ * multiple times. This needs to be refactored since we're performing more
+ * work than necessary and these values should only be set once. When that
+ * happens, we can merge the per-user value with the seInfo state above.
+ *
+ * {@hide}
+ */
+ public String seInfoUser;
+
+ /**
+ * Paths to all shared libraries this application is linked against. This
+ * field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
+ * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
+ * the structure.
+ */
+ public String[] sharedLibraryFiles;
+
+ /**
+ * List of all shared libraries this application is linked against. This
+ * field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
+ * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
+ * the structure.
+ *
+ * {@hide}
+ */
+ public List<SharedLibraryInfo> sharedLibraryInfos;
+
+ /**
+ * Full path to the default directory assigned to the package for its
+ * persistent data.
+ */
+ public String dataDir;
+
+ /**
+ * Full path to the device-protected directory assigned to the package for
+ * its persistent data.
+ *
+ * @see Context#createDeviceProtectedStorageContext()
+ */
+ public String deviceProtectedDataDir;
+
+ /**
+ * Full path to the credential-protected directory assigned to the package
+ * for its persistent data.
+ *
+ * @hide
+ */
+ @SystemApi
+ public String credentialProtectedDataDir;
+
+ /**
+ * Full path to the directory where native JNI libraries are stored.
+ */
+ public String nativeLibraryDir;
+
+ /**
+ * Full path where unpacked native libraries for {@link #secondaryCpuAbi}
+ * are stored, if present.
+ *
+ * The main reason this exists is for bundled multi-arch apps, where
+ * it's not trivial to calculate the location of libs for the secondary abi
+ * given the location of the primary.
+ *
+ * TODO: Change the layout of bundled installs so that we can use
+ * nativeLibraryRootDir & nativeLibraryRootRequiresIsa there as well.
+ * (e.g {@code [ "/system/app-lib/Foo/arm", "/system/app-lib/Foo/arm64" ]}
+ * instead of {@code [ "/system/lib/Foo", "/system/lib64/Foo" ]}.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String secondaryNativeLibraryDir;
+
+ /**
+ * The root path where unpacked native libraries are stored.
+ * <p>
+ * When {@link #nativeLibraryRootRequiresIsa} is set, the libraries are
+ * placed in ISA-specific subdirectories under this path, otherwise the
+ * libraries are placed directly at this path.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String nativeLibraryRootDir;
+
+ /**
+ * Flag indicating that ISA must be appended to
+ * {@link #nativeLibraryRootDir} to be useful.
+ *
+ * @hide
+ */
+ public boolean nativeLibraryRootRequiresIsa;
+
+ /**
+ * The primary ABI that this application requires, This is inferred from the ABIs
+ * of the native JNI libraries the application bundles. Will be {@code null}
+ * if this application does not require any particular ABI.
+ *
+ * If non-null, the application will always be launched with this ABI.
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public String primaryCpuAbi;
+
+ /**
+ * The secondary ABI for this application. Might be non-null for multi-arch
+ * installs. The application itself never uses this ABI, but other applications that
+ * use its code might.
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public String secondaryCpuAbi;
+
+ /**
+ * The kernel user-ID that has been assigned to this application;
+ * currently this is not a unique ID (multiple applications can have
+ * the same uid).
+ */
+ public int uid;
+
+ /**
+ * The minimum SDK version this application can run on. It will not run
+ * on earlier versions.
+ */
+ public int minSdkVersion;
+
+ /**
+ * The minimum SDK version this application targets. It may run on earlier
+ * versions, but it knows how to work with any new behavior added at this
+ * version. Will be {@link android.os.Build.VERSION_CODES#CUR_DEVELOPMENT}
+ * if this is a development build and the app is targeting that. You should
+ * compare that this number is >= the SDK version number at which your
+ * behavior was introduced.
+ */
+ public int targetSdkVersion;
+
+ /**
+ * The app's declared version code.
+ * @hide
+ */
+ public long longVersionCode;
+
+ /**
+ * An integer representation of the app's declared version code. This is being left in place as
+ * some apps were using reflection to access it before the move to long in
+ * {@link android.os.Build.VERSION_CODES#P}
+ * @deprecated Use {@link #longVersionCode} instead.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public int versionCode;
+
+ /**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "S", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
+ /**
+ * When false, indicates that all components within this application are
+ * considered disabled, regardless of their individually set enabled status.
+ */
+ public boolean enabled = true;
+
+ /**
+ * For convenient access to the current enabled setting of this app.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+ /**
+ * For convenient access to package's install location.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+
+ /**
+ * Resource file providing the application's Network Security Config.
+ * @hide
+ */
+ public int networkSecurityConfigRes;
+
+ /**
+ * Version of the sandbox the application wants to run in.
+ * @hide
+ */
+ @SystemApi
+ public int targetSandboxVersion;
+
+ /**
+ * The factory of this package, as specified by the <manifest>
+ * tag's {@link android.R.styleable#AndroidManifestApplication_appComponentFactory}
+ * attribute.
+ */
+ public String appComponentFactory;
+
+ /**
+ * Resource id of {@link com.android.internal.R.styleable.AndroidManifestProvider_icon}
+ * @hide
+ */
+ public int iconRes;
+
+ /**
+ * Resource id of {@link com.android.internal.R.styleable.AndroidManifestProvider_roundIcon}
+ * @hide
+ */
+ public int roundIconRes;
+
+ /**
+ * The category of this app. Categories are used to cluster multiple apps
+ * together into meaningful groups, such as when summarizing battery,
+ * network, or disk usage. Apps should only define this value when they fit
+ * well into one of the specific categories.
+ * <p>
+ * Set from the {@link android.R.attr#appCategory} attribute in the
+ * manifest. If the manifest doesn't define a category, this value may have
+ * been provided by the installer via
+ * {@link PackageManager#setApplicationCategoryHint(String, int)}.
+ */
+ public @Category int category = CATEGORY_UNDEFINED;
+
+ /** {@hide} */
+ @IntDef(prefix = { "CATEGORY_" }, value = {
+ CATEGORY_UNDEFINED,
+ CATEGORY_GAME,
+ CATEGORY_AUDIO,
+ CATEGORY_VIDEO,
+ CATEGORY_IMAGE,
+ CATEGORY_SOCIAL,
+ CATEGORY_NEWS,
+ CATEGORY_MAPS,
+ CATEGORY_PRODUCTIVITY,
+ CATEGORY_ACCESSIBILITY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Category {
+ }
+
+ /**
+ * Value when category is undefined.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_UNDEFINED = -1;
+
+ /**
+ * Category for apps which are primarily games.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_GAME = 0;
+
+ /**
+ * Category for apps which primarily work with audio or music, such as music
+ * players.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_AUDIO = 1;
+
+ /**
+ * Category for apps which primarily work with video or movies, such as
+ * streaming video apps.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_VIDEO = 2;
+
+ /**
+ * Category for apps which primarily work with images or photos, such as
+ * camera or gallery apps.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_IMAGE = 3;
+
+ /**
+ * Category for apps which are primarily social apps, such as messaging,
+ * communication, email, or social network apps.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_SOCIAL = 4;
+
+ /**
+ * Category for apps which are primarily news apps, such as newspapers,
+ * magazines, or sports apps.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_NEWS = 5;
+
+ /**
+ * Category for apps which are primarily maps apps, such as navigation apps.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_MAPS = 6;
+
+ /**
+ * Category for apps which are primarily productivity apps, such as cloud
+ * storage or workplace apps.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_PRODUCTIVITY = 7;
+
+ /**
+ * Category for apps which are primarily accessibility apps, such as screen-readers.
+ *
+ * @see #category
+ */
+ public static final int CATEGORY_ACCESSIBILITY = 8;
+
+ /**
+ * Return a concise, localized title for the given
+ * {@link ApplicationInfo#category} value, or {@code null} for unknown
+ * values such as {@link #CATEGORY_UNDEFINED}.
+ *
+ * @see #category
+ */
+ public static CharSequence getCategoryTitle(Context context, @Category int category) {
+ switch (category) {
+ case ApplicationInfo.CATEGORY_GAME:
+ return context.getText(com.android.internal.R.string.app_category_game);
+ case ApplicationInfo.CATEGORY_AUDIO:
+ return context.getText(com.android.internal.R.string.app_category_audio);
+ case ApplicationInfo.CATEGORY_VIDEO:
+ return context.getText(com.android.internal.R.string.app_category_video);
+ case ApplicationInfo.CATEGORY_IMAGE:
+ return context.getText(com.android.internal.R.string.app_category_image);
+ case ApplicationInfo.CATEGORY_SOCIAL:
+ return context.getText(com.android.internal.R.string.app_category_social);
+ case ApplicationInfo.CATEGORY_NEWS:
+ return context.getText(com.android.internal.R.string.app_category_news);
+ case ApplicationInfo.CATEGORY_MAPS:
+ return context.getText(com.android.internal.R.string.app_category_maps);
+ case ApplicationInfo.CATEGORY_PRODUCTIVITY:
+ return context.getText(com.android.internal.R.string.app_category_productivity);
+ case ApplicationInfo.CATEGORY_ACCESSIBILITY:
+ return context.getText(com.android.internal.R.string.app_category_accessibility);
+ default:
+ return null;
+ }
+ }
+
+ /** @hide */
+ public String classLoaderName;
+
+ /** @hide */
+ public String[] splitClassLoaderNames;
+
+ /** @hide */
+ public boolean hiddenUntilInstalled;
+
+ /** @hide */
+ public String zygotePreloadName;
+
+ /**
+ * Default (unspecified) setting of GWP-ASan.
+ */
+ public static final int GWP_ASAN_DEFAULT = -1;
+
+ /**
+ * Never enable GWP-ASan in this application or process.
+ */
+ public static final int GWP_ASAN_NEVER = 0;
+
+ /**
+ * Always enable GWP-ASan in this application or process.
+ */
+ public static final int GWP_ASAN_ALWAYS = 1;
+
+ /**
+ * These constants need to match the values of gwpAsanMode in application manifest.
+ * @hide
+ */
+ @IntDef(prefix = {"GWP_ASAN_"}, value = {
+ GWP_ASAN_DEFAULT,
+ GWP_ASAN_NEVER,
+ GWP_ASAN_ALWAYS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GwpAsanMode {}
+
+ /**
+ * Indicates if the application has requested GWP-ASan to be enabled, disabled, or left
+ * unspecified. Processes can override this setting.
+ */
+ private @GwpAsanMode int gwpAsanMode = GWP_ASAN_DEFAULT;
+
+ /**
+ * Default (unspecified) setting of Memtag.
+ */
+ public static final int MEMTAG_DEFAULT = -1;
+
+ /**
+ * Do not enable Memtag in this application or process.
+ */
+ public static final int MEMTAG_OFF = 0;
+
+ /**
+ * Enable Memtag in Async mode in this application or process.
+ */
+ public static final int MEMTAG_ASYNC = 1;
+
+ /**
+ * Enable Memtag in Sync mode in this application or process.
+ */
+ public static final int MEMTAG_SYNC = 2;
+
+ /**
+ * These constants need to match the values of memtagMode in application manifest.
+ * @hide
+ */
+ @IntDef(prefix = {"MEMTAG_"}, value = {
+ MEMTAG_DEFAULT,
+ MEMTAG_OFF,
+ MEMTAG_ASYNC,
+ MEMTAG_SYNC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MemtagMode {}
+
+ /**
+ * Indicates if the application has requested Memtag to be enabled, disabled, or left
+ * unspecified. Processes can override this setting.
+ */
+ private @MemtagMode int memtagMode = MEMTAG_DEFAULT;
+
+ /**
+ * Default (unspecified) setting of nativeHeapZeroInitialized.
+ */
+ public static final int ZEROINIT_DEFAULT = -1;
+
+ /**
+ * Disable zero-initialization of the native heap in this application or process.
+ */
+ public static final int ZEROINIT_DISABLED = 0;
+
+ /**
+ * Enable zero-initialization of the native heap in this application or process.
+ */
+ public static final int ZEROINIT_ENABLED = 1;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ZEROINIT_"}, value = {
+ ZEROINIT_DEFAULT,
+ ZEROINIT_DISABLED,
+ ZEROINIT_ENABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NativeHeapZeroInitialized {}
+
+ /**
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ private @NativeHeapZeroInitialized int nativeHeapZeroInitialized = ZEROINIT_DEFAULT;
+
+ /**
+ * If {@code true} this app requests raw external storage access.
+ * The request may not be honored due to policy or other reasons.
+ */
+ @Nullable
+ private Boolean requestRawExternalStorageAccess;
+
+ /**
+ * 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_DISABLED = 0;
+ /**
+ * No API enforcement, but enable the detection logic and warnings. Observed behaviour is the
+ * same as {@link #HIDDEN_API_ENFORCEMENT_DISABLED} but you may see warnings in the log when
+ * APIs are accessed.
+ * @hide
+ * */
+ public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1;
+ /**
+ * Dark grey list enforcement. Enforces the dark grey and black lists
+ * @hide
+ */
+ public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2;
+
+ private static final int HIDDEN_API_ENFORCEMENT_MIN = HIDDEN_API_ENFORCEMENT_DEFAULT;
+ private static final int HIDDEN_API_ENFORCEMENT_MAX = HIDDEN_API_ENFORCEMENT_ENABLED;
+
+ /**
+ * 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_DISABLED,
+ HIDDEN_API_ENFORCEMENT_JUST_WARN,
+ HIDDEN_API_ENFORCEMENT_ENABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HiddenApiEnforcementPolicy {}
+
+ /** @hide */
+ public static boolean isValidHiddenApiEnforcementPolicy(int policy) {
+ return policy >= HIDDEN_API_ENFORCEMENT_MIN && 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);
+ }
+
+ /** @hide */
+ public void dump(Printer pw, String prefix, int dumpFlags) {
+ super.dumpFront(pw, prefix);
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0 && className != null) {
+ pw.println(prefix + "className=" + className);
+ }
+ if (permission != null) {
+ pw.println(prefix + "permission=" + permission);
+ }
+ pw.println(prefix + "processName=" + processName);
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ pw.println(prefix + "taskAffinity=" + taskAffinity);
+ }
+ pw.println(prefix + "uid=" + uid + " flags=0x" + Integer.toHexString(flags)
+ + " privateFlags=0x" + Integer.toHexString(privateFlags)
+ + " theme=0x" + Integer.toHexString(theme));
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ pw.println(prefix + "requiresSmallestWidthDp=" + requiresSmallestWidthDp
+ + " compatibleWidthLimitDp=" + compatibleWidthLimitDp
+ + " largestWidthLimitDp=" + largestWidthLimitDp);
+ }
+ pw.println(prefix + "sourceDir=" + sourceDir);
+ if (!Objects.equals(sourceDir, publicSourceDir)) {
+ pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+ }
+ if (!ArrayUtils.isEmpty(splitSourceDirs)) {
+ pw.println(prefix + "splitSourceDirs=" + Arrays.toString(splitSourceDirs));
+ }
+ if (!ArrayUtils.isEmpty(splitPublicSourceDirs)
+ && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) {
+ pw.println(prefix + "splitPublicSourceDirs=" + Arrays.toString(splitPublicSourceDirs));
+ }
+ if (resourceDirs != null) {
+ pw.println(prefix + "resourceDirs=" + Arrays.toString(resourceDirs));
+ }
+ if (overlayPaths != null) {
+ pw.println(prefix + "overlayPaths=" + Arrays.toString(overlayPaths));
+ }
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0 && seInfo != null) {
+ pw.println(prefix + "seinfo=" + seInfo);
+ pw.println(prefix + "seinfoUser=" + seInfoUser);
+ }
+ pw.println(prefix + "dataDir=" + dataDir);
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ pw.println(prefix + "deviceProtectedDataDir=" + deviceProtectedDataDir);
+ pw.println(prefix + "credentialProtectedDataDir=" + credentialProtectedDataDir);
+ if (sharedLibraryFiles != null) {
+ pw.println(prefix + "sharedLibraryFiles=" + Arrays.toString(sharedLibraryFiles));
+ }
+ }
+ if (classLoaderName != null) {
+ pw.println(prefix + "classLoaderName=" + classLoaderName);
+ }
+ if (!ArrayUtils.isEmpty(splitClassLoaderNames)) {
+ pw.println(prefix + "splitClassLoaderNames=" + Arrays.toString(splitClassLoaderNames));
+ }
+
+ pw.println(prefix + "enabled=" + enabled
+ + " minSdkVersion=" + minSdkVersion
+ + " targetSdkVersion=" + targetSdkVersion
+ + " versionCode=" + longVersionCode
+ + " targetSandboxVersion=" + targetSandboxVersion);
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ if (manageSpaceActivityName != null) {
+ pw.println(prefix + "manageSpaceActivityName=" + manageSpaceActivityName);
+ }
+ if (descriptionRes != 0) {
+ pw.println(prefix + "description=0x" + Integer.toHexString(descriptionRes));
+ }
+ if (uiOptions != 0) {
+ pw.println(prefix + "uiOptions=0x" + Integer.toHexString(uiOptions));
+ }
+ pw.println(prefix + "supportsRtl=" + (hasRtlSupport() ? "true" : "false"));
+ if (fullBackupContent > 0) {
+ pw.println(prefix + "fullBackupContent=@xml/" + fullBackupContent);
+ } else {
+ pw.println(prefix + "fullBackupContent="
+ + (fullBackupContent < 0 ? "false" : "true"));
+ }
+ if (dataExtractionRulesRes != 0) {
+ pw.println(prefix + "dataExtractionRules=@xml/" + dataExtractionRulesRes);
+ }
+ pw.println(prefix + "crossProfile=" + (crossProfile ? "true" : "false"));
+ if (networkSecurityConfigRes != 0) {
+ pw.println(prefix + "networkSecurityConfigRes=0x"
+ + Integer.toHexString(networkSecurityConfigRes));
+ }
+ if (category != CATEGORY_UNDEFINED) {
+ pw.println(prefix + "category=" + category);
+ }
+ pw.println(prefix + "HiddenApiEnforcementPolicy=" + getHiddenApiEnforcementPolicy());
+ pw.println(prefix + "usesNonSdkApi=" + usesNonSdkApi());
+ pw.println(prefix + "allowsPlaybackCapture="
+ + (isAudioPlaybackCaptureAllowed() ? "true" : "false"));
+ if (gwpAsanMode != GWP_ASAN_DEFAULT) {
+ pw.println(prefix + "gwpAsanMode=" + gwpAsanMode);
+ }
+ if (memtagMode != MEMTAG_DEFAULT) {
+ pw.println(prefix + "memtagMode=" + memtagMode);
+ }
+ if (nativeHeapZeroInitialized != ZEROINIT_DEFAULT) {
+ pw.println(prefix + "nativeHeapZeroInitialized=" + nativeHeapZeroInitialized);
+ }
+ if (requestRawExternalStorageAccess != null) {
+ pw.println(prefix + "requestRawExternalStorageAccess="
+ + requestRawExternalStorageAccess);
+ }
+ }
+ super.dumpBack(pw, prefix);
+ }
+
+ /** {@hide} */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId, int dumpFlags) {
+ long token = proto.start(fieldId);
+ super.dumpDebug(proto, ApplicationInfoProto.PACKAGE, dumpFlags);
+ proto.write(ApplicationInfoProto.PERMISSION, permission);
+ proto.write(ApplicationInfoProto.PROCESS_NAME, processName);
+ proto.write(ApplicationInfoProto.UID, uid);
+ proto.write(ApplicationInfoProto.FLAGS, flags);
+ proto.write(ApplicationInfoProto.PRIVATE_FLAGS, privateFlags);
+ proto.write(ApplicationInfoProto.THEME, theme);
+ proto.write(ApplicationInfoProto.SOURCE_DIR, sourceDir);
+ if (!Objects.equals(sourceDir, publicSourceDir)) {
+ proto.write(ApplicationInfoProto.PUBLIC_SOURCE_DIR, publicSourceDir);
+ }
+ if (!ArrayUtils.isEmpty(splitSourceDirs)) {
+ for (String dir : splitSourceDirs) {
+ proto.write(ApplicationInfoProto.SPLIT_SOURCE_DIRS, dir);
+ }
+ }
+ if (!ArrayUtils.isEmpty(splitPublicSourceDirs)
+ && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) {
+ for (String dir : splitPublicSourceDirs) {
+ proto.write(ApplicationInfoProto.SPLIT_PUBLIC_SOURCE_DIRS, dir);
+ }
+ }
+ if (resourceDirs != null) {
+ for (String dir : resourceDirs) {
+ proto.write(ApplicationInfoProto.RESOURCE_DIRS, dir);
+ }
+ }
+ if (overlayPaths != null) {
+ for (String dir : overlayPaths) {
+ proto.write(ApplicationInfoProto.OVERLAY_PATHS, dir);
+ }
+ }
+ proto.write(ApplicationInfoProto.DATA_DIR, dataDir);
+ proto.write(ApplicationInfoProto.CLASS_LOADER_NAME, classLoaderName);
+ if (!ArrayUtils.isEmpty(splitClassLoaderNames)) {
+ for (String name : splitClassLoaderNames) {
+ proto.write(ApplicationInfoProto.SPLIT_CLASS_LOADER_NAMES, name);
+ }
+ }
+
+ long versionToken = proto.start(ApplicationInfoProto.VERSION);
+ proto.write(ApplicationInfoProto.Version.ENABLED, enabled);
+ proto.write(ApplicationInfoProto.Version.MIN_SDK_VERSION, minSdkVersion);
+ proto.write(ApplicationInfoProto.Version.TARGET_SDK_VERSION, targetSdkVersion);
+ proto.write(ApplicationInfoProto.Version.VERSION_CODE, longVersionCode);
+ proto.write(ApplicationInfoProto.Version.TARGET_SANDBOX_VERSION, targetSandboxVersion);
+ proto.end(versionToken);
+
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ long detailToken = proto.start(ApplicationInfoProto.DETAIL);
+ if (className != null) {
+ proto.write(ApplicationInfoProto.Detail.CLASS_NAME, className);
+ }
+ proto.write(ApplicationInfoProto.Detail.TASK_AFFINITY, taskAffinity);
+ proto.write(ApplicationInfoProto.Detail.REQUIRES_SMALLEST_WIDTH_DP,
+ requiresSmallestWidthDp);
+ proto.write(ApplicationInfoProto.Detail.COMPATIBLE_WIDTH_LIMIT_DP,
+ compatibleWidthLimitDp);
+ proto.write(ApplicationInfoProto.Detail.LARGEST_WIDTH_LIMIT_DP,
+ largestWidthLimitDp);
+ if (seInfo != null) {
+ proto.write(ApplicationInfoProto.Detail.SEINFO, seInfo);
+ proto.write(ApplicationInfoProto.Detail.SEINFO_USER, seInfoUser);
+ }
+ proto.write(ApplicationInfoProto.Detail.DEVICE_PROTECTED_DATA_DIR,
+ deviceProtectedDataDir);
+ proto.write(ApplicationInfoProto.Detail.CREDENTIAL_PROTECTED_DATA_DIR,
+ credentialProtectedDataDir);
+ if (sharedLibraryFiles != null) {
+ for (String f : sharedLibraryFiles) {
+ proto.write(ApplicationInfoProto.Detail.SHARED_LIBRARY_FILES, f);
+ }
+ }
+ if (manageSpaceActivityName != null) {
+ proto.write(ApplicationInfoProto.Detail.MANAGE_SPACE_ACTIVITY_NAME,
+ manageSpaceActivityName);
+ }
+ if (descriptionRes != 0) {
+ proto.write(ApplicationInfoProto.Detail.DESCRIPTION_RES, descriptionRes);
+ }
+ if (uiOptions != 0) {
+ proto.write(ApplicationInfoProto.Detail.UI_OPTIONS, uiOptions);
+ }
+ proto.write(ApplicationInfoProto.Detail.SUPPORTS_RTL, hasRtlSupport());
+ if (fullBackupContent > 0) {
+ proto.write(ApplicationInfoProto.Detail.CONTENT, "@xml/" + fullBackupContent);
+ } else {
+ proto.write(ApplicationInfoProto.Detail.IS_FULL_BACKUP, fullBackupContent == 0);
+ }
+ if (networkSecurityConfigRes != 0) {
+ proto.write(ApplicationInfoProto.Detail.NETWORK_SECURITY_CONFIG_RES,
+ networkSecurityConfigRes);
+ }
+ if (category != CATEGORY_UNDEFINED) {
+ proto.write(ApplicationInfoProto.Detail.CATEGORY, category);
+ }
+ if (gwpAsanMode != GWP_ASAN_DEFAULT) {
+ proto.write(ApplicationInfoProto.Detail.ENABLE_GWP_ASAN, gwpAsanMode);
+ }
+ if (memtagMode != MEMTAG_DEFAULT) {
+ proto.write(ApplicationInfoProto.Detail.ENABLE_MEMTAG, memtagMode);
+ }
+ if (nativeHeapZeroInitialized != ZEROINIT_DEFAULT) {
+ proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT,
+ nativeHeapZeroInitialized);
+ }
+ proto.end(detailToken);
+ }
+ proto.end(token);
+ }
+
+ /**
+ * @return true if "supportsRtl" has been set to true in the AndroidManifest
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean hasRtlSupport() {
+ return (flags & FLAG_SUPPORTS_RTL) == FLAG_SUPPORTS_RTL;
+ }
+
+ /** {@hide} */
+ public boolean hasCode() {
+ return (flags & FLAG_HAS_CODE) != 0;
+ }
+
+ public static class DisplayNameComparator
+ implements Comparator<ApplicationInfo> {
+ public DisplayNameComparator(PackageManager pm) {
+ mPM = pm;
+ }
+
+ public final int compare(ApplicationInfo aa, ApplicationInfo ab) {
+ CharSequence sa = mPM.getApplicationLabel(aa);
+ if (sa == null) {
+ sa = aa.packageName;
+ }
+ CharSequence sb = mPM.getApplicationLabel(ab);
+ if (sb == null) {
+ sb = ab.packageName;
+ }
+
+ return sCollator.compare(sa.toString(), sb.toString());
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final Collator sCollator = Collator.getInstance();
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private PackageManager mPM;
+ }
+
+ public ApplicationInfo() {
+ }
+
+ public ApplicationInfo(ApplicationInfo orig) {
+ super(orig);
+ taskAffinity = orig.taskAffinity;
+ permission = orig.permission;
+ processName = orig.processName;
+ className = orig.className;
+ theme = orig.theme;
+ flags = orig.flags;
+ privateFlags = orig.privateFlags;
+ privateFlagsExt = orig.privateFlagsExt;
+ requiresSmallestWidthDp = orig.requiresSmallestWidthDp;
+ compatibleWidthLimitDp = orig.compatibleWidthLimitDp;
+ largestWidthLimitDp = orig.largestWidthLimitDp;
+ volumeUuid = orig.volumeUuid;
+ storageUuid = orig.storageUuid;
+ scanSourceDir = orig.scanSourceDir;
+ scanPublicSourceDir = orig.scanPublicSourceDir;
+ sourceDir = orig.sourceDir;
+ publicSourceDir = orig.publicSourceDir;
+ splitNames = orig.splitNames;
+ splitSourceDirs = orig.splitSourceDirs;
+ splitPublicSourceDirs = orig.splitPublicSourceDirs;
+ splitDependencies = orig.splitDependencies;
+ nativeLibraryDir = orig.nativeLibraryDir;
+ secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir;
+ nativeLibraryRootDir = orig.nativeLibraryRootDir;
+ nativeLibraryRootRequiresIsa = orig.nativeLibraryRootRequiresIsa;
+ primaryCpuAbi = orig.primaryCpuAbi;
+ secondaryCpuAbi = orig.secondaryCpuAbi;
+ resourceDirs = orig.resourceDirs;
+ overlayPaths = orig.overlayPaths;
+ seInfo = orig.seInfo;
+ seInfoUser = orig.seInfoUser;
+ sharedLibraryFiles = orig.sharedLibraryFiles;
+ sharedLibraryInfos = orig.sharedLibraryInfos;
+ dataDir = orig.dataDir;
+ deviceProtectedDataDir = orig.deviceProtectedDataDir;
+ credentialProtectedDataDir = orig.credentialProtectedDataDir;
+ uid = orig.uid;
+ minSdkVersion = orig.minSdkVersion;
+ targetSdkVersion = orig.targetSdkVersion;
+ setVersionCode(orig.longVersionCode);
+ enabled = orig.enabled;
+ enabledSetting = orig.enabledSetting;
+ installLocation = orig.installLocation;
+ manageSpaceActivityName = orig.manageSpaceActivityName;
+ descriptionRes = orig.descriptionRes;
+ uiOptions = orig.uiOptions;
+ backupAgentName = orig.backupAgentName;
+ fullBackupContent = orig.fullBackupContent;
+ dataExtractionRulesRes = orig.dataExtractionRulesRes;
+ crossProfile = orig.crossProfile;
+ networkSecurityConfigRes = orig.networkSecurityConfigRes;
+ category = orig.category;
+ targetSandboxVersion = orig.targetSandboxVersion;
+ classLoaderName = orig.classLoaderName;
+ splitClassLoaderNames = orig.splitClassLoaderNames;
+ appComponentFactory = orig.appComponentFactory;
+ iconRes = orig.iconRes;
+ roundIconRes = orig.roundIconRes;
+ compileSdkVersion = orig.compileSdkVersion;
+ compileSdkVersionCodename = orig.compileSdkVersionCodename;
+ mHiddenApiPolicy = orig.mHiddenApiPolicy;
+ hiddenUntilInstalled = orig.hiddenUntilInstalled;
+ zygotePreloadName = orig.zygotePreloadName;
+ gwpAsanMode = orig.gwpAsanMode;
+ memtagMode = orig.memtagMode;
+ nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
+ requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess;
+ }
+
+ public String toString() {
+ return "ApplicationInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ if (dest.maybeWriteSquashed(this)) {
+ return;
+ }
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeString8(taskAffinity);
+ dest.writeString8(permission);
+ dest.writeString8(processName);
+ dest.writeString8(className);
+ dest.writeInt(theme);
+ dest.writeInt(flags);
+ dest.writeInt(privateFlags);
+ dest.writeInt(privateFlagsExt);
+ dest.writeInt(requiresSmallestWidthDp);
+ dest.writeInt(compatibleWidthLimitDp);
+ dest.writeInt(largestWidthLimitDp);
+ if (storageUuid != null) {
+ dest.writeInt(1);
+ dest.writeLong(storageUuid.getMostSignificantBits());
+ dest.writeLong(storageUuid.getLeastSignificantBits());
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString8(scanSourceDir);
+ dest.writeString8(scanPublicSourceDir);
+ dest.writeString8(sourceDir);
+ dest.writeString8(publicSourceDir);
+ dest.writeString8Array(splitNames);
+ dest.writeString8Array(splitSourceDirs);
+ dest.writeString8Array(splitPublicSourceDirs);
+ dest.writeSparseArray((SparseArray) splitDependencies);
+ dest.writeString8(nativeLibraryDir);
+ dest.writeString8(secondaryNativeLibraryDir);
+ dest.writeString8(nativeLibraryRootDir);
+ dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0);
+ dest.writeString8(primaryCpuAbi);
+ dest.writeString8(secondaryCpuAbi);
+ dest.writeString8Array(resourceDirs);
+ dest.writeString8Array(overlayPaths);
+ dest.writeString8(seInfo);
+ dest.writeString8(seInfoUser);
+ dest.writeString8Array(sharedLibraryFiles);
+ dest.writeTypedList(sharedLibraryInfos);
+ dest.writeString8(dataDir);
+ dest.writeString8(deviceProtectedDataDir);
+ dest.writeString8(credentialProtectedDataDir);
+ dest.writeInt(uid);
+ dest.writeInt(minSdkVersion);
+ dest.writeInt(targetSdkVersion);
+ dest.writeLong(longVersionCode);
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(enabledSetting);
+ dest.writeInt(installLocation);
+ dest.writeString8(manageSpaceActivityName);
+ dest.writeString8(backupAgentName);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(uiOptions);
+ dest.writeInt(fullBackupContent);
+ dest.writeInt(dataExtractionRulesRes);
+ dest.writeBoolean(crossProfile);
+ dest.writeInt(networkSecurityConfigRes);
+ dest.writeInt(category);
+ dest.writeInt(targetSandboxVersion);
+ dest.writeString8(classLoaderName);
+ dest.writeString8Array(splitClassLoaderNames);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString8(compileSdkVersionCodename);
+ dest.writeString8(appComponentFactory);
+ dest.writeInt(iconRes);
+ dest.writeInt(roundIconRes);
+ dest.writeInt(mHiddenApiPolicy);
+ dest.writeInt(hiddenUntilInstalled ? 1 : 0);
+ dest.writeString8(zygotePreloadName);
+ dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ dest.writeInt(nativeHeapZeroInitialized);
+ sForBoolean.parcel(requestRawExternalStorageAccess, dest, parcelableFlags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
+ = new Parcelable.Creator<ApplicationInfo>() {
+ @Override
+ public ApplicationInfo createFromParcel(Parcel source) {
+ return source.readSquashed(ApplicationInfo::new);
+ }
+
+ @Override
+ public ApplicationInfo[] newArray(int size) {
+ return new ApplicationInfo[size];
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ private ApplicationInfo(Parcel source) {
+ super(source);
+ taskAffinity = source.readString8();
+ permission = source.readString8();
+ processName = source.readString8();
+ className = source.readString8();
+ theme = source.readInt();
+ flags = source.readInt();
+ privateFlags = source.readInt();
+ privateFlagsExt = source.readInt();
+ requiresSmallestWidthDp = source.readInt();
+ compatibleWidthLimitDp = source.readInt();
+ largestWidthLimitDp = source.readInt();
+ if (source.readInt() != 0) {
+ storageUuid = new UUID(source.readLong(), source.readLong());
+ volumeUuid = StorageManager.convert(storageUuid);
+ }
+ scanSourceDir = source.readString8();
+ scanPublicSourceDir = source.readString8();
+ sourceDir = source.readString8();
+ publicSourceDir = source.readString8();
+ splitNames = source.createString8Array();
+ splitSourceDirs = source.createString8Array();
+ splitPublicSourceDirs = source.createString8Array();
+ splitDependencies = source.readSparseArray(null);
+ nativeLibraryDir = source.readString8();
+ secondaryNativeLibraryDir = source.readString8();
+ nativeLibraryRootDir = source.readString8();
+ nativeLibraryRootRequiresIsa = source.readInt() != 0;
+ primaryCpuAbi = source.readString8();
+ secondaryCpuAbi = source.readString8();
+ resourceDirs = source.createString8Array();
+ overlayPaths = source.createString8Array();
+ seInfo = source.readString8();
+ seInfoUser = source.readString8();
+ sharedLibraryFiles = source.createString8Array();
+ sharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR);
+ dataDir = source.readString8();
+ deviceProtectedDataDir = source.readString8();
+ credentialProtectedDataDir = source.readString8();
+ uid = source.readInt();
+ minSdkVersion = source.readInt();
+ targetSdkVersion = source.readInt();
+ setVersionCode(source.readLong());
+ enabled = source.readInt() != 0;
+ enabledSetting = source.readInt();
+ installLocation = source.readInt();
+ manageSpaceActivityName = source.readString8();
+ backupAgentName = source.readString8();
+ descriptionRes = source.readInt();
+ uiOptions = source.readInt();
+ fullBackupContent = source.readInt();
+ dataExtractionRulesRes = source.readInt();
+ crossProfile = source.readBoolean();
+ networkSecurityConfigRes = source.readInt();
+ category = source.readInt();
+ targetSandboxVersion = source.readInt();
+ classLoaderName = source.readString8();
+ splitClassLoaderNames = source.createString8Array();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString8();
+ appComponentFactory = source.readString8();
+ iconRes = source.readInt();
+ roundIconRes = source.readInt();
+ mHiddenApiPolicy = source.readInt();
+ hiddenUntilInstalled = source.readInt() != 0;
+ zygotePreloadName = source.readString8();
+ gwpAsanMode = source.readInt();
+ memtagMode = source.readInt();
+ nativeHeapZeroInitialized = source.readInt();
+ requestRawExternalStorageAccess = sForBoolean.unparcel(source);
+ }
+
+ /**
+ * Retrieve the textual description of the application. This
+ * will call back on the given PackageManager to load the description from
+ * the application.
+ *
+ * @param pm A PackageManager from which the label can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a CharSequence containing the application's description.
+ * If there is no description, null is returned.
+ */
+ public CharSequence loadDescription(PackageManager pm) {
+ if (descriptionRes != 0) {
+ CharSequence label = pm.getText(packageName, descriptionRes, this);
+ if (label != null) {
+ return label;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Disable compatibility mode
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void disableCompatibilityMode() {
+ flags |= (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS |
+ FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS |
+ FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+ }
+
+ /**
+ * Is using compatibility mode for non densty aware legacy applications.
+ *
+ * @hide
+ */
+ public boolean usesCompatibilityMode() {
+ return targetSdkVersion < DONUT ||
+ (flags & (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS |
+ FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS |
+ FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS)) == 0;
+ }
+
+ /** {@hide} */
+ public void initForUser(int userId) {
+ uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+
+ if ("android".equals(packageName)) {
+ dataDir = Environment.getDataSystemDirectory().getAbsolutePath();
+ return;
+ }
+
+ deviceProtectedDataDir = Environment
+ .getDataUserDePackageDirectory(volumeUuid, userId, packageName)
+ .getAbsolutePath();
+ credentialProtectedDataDir = Environment
+ .getDataUserCePackageDirectory(volumeUuid, userId, packageName)
+ .getAbsolutePath();
+
+ if ((privateFlags & PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ dataDir = deviceProtectedDataDir;
+ } else {
+ dataDir = credentialProtectedDataDir;
+ }
+ }
+
+ private boolean isPackageWhitelistedForHiddenApis() {
+ return SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(packageName);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean usesNonSdkApi() {
+ return (privateFlags & PRIVATE_FLAG_USES_NON_SDK_API) != 0;
+ }
+
+ /**
+ * Whether an app needs to keep the app data on uninstall.
+ *
+ * @return {@code true} if the app indicates that it needs to keep the app data
+ *
+ * @hide
+ */
+ public boolean hasFragileUserData() {
+ return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0;
+ }
+
+ /**
+ * Whether an app allows its playback audio to be captured by other apps.
+ *
+ * @return {@code true} if the app indicates that its audio can be captured by other apps.
+ *
+ * @hide
+ */
+ public boolean isAudioPlaybackCaptureAllowed() {
+ return (privateFlags & PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE) != 0;
+ }
+
+ /**
+ * If {@code true} this app requested to run in the legacy storage mode.
+ *
+ * @hide
+ */
+ public boolean hasRequestedLegacyExternalStorage() {
+ return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0;
+ }
+
+ /**
+ * Use default value for
+ * {@link android.R.styleable#AndroidManifestApplication_requestRawExternalStorageAccess}.
+ */
+ public static final int RAW_EXTERNAL_STORAGE_ACCESS_DEFAULT = 0;
+
+ /**
+ * Raw external storage was requested by this app.
+ */
+ public static final int RAW_EXTERNAL_STORAGE_ACCESS_REQUESTED = 1;
+
+ /**
+ * Raw external storage was not requested by this app.
+ */
+ public static final int RAW_EXTERNAL_STORAGE_ACCESS_NOT_REQUESTED = 2;
+
+ /**
+ * These constants need to match the value of
+ * {@link android.R.styleable#AndroidManifestApplication_requestRawExternalStorageAccess}.
+ * in application manifest.
+ * @hide
+ */
+ @IntDef(prefix = {"RAW_EXTERNAL_STORAGE_"}, value = {
+ RAW_EXTERNAL_STORAGE_ACCESS_DEFAULT,
+ RAW_EXTERNAL_STORAGE_ACCESS_REQUESTED,
+ RAW_EXTERNAL_STORAGE_ACCESS_NOT_REQUESTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RawExternalStorage {}
+
+ /**
+ * @return
+ * <ul>
+ * <li>{@link ApplicationInfo#RAW_EXTERNAL_STORAGE_ACCESS_DEFAULT} if app didn't specify
+ * {@link android.R.styleable#AndroidManifestApplication_requestRawExternalStorageAccess}
+ * attribute in the manifest.
+ * <li>{@link ApplicationInfo#RAW_EXTERNAL_STORAGE_ACCESS_REQUESTED} if this app requested raw
+ * external storage access.
+ * <li>{@link ApplicationInfo#RAW_EXTERNAL_STORAGE_ACCESS_NOT_REQUESTED} if this app requests to
+ * disable raw external storage access
+ * </ul
+ * <p>
+ * Note that this doesn't give any hints on whether the app gets raw external storage access or
+ * not. Also, apps may get raw external storage access by default in some cases, see
+ * {@link android.R.styleable#AndroidManifestApplication_requestRawExternalStorageAccess}.
+ */
+ public @RawExternalStorage int getRequestRawExternalStorageAccess() {
+ if (requestRawExternalStorageAccess == null) {
+ return RAW_EXTERNAL_STORAGE_ACCESS_DEFAULT;
+ }
+ return requestRawExternalStorageAccess ? RAW_EXTERNAL_STORAGE_ACCESS_REQUESTED
+ : RAW_EXTERNAL_STORAGE_ACCESS_NOT_REQUESTED;
+ }
+
+ /**
+ * If {@code true} this app allows heap pointer tagging.
+ *
+ * @hide
+ */
+ public boolean allowsNativeHeapPointerTagging() {
+ return (privateFlags & PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) != 0;
+ }
+
+ private boolean isAllowedToUseHiddenApis() {
+ if (isSignedWithPlatformKey()) {
+ return true;
+ } else if (isSystemApp() || isUpdatedSystemApp()) {
+ return usesNonSdkApi() || isPackageWhitelistedForHiddenApis();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public @HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
+ if (isAllowedToUseHiddenApis()) {
+ return HIDDEN_API_ENFORCEMENT_DISABLED;
+ }
+ if (mHiddenApiPolicy != HIDDEN_API_ENFORCEMENT_DEFAULT) {
+ return mHiddenApiPolicy;
+ }
+ return HIDDEN_API_ENFORCEMENT_ENABLED;
+ }
+
+ /**
+ * @hide
+ */
+ public void setHiddenApiEnforcementPolicy(@HiddenApiEnforcementPolicy int policy) {
+ if (!isValidHiddenApiEnforcementPolicy(policy)) {
+ throw new IllegalArgumentException("Invalid API enforcement policy: " + policy);
+ }
+ mHiddenApiPolicy = policy;
+ }
+
+ /**
+ * Updates the hidden API enforcement policy for this app from the given values, if appropriate.
+ *
+ * This will have no effect if this app is not subject to hidden API enforcement, i.e. if it
+ * is on the package allowlist.
+ *
+ * @param policy configured policy for this app, or {@link #HIDDEN_API_ENFORCEMENT_DEFAULT}
+ * if nothing configured.
+ * @hide
+ */
+ public void maybeUpdateHiddenApiEnforcementPolicy(@HiddenApiEnforcementPolicy int policy) {
+ if (isPackageWhitelistedForHiddenApis()) {
+ return;
+ }
+ setHiddenApiEnforcementPolicy(policy);
+ }
+
+ /**
+ * @hide
+ */
+ public void setVersionCode(long newVersionCode) {
+ longVersionCode = newVersionCode;
+ versionCode = (int) newVersionCode;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public Drawable loadDefaultIcon(PackageManager pm) {
+ if ((flags & FLAG_EXTERNAL_STORAGE) != 0
+ && isPackageUnavailable(pm)) {
+ return Resources.getSystem().getDrawable(
+ com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
+ }
+ return pm.getDefaultActivityIcon();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private boolean isPackageUnavailable(PackageManager pm) {
+ try {
+ return pm.getPackageInfo(packageName, 0) == null;
+ } catch (NameNotFoundException ex) {
+ return true;
+ }
+ }
+
+ /** @hide */
+ public boolean isDefaultToDeviceProtectedStorage() {
+ return (privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
+ }
+
+ /** @hide */
+ public boolean isDirectBootAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0;
+ }
+
+ /**
+ * Check whether the application is encryption aware.
+ *
+ * @see #isDirectBootAware()
+ * @see #isPartiallyDirectBootAware()
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isEncryptionAware() {
+ return isDirectBootAware() || isPartiallyDirectBootAware();
+ }
+
+ /** @hide */
+ public boolean isExternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+ }
+
+ /**
+ * True if the application is installed as an instant app.
+ * @hide
+ */
+ @SystemApi
+ public boolean isInstantApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ }
+
+ /** @hide */
+ public boolean isInternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+ }
+
+ /**
+ * True if the application is pre-installed on the OEM partition of the system image.
+ * @hide
+ */
+ @SystemApi
+ public boolean isOem() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+ }
+
+ /** @hide */
+ public boolean isOdm() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0;
+ }
+
+ /** @hide */
+ public boolean isPartiallyDirectBootAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
+ }
+
+ /** @hide */
+ public boolean isSignedWithPlatformKey() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY) != 0;
+ }
+
+ /** @hide */
+ @TestApi
+ public boolean isPrivilegedApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
+ }
+
+ /** @hide */
+ public boolean isRequiredForSystemUser() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
+ }
+
+ /** @hide */
+ public boolean isStaticSharedLibrary() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
+ }
+
+ /** @hide */
+ @TestApi
+ public boolean isSystemApp() {
+ return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /** @hide */
+ public boolean isUpdatedSystemApp() {
+ return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
+ /**
+ * True if the application is pre-installed on the vendor partition of the system image.
+ * @hide
+ */
+ @SystemApi
+ public boolean isVendor() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0;
+ }
+
+ /**
+ * True if the application is pre-installed on the product partition of the system image.
+ * @hide
+ */
+ @SystemApi
+ public boolean isProduct() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0;
+ }
+
+ /** @hide */
+ public boolean isSystemExt() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0;
+ }
+
+ /** @hide */
+ public boolean isEmbeddedDexUsed() {
+ return (privateFlags & PRIVATE_FLAG_USE_EMBEDDED_DEX) != 0;
+ }
+
+ /**
+ * Returns whether or not this application was installed as a virtual preload.
+ */
+ public boolean isVirtualPreload() {
+ return (privateFlags & PRIVATE_FLAG_VIRTUAL_PRELOAD) != 0;
+ }
+
+ /**
+ * Returns whether or not this application can be profiled by the shell user,
+ * even when running on a device that is running in user mode.
+ */
+ public boolean isProfileableByShell() {
+ return (privateFlags & PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0;
+ }
+
+ /**
+ * Returns whether this application can be profiled, either by the shell user or the system.
+ */
+ public boolean isProfileable() {
+ return (privateFlagsExt & PRIVATE_FLAG_EXT_PROFILEABLE) != 0;
+ }
+
+ /**
+ * Returns whether attributions provided by the application are meant to be user-visible.
+ * Defaults to false if application info is retrieved without
+ * {@link PackageManager#GET_ATTRIBUTIONS}.
+ */
+ public boolean areAttributionsUserVisible() {
+ return (privateFlagsExt & PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE) != 0;
+ }
+
+ /**
+ * Returns true if the app has declared in its manifest that it wants its split APKs to be
+ * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
+ * @hide
+ */
+ public boolean requestsIsolatedSplitLoading() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
+ }
+
+ /**
+ * Returns true if the package has declared in its manifest that it is a
+ * runtime resource overlay.
+ */
+ public boolean isResourceOverlay() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY) != 0;
+ }
+
+ /**
+ * @return whether the app has requested exemption from the foreground service restrictions.
+ * This does not take any affect for now.
+ * @hide
+ */
+ @TestApi
+ public boolean hasRequestForegroundServiceExemption() {
+ return (privateFlagsExt
+ & ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ @Override protected ApplicationInfo getApplicationInfo() {
+ return this;
+ }
+
+ /**
+ * Return all the APK paths that may be required to load this application, including all
+ * splits, shared libraries, and resource overlays.
+ * @hide
+ */
+ public String[] getAllApkPaths() {
+ final String[][] inputLists = {
+ splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
+ };
+ final List<String> output = new ArrayList<>(10);
+ if (sourceDir != null) {
+ output.add(sourceDir);
+ }
+ for (String[] inputList : inputLists) {
+ if (inputList != null) {
+ for (String input : inputList) {
+ output.add(input);
+ }
+ }
+ }
+ return output.toArray(new String[output.size()]);
+ }
+
+ /** {@hide} */ public void setCodePath(String codePath) { scanSourceDir = codePath; }
+ /** {@hide} */ public void setBaseCodePath(String baseCodePath) { sourceDir = baseCodePath; }
+ /** {@hide} */ public void setSplitCodePaths(String[] splitCodePaths) { splitSourceDirs = splitCodePaths; }
+ /** {@hide} */ public void setResourcePath(String resourcePath) { scanPublicSourceDir = resourcePath; }
+ /** {@hide} */ public void setBaseResourcePath(String baseResourcePath) { publicSourceDir = baseResourcePath; }
+ /** {@hide} */ public void setSplitResourcePaths(String[] splitResourcePaths) { splitPublicSourceDirs = splitResourcePaths; }
+ /** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; }
+ /** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; }
+ /** {@hide} */ public void setNativeHeapZeroInitialized(@NativeHeapZeroInitialized int value) {
+ nativeHeapZeroInitialized = value;
+ }
+ /** {@hide} */
+ public void setRequestRawExternalStorageAccess(@Nullable Boolean value) {
+ requestRawExternalStorageAccess = value;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String getCodePath() { return scanSourceDir; }
+ /** {@hide} */ public String getBaseCodePath() { return sourceDir; }
+ /** {@hide} */ public String[] getSplitCodePaths() { return splitSourceDirs; }
+ /** {@hide} */ public String getResourcePath() { return scanPublicSourceDir; }
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String getBaseResourcePath() { return publicSourceDir; }
+ /** {@hide} */ public String[] getSplitResourcePaths() { return splitPublicSourceDirs; }
+ @GwpAsanMode
+ public int getGwpAsanMode() { return gwpAsanMode; }
+
+ /**
+ * Returns whether the application has requested Memtag to be enabled, disabled, or left
+ * unspecified. Processes can override this setting.
+ */
+ @MemtagMode
+ public int getMemtagMode() {
+ return memtagMode;
+ }
+
+ /**
+ * Returns whether the application has requested automatic zero-initialization of native heap
+ * memory allocations to be enabled or disabled.
+ */
+ @NativeHeapZeroInitialized
+ public int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
+ }
+}
diff --git a/android/content/pm/AppsQueryHelper.java b/android/content/pm/AppsQueryHelper.java
new file mode 100644
index 0000000..6cb7f77
--- /dev/null
+++ b/android/content/pm/AppsQueryHelper.java
@@ -0,0 +1,217 @@
+/*
+ * 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.Manifest;
+import android.app.AppGlobals;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.view.inputmethod.InputMethod;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for querying installed applications using multiple criteria.
+ *
+ * @hide
+ */
+public class AppsQueryHelper {
+
+ /**
+ * Return apps without launcher icon
+ */
+ public static int GET_NON_LAUNCHABLE_APPS = 1;
+
+ /**
+ * Return apps with {@link Manifest.permission#INTERACT_ACROSS_USERS} permission
+ */
+ public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1;
+
+ /**
+ * Return all input methods available for the current user.
+ */
+ public static int GET_IMES = 1 << 2;
+
+ /**
+ * Return all apps that are flagged as required for the system user.
+ */
+ public static int GET_REQUIRED_FOR_SYSTEM_USER = 1 << 3;
+
+ private final IPackageManager mPackageManager;
+ private List<ApplicationInfo> mAllApps;
+
+ public AppsQueryHelper(IPackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ public AppsQueryHelper() {
+ this(AppGlobals.getPackageManager());
+ }
+
+ /**
+ * Return a List of all packages that satisfy a specified criteria.
+ * @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS},
+ * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_IMES}.
+ * @param systemAppsOnly if true, only system apps will be returned
+ * @param user user, whose apps are queried
+ */
+ public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) {
+ boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0;
+ boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0;
+ boolean imes = (flags & GET_IMES) > 0;
+ boolean requiredForSystemUser = (flags & GET_REQUIRED_FOR_SYSTEM_USER) > 0;
+ if (mAllApps == null) {
+ mAllApps = getAllApps(user.getIdentifier());
+ }
+
+ List<String> result = new ArrayList<>();
+ if (flags == 0) {
+ final int allAppsSize = mAllApps.size();
+ for (int i = 0; i < allAppsSize; i++) {
+ final ApplicationInfo appInfo = mAllApps.get(i);
+ if (systemAppsOnly && !appInfo.isSystemApp()) {
+ continue;
+ }
+ result.add(appInfo.packageName);
+ }
+ return result;
+ }
+
+ if (nonLaunchableApps) {
+ Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
+ final List<ResolveInfo> resolveInfos = queryIntentActivitiesAsUser(intent,
+ user.getIdentifier());
+
+ ArraySet<String> appsWithLaunchers = new ArraySet<>();
+ final int resolveInfosSize = resolveInfos.size();
+ for (int i = 0; i < resolveInfosSize; i++) {
+ appsWithLaunchers.add(resolveInfos.get(i).activityInfo.packageName);
+ }
+ final int allAppsSize = mAllApps.size();
+ for (int i = 0; i < allAppsSize; i++) {
+ final ApplicationInfo appInfo = mAllApps.get(i);
+ if (systemAppsOnly && !appInfo.isSystemApp()) {
+ continue;
+ }
+ final String packageName = appInfo.packageName;
+ if (!appsWithLaunchers.contains(packageName)) {
+ result.add(packageName);
+ }
+ }
+ }
+ if (interactAcrossUsers) {
+ final List<PackageInfo> packagesHoldingPermissions = getPackagesHoldingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS, user.getIdentifier());
+ final int packagesHoldingPermissionsSize = packagesHoldingPermissions.size();
+ for (int i = 0; i < packagesHoldingPermissionsSize; i++) {
+ PackageInfo packageInfo = packagesHoldingPermissions.get(i);
+ if (systemAppsOnly && !packageInfo.applicationInfo.isSystemApp()) {
+ continue;
+ }
+ if (!result.contains(packageInfo.packageName)) {
+ result.add(packageInfo.packageName);
+ }
+ }
+ }
+
+ if (imes) {
+ final List<ResolveInfo> resolveInfos = queryIntentServicesAsUser(
+ new Intent(InputMethod.SERVICE_INTERFACE), user.getIdentifier());
+ final int resolveInfosSize = resolveInfos.size();
+
+ for (int i = 0; i < resolveInfosSize; i++) {
+ ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+ if (systemAppsOnly && !serviceInfo.applicationInfo.isSystemApp()) {
+ continue;
+ }
+ if (!result.contains(serviceInfo.packageName)) {
+ result.add(serviceInfo.packageName);
+ }
+ }
+ }
+
+ if (requiredForSystemUser) {
+ final int allAppsSize = mAllApps.size();
+ for (int i = 0; i < allAppsSize; i++) {
+ final ApplicationInfo appInfo = mAllApps.get(i);
+ if (systemAppsOnly && !appInfo.isSystemApp()) {
+ continue;
+ }
+ if (appInfo.isRequiredForSystemUser()) {
+ result.add(appInfo.packageName);
+ }
+ }
+ }
+ return result;
+ }
+
+ @VisibleForTesting
+ @SuppressWarnings("unchecked")
+ protected List<ApplicationInfo> getAllApps(int userId) {
+ try {
+ return mPackageManager.getInstalledApplications(
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS, userId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @VisibleForTesting
+ protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int userId) {
+ try {
+ return mPackageManager.queryIntentActivities(intent, null,
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ userId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @VisibleForTesting
+ protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
+ try {
+ return mPackageManager.queryIntentServices(intent, null,
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
+ .getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @VisibleForTesting
+ @SuppressWarnings("unchecked")
+ protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+ try {
+ return mPackageManager.getPackagesHoldingPermissions(new String[]{perm}, 0,
+ userId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/content/pm/Attribution.java b/android/content/pm/Attribution.java
new file mode 100644
index 0000000..989a5b9
--- /dev/null
+++ b/android/content/pm/Attribution.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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.IdRes;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information about an attribution declared by a package. This corresponds to the information
+ * collected from the AndroidManifest.xml's <attribution> tags.
+ */
+@DataClass(genHiddenConstructor = true)
+public final class Attribution implements Parcelable {
+
+ /**
+ * The tag of this attribution. From the <manifest> tag's "tag" attribute
+ */
+ private @NonNull String mTag;
+
+ /**
+ * The resource ID of the label of the attribution From the <manifest> tag's "label"
+ * attribute
+ */
+ private final @IdRes int mLabel;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/Attribution.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new Attribution.
+ *
+ * @param tag
+ * The tag of this attribution. From the <manifest> tag's "tag" attribute
+ * @param label
+ * The resource ID of the label of the attribution From the <manifest> tag's "label"
+ * attribute
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public Attribution(
+ @NonNull String tag,
+ @IdRes int label) {
+ this.mTag = tag;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTag);
+ this.mLabel = label;
+ com.android.internal.util.AnnotationValidations.validate(
+ IdRes.class, null, mLabel);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The tag of this attribution. From the <manifest> tag's "tag" attribute
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getTag() {
+ return mTag;
+ }
+
+ /**
+ * The resource ID of the label of the attribution From the <manifest> tag's "label"
+ * attribute
+ */
+ @DataClass.Generated.Member
+ public @IdRes int getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(mTag);
+ dest.writeInt(mLabel);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ Attribution(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String tag = in.readString();
+ int label = in.readInt();
+
+ this.mTag = tag;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTag);
+ this.mLabel = label;
+ com.android.internal.util.AnnotationValidations.validate(
+ IdRes.class, null, mLabel);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<Attribution> CREATOR
+ = new Parcelable.Creator<Attribution>() {
+ @Override
+ public Attribution[] newArray(int size) {
+ return new Attribution[size];
+ }
+
+ @Override
+ public Attribution createFromParcel(@NonNull Parcel in) {
+ return new Attribution(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1608139558081L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/Attribution.java",
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mTag\nprivate final @android.annotation.IdRes int mLabel\nclass Attribution extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/AuxiliaryResolveInfo.java b/android/content/pm/AuxiliaryResolveInfo.java
new file mode 100644
index 0000000..7d07e1d
--- /dev/null
+++ b/android/content/pm/AuxiliaryResolveInfo.java
@@ -0,0 +1,134 @@
+/*
+ * 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.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.
+ * <p>
+ * Used when resolution occurs, but, the target is not actually on the device.
+ * This happens resolving instant apps that haven't been installed yet or if
+ * the application consists of multiple feature splits and the needed split
+ * hasn't been installed.
+ * @hide
+ */
+public final class AuxiliaryResolveInfo {
+ /** The activity to launch if there's an installation failure. */
+ public final ComponentName installFailureActivity;
+ /** 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;
+ /** An intent to start upon failure to install */
+ public final Intent failureIntent;
+ /** The matching filters for this resolve info. */
+ public final List<AuxiliaryFilter> filters;
+ /** Stored {@link InstantAppRequest#hostDigestPrefixSecure} to prevent re-generation */
+ public final int[] hostDigestPrefixSecure;
+
+ /** Create a response for installing an instant application. */
+ public AuxiliaryResolveInfo(@NonNull String token,
+ boolean needsPhase2,
+ @Nullable Intent failureIntent,
+ @Nullable List<AuxiliaryFilter> filters,
+ @Nullable int[] hostDigestPrefix) {
+ this.token = token;
+ this.needsPhaseTwo = needsPhase2;
+ this.failureIntent = failureIntent;
+ this.filters = filters;
+ this.installFailureActivity = null;
+ this.hostDigestPrefixSecure = hostDigestPrefix;
+ }
+
+ /** Create a response for installing a split on demand. */
+ public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity,
+ @Nullable Intent failureIntent,
+ @Nullable List<AuxiliaryFilter> filters) {
+ super();
+ this.installFailureActivity = failureActivity;
+ this.filters = filters;
+ this.token = null;
+ this.needsPhaseTwo = false;
+ this.failureIntent = failureIntent;
+ this.hostDigestPrefixSecure = null;
+ }
+
+ /** 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 + '\'' + '}';
+ }
+ }
+}
diff --git a/android/content/pm/BaseParceledListSlice.java b/android/content/pm/BaseParceledListSlice.java
new file mode 100644
index 0000000..7bade74
--- /dev/null
+++ b/android/content/pm/BaseParceledListSlice.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2011 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * Caveat: for efficiency and security, all elements must be the same concrete type.
+ * In order to avoid writing the class name of each object, we must ensure that
+ * each object is the same type, or else unparceling then reparceling the data may yield
+ * a different result if the class name encoded in the Parcelable is a Base type.
+ * See b/17671747.
+ *
+ * @hide
+ */
+abstract class BaseParceledListSlice<T> implements Parcelable {
+ private static String TAG = "ParceledListSlice";
+ private static boolean DEBUG = false;
+
+ /*
+ * TODO get this number from somewhere else. For now set it to a quarter of
+ * the 1MB limit.
+ */
+ private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+
+ private final List<T> mList;
+
+ private int mInlineCountLimit = Integer.MAX_VALUE;
+
+ public BaseParceledListSlice(List<T> list) {
+ mList = list;
+ }
+
+ @SuppressWarnings("unchecked")
+ BaseParceledListSlice(Parcel p, ClassLoader loader) {
+ final int N = p.readInt();
+ mList = new ArrayList<T>(N);
+ if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+ if (N <= 0) {
+ return;
+ }
+
+ Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
+ Class<?> listElementClass = null;
+
+ int i = 0;
+ while (i < N) {
+ if (p.readInt() == 0) {
+ break;
+ }
+ listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass);
+ if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ if (i >= N) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < N) {
+ if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
+ return;
+ }
+ while (i < N && reply.readInt() != 0) {
+ listElementClass = readVerifyAndAddElement(creator, reply, loader,
+ listElementClass);
+ if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p,
+ ClassLoader loader, Class<?> listElementClass) {
+ final T parcelable = readCreator(creator, p, loader);
+ if (listElementClass == null) {
+ listElementClass = parcelable.getClass();
+ } else {
+ verifySameType(listElementClass, parcelable.getClass());
+ }
+ mList.add(parcelable);
+ return listElementClass;
+ }
+
+ private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+ (Parcelable.ClassLoaderCreator<?>) creator;
+ return (T) classLoaderCreator.createFromParcel(p, loader);
+ }
+ return (T) creator.createFromParcel(p);
+ }
+
+ private static void verifySameType(final Class<?> expected, final Class<?> actual) {
+ if (!actual.equals(expected)) {
+ throw new IllegalArgumentException("Can't unparcel type "
+ + (actual == null ? null : actual.getName()) + " in list of type "
+ + (expected == null ? null : expected.getName()));
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public List<T> getList() {
+ return mList;
+ }
+
+ /**
+ * Set a limit on the maximum number of entries in the array that will be included
+ * inline in the initial parcelling of this object.
+ */
+ public void setInlineCountLimit(int maxCount) {
+ mInlineCountLimit = maxCount;
+ }
+
+ /**
+ * Write this to another Parcel. Note that this discards the internal Parcel
+ * and should not be used anymore. This is so we can pass this to a Binder
+ * where we won't have a chance to call recycle on this.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int N = mList.size();
+ final int callFlags = flags;
+ dest.writeInt(N);
+ if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+ if (N > 0) {
+ final Class<?> listElementClass = mList.get(0).getClass();
+ writeParcelableCreator(mList.get(0), dest);
+ int i = 0;
+ while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
+ dest.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ writeElement(parcelable, dest, callFlags);
+
+ if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+ while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ writeElement(parcelable, reply, callFlags);
+
+ if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+ dest.writeStrongBinder(retriever);
+ }
+ }
+ }
+
+ protected abstract void writeElement(T parcelable, Parcel reply, int callFlags);
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ protected abstract void writeParcelableCreator(T parcelable, Parcel dest);
+
+ protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
+}
diff --git a/android/content/pm/ChangedPackages.java b/android/content/pm/ChangedPackages.java
new file mode 100644
index 0000000..950a29a
--- /dev/null
+++ b/android/content/pm/ChangedPackages.java
@@ -0,0 +1,84 @@
+/**
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Packages that have been changed since the last time they
+ * were requested.
+ * @see PackageManager#getChangedPackages(int)
+ */
+public final class ChangedPackages implements Parcelable {
+ /** The last known sequence number for these changes */
+ private final int mSequenceNumber;
+ /** The names of the packages that have changed */
+ private final List<String> mPackageNames;
+
+ public ChangedPackages(int sequenceNumber, @NonNull List<String> packageNames) {
+ this.mSequenceNumber = sequenceNumber;
+ this.mPackageNames = packageNames;
+ }
+
+ /** @hide */
+ protected ChangedPackages(Parcel in) {
+ mSequenceNumber = in.readInt();
+ mPackageNames = in.createStringArrayList();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSequenceNumber);
+ dest.writeStringList(mPackageNames);
+ }
+
+ /**
+ * Returns the last known sequence number for these changes.
+ */
+ public int getSequenceNumber() {
+ return mSequenceNumber;
+ }
+
+ /**
+ * Returns the names of the packages that have changed.
+ */
+ public @NonNull List<String> getPackageNames() {
+ return mPackageNames;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ChangedPackages> CREATOR =
+ new Parcelable.Creator<ChangedPackages>() {
+ public ChangedPackages createFromParcel(Parcel in) {
+ return new ChangedPackages(in);
+ }
+
+ public ChangedPackages[] newArray(int size) {
+ return new ChangedPackages[size];
+ }
+ };
+}
diff --git a/android/content/pm/Checksum.java b/android/content/pm/Checksum.java
new file mode 100644
index 0000000..ff17496
--- /dev/null
+++ b/android/content/pm/Checksum.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 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.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A typed checksum.
+ *
+ * @see ApkChecksum
+ * @see PackageManager#requestChecksums
+ */
+@DataClass(genConstDefs = false)
+public final class Checksum implements Parcelable {
+ /**
+ * Root SHA256 hash of a 4K Merkle tree computed over all file bytes.
+ * <a href="https://source.android.com/security/apksigning/v4">See APK Signature Scheme V4</a>.
+ * <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst">See fs-verity</a>.
+ *
+ * Recommended for all new applications.
+ * Can be used by kernel to enforce authenticity and integrity of the APK.
+ * <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst#">See fs-verity for details</a>
+ *
+ * @see PackageManager#requestChecksums
+ */
+ public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
+
+ /**
+ * MD5 hash computed over all file bytes.
+ *
+ * @see PackageManager#requestChecksums
+ * @deprecated Not platform enforced. Cryptographically broken and unsuitable for further use.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
+ */
+ @Deprecated
+ public static final int TYPE_WHOLE_MD5 = 0x00000002;
+
+ /**
+ * SHA1 hash computed over all file bytes.
+ *
+ * @see PackageManager#requestChecksums
+ * @deprecated Not platform enforced. Broken and should not be used.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
+ */
+ @Deprecated
+ public static final int TYPE_WHOLE_SHA1 = 0x00000004;
+
+ /**
+ * SHA256 hash computed over all file bytes.
+ * @deprecated Not platform enforced.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
+ *
+ * @see PackageManager#requestChecksums
+ */
+ @Deprecated
+ public static final int TYPE_WHOLE_SHA256 = 0x00000008;
+
+ /**
+ * SHA512 hash computed over all file bytes.
+ * @deprecated Not platform enforced.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
+ *
+ * @see PackageManager#requestChecksums
+ */
+ @Deprecated
+ public static final int TYPE_WHOLE_SHA512 = 0x00000010;
+
+ /**
+ * Root SHA256 hash of a 1M Merkle tree computed over protected content.
+ * Excludes signing block.
+ * <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
+ *
+ * @see PackageManager#requestChecksums
+ */
+ public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
+
+ /**
+ * Root SHA512 hash of a 1M Merkle tree computed over protected content.
+ * Excludes signing block.
+ * <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
+ *
+ * @see PackageManager#requestChecksums
+ */
+ public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
+
+ /** @hide */
+ @IntDef(prefix = {"TYPE_"}, value = {
+ TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
+ TYPE_WHOLE_MD5,
+ TYPE_WHOLE_SHA1,
+ TYPE_WHOLE_SHA256,
+ TYPE_WHOLE_SHA512,
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"TYPE_"}, value = {
+ TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
+ TYPE_WHOLE_MD5,
+ TYPE_WHOLE_SHA1,
+ TYPE_WHOLE_SHA256,
+ TYPE_WHOLE_SHA512,
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TypeMask {}
+
+ /**
+ * Serialize checksum to the stream in binary format.
+ * @hide
+ */
+ public static void writeToStream(@NonNull DataOutputStream dos, @NonNull Checksum checksum)
+ throws IOException {
+ dos.writeInt(checksum.getType());
+
+ final byte[] valueBytes = checksum.getValue();
+ dos.writeInt(valueBytes.length);
+ dos.write(valueBytes);
+ }
+
+ /**
+ * Deserialize checksum previously stored in
+ * {@link #writeToStream(DataOutputStream, Checksum)}.
+ * @hide
+ */
+ public static @NonNull Checksum readFromStream(@NonNull DataInputStream dis)
+ throws IOException {
+ final int type = dis.readInt();
+
+ final byte[] valueBytes = new byte[dis.readInt()];
+ dis.read(valueBytes);
+ return new Checksum(type, valueBytes);
+ }
+
+ /**
+ * Checksum type.
+ */
+ private final @Checksum.Type int mType;
+ /**
+ * Checksum value.
+ */
+ private final @NonNull byte[] mValue;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/Checksum.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new Checksum.
+ *
+ * @param type
+ * Checksum type.
+ * @param value
+ * Checksum value.
+ */
+ @DataClass.Generated.Member
+ public Checksum(
+ @Checksum.Type int type,
+ @NonNull byte[] value) {
+ this.mType = type;
+ com.android.internal.util.AnnotationValidations.validate(
+ Checksum.Type.class, null, mType);
+ this.mValue = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mValue);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Checksum type.
+ */
+ @DataClass.Generated.Member
+ public @Checksum.Type int getType() {
+ return mType;
+ }
+
+ /**
+ * Checksum value.
+ */
+ @DataClass.Generated.Member
+ public @NonNull byte[] getValue() {
+ return mValue;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mType);
+ dest.writeByteArray(mValue);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ Checksum(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int type = in.readInt();
+ byte[] value = in.createByteArray();
+
+ this.mType = type;
+ com.android.internal.util.AnnotationValidations.validate(
+ Checksum.Type.class, null, mType);
+ this.mValue = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mValue);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<Checksum> CREATOR
+ = new Parcelable.Creator<Checksum>() {
+ @Override
+ public Checksum[] newArray(int size) {
+ return new Checksum[size];
+ }
+
+ @Override
+ public Checksum createFromParcel(@NonNull Parcel in) {
+ return new Checksum(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1619810358402L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/Checksum.java",
+ inputSignatures = "public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_MD5\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA1\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA512\npublic static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256\npublic static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512\nprivate final @android.content.pm.Checksum.Type int mType\nprivate final @android.annotation.NonNull byte[] mValue\npublic static void writeToStream(java.io.DataOutputStream,android.content.pm.Checksum)\npublic static @android.annotation.NonNull android.content.pm.Checksum readFromStream(java.io.DataInputStream)\nclass Checksum extends java.lang.Object implements [android.os.Parcelable]\[email protected](genConstDefs=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/ComponentInfo.java b/android/content/pm/ComponentInfo.java
new file mode 100644
index 0000000..42847c8
--- /dev/null
+++ b/android/content/pm/ComponentInfo.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2008 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.SuppressLint;
+import android.app.Activity;
+import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.util.Printer;
+
+/**
+ * Base class containing information common to all application components
+ * ({@link ActivityInfo}, {@link ServiceInfo}). This class is not intended
+ * to be used by itself; it is simply here to share common definitions
+ * between all application components. As such, it does not itself
+ * implement Parcelable, but does provide convenience methods to assist
+ * in the implementation of Parcelable in subclasses.
+ */
+public class ComponentInfo extends PackageItemInfo {
+ /**
+ * Global information about the application/package this component is a
+ * part of.
+ */
+ public ApplicationInfo applicationInfo;
+
+ /**
+ * The name of the process this component should run in.
+ * From the "android:process" attribute or, if not set, the same
+ * as <var>applicationInfo.processName</var>.
+ */
+ public String processName;
+
+ /**
+ * The name of the split in which this component is declared.
+ * Null if the component was declared in the base APK.
+ */
+ public String splitName;
+
+ /**
+ * Set of attribution tags that should be automatically applied to this
+ * component.
+ * <p>
+ * When this component represents an {@link Activity}, {@link Service},
+ * {@link ContentResolver} or {@link BroadcastReceiver}, each instance will
+ * be automatically configured with {@link Context#createAttributionContext}
+ * using the first attribution tag contained here.
+ * <p>
+ * Additionally, when this component represents a {@link BroadcastReceiver}
+ * and the sender of a broadcast requires the receiver to hold one or more
+ * specific permissions, those permission checks will be performed using
+ * each of the attributions tags contained here.
+ *
+ * @see Context#createAttributionContext(String)
+ */
+ @SuppressLint({"MissingNullability", "MutableBareField"})
+ public String[] attributionTags;
+
+ /**
+ * A string resource identifier (in the package's resources) containing
+ * a user-readable description of the component. From the "description"
+ * attribute or, if not set, 0.
+ */
+ public int descriptionRes;
+
+ /**
+ * Indicates whether or not this component may be instantiated. Note that this value can be
+ * overridden by the one in its parent {@link ApplicationInfo}.
+ */
+ public boolean enabled = true;
+
+ /**
+ * Set to true if this component is available for use by other applications.
+ * Comes from {@link android.R.attr#exported android:exported} of the
+ * <activity>, <receiver>, <service>, or
+ * <provider> tag.
+ */
+ public boolean exported = false;
+
+ /**
+ * Indicates if this component is aware of direct boot lifecycle, and can be
+ * safely run before the user has entered their credentials (such as a lock
+ * pattern or PIN).
+ */
+ public boolean directBootAware = false;
+
+ public ComponentInfo() {
+ }
+
+ public ComponentInfo(ComponentInfo orig) {
+ super(orig);
+ applicationInfo = orig.applicationInfo;
+ processName = orig.processName;
+ splitName = orig.splitName;
+ attributionTags = orig.attributionTags;
+ descriptionRes = orig.descriptionRes;
+ enabled = orig.enabled;
+ exported = orig.exported;
+ directBootAware = orig.directBootAware;
+ }
+
+ /** @hide */
+ @Override public CharSequence loadUnsafeLabel(PackageManager pm) {
+ if (nonLocalizedLabel != null) {
+ return nonLocalizedLabel;
+ }
+ ApplicationInfo ai = applicationInfo;
+ CharSequence label;
+ if (labelRes != 0) {
+ label = pm.getText(packageName, labelRes, ai);
+ if (label != null) {
+ return label;
+ }
+ }
+ if (ai.nonLocalizedLabel != null) {
+ return ai.nonLocalizedLabel;
+ }
+ if (ai.labelRes != 0) {
+ label = pm.getText(packageName, ai.labelRes, ai);
+ if (label != null) {
+ return label;
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Return whether this component and its enclosing application are enabled.
+ */
+ public boolean isEnabled() {
+ return enabled && applicationInfo.enabled;
+ }
+
+ /**
+ * Return the icon resource identifier to use for this component. If
+ * the component defines an icon, that is used; else, the application
+ * icon is used.
+ *
+ * @return The icon associated with this component.
+ */
+ public final int getIconResource() {
+ return icon != 0 ? icon : applicationInfo.icon;
+ }
+
+ /**
+ * Return the logo resource identifier to use for this component. If
+ * the component defines a logo, that is used; else, the application
+ * logo is used.
+ *
+ * @return The logo associated with this component.
+ */
+ public final int getLogoResource() {
+ return logo != 0 ? logo : applicationInfo.logo;
+ }
+
+ /**
+ * Return the banner resource identifier to use for this component. If the
+ * component defines a banner, that is used; else, the application banner is
+ * used.
+ *
+ * @return The banner associated with this component.
+ */
+ public final int getBannerResource() {
+ return banner != 0 ? banner : applicationInfo.banner;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ComponentName getComponentName() {
+ return new ComponentName(packageName, name);
+ }
+
+ protected void dumpFront(Printer pw, String prefix) {
+ super.dumpFront(pw, prefix);
+ if (processName != null && !packageName.equals(processName)) {
+ pw.println(prefix + "processName=" + processName);
+ }
+ if (splitName != null) {
+ pw.println(prefix + "splitName=" + splitName);
+ }
+ if (attributionTags != null && attributionTags.length > 0) {
+ StringBuilder tags = new StringBuilder();
+ tags.append(attributionTags[0]);
+ for (int i = 1; i < attributionTags.length; i++) {
+ tags.append(", ");
+ tags.append(attributionTags[i]);
+ }
+ pw.println(prefix + "attributionTags=[" + tags + "]");
+ }
+ pw.println(prefix + "enabled=" + enabled + " exported=" + exported
+ + " directBootAware=" + directBootAware);
+ if (descriptionRes != 0) {
+ pw.println(prefix + "description=" + descriptionRes);
+ }
+ }
+
+ protected void dumpBack(Printer pw, String prefix) {
+ dumpBack(pw, prefix, DUMP_FLAG_ALL);
+ }
+
+ void dumpBack(Printer pw, String prefix, int dumpFlags) {
+ if ((dumpFlags & DUMP_FLAG_APPLICATION) != 0) {
+ if (applicationInfo != null) {
+ pw.println(prefix + "ApplicationInfo:");
+ applicationInfo.dump(pw, prefix + " ", dumpFlags);
+ } else {
+ pw.println(prefix + "ApplicationInfo: null");
+ }
+ }
+ super.dumpBack(pw, prefix);
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ applicationInfo.writeToParcel(dest, parcelableFlags);
+ dest.writeString8(processName);
+ dest.writeString8(splitName);
+ dest.writeString8Array(attributionTags);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(exported ? 1 : 0);
+ dest.writeInt(directBootAware ? 1 : 0);
+ }
+
+ protected ComponentInfo(Parcel source) {
+ super(source);
+ applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+ processName = source.readString8();
+ splitName = source.readString8();
+ attributionTags = source.createString8Array();
+ descriptionRes = source.readInt();
+ enabled = (source.readInt() != 0);
+ exported = (source.readInt() != 0);
+ directBootAware = (source.readInt() != 0);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public Drawable loadDefaultIcon(PackageManager pm) {
+ return applicationInfo.loadIcon(pm);
+ }
+
+ /**
+ * @hide
+ */
+ @Override protected Drawable loadDefaultBanner(PackageManager pm) {
+ return applicationInfo.loadBanner(pm);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected Drawable loadDefaultLogo(PackageManager pm) {
+ return applicationInfo.loadLogo(pm);
+ }
+
+ /**
+ * @hide
+ */
+ @Override protected ApplicationInfo getApplicationInfo() {
+ return applicationInfo;
+ }
+}
diff --git a/android/content/pm/ConfigurationInfo.java b/android/content/pm/ConfigurationInfo.java
new file mode 100644
index 0000000..20494e9
--- /dev/null
+++ b/android/content/pm/ConfigurationInfo.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2008 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information you can retrieve about hardware configuration preferences
+ * declared by an application. This corresponds to information collected from the
+ * AndroidManifest.xml's <uses-configuration> and <uses-feature> tags.
+ */
+public class ConfigurationInfo implements Parcelable {
+ /**
+ * The kind of touch screen attached to the device.
+ * One of: {@link android.content.res.Configuration#TOUCHSCREEN_NOTOUCH},
+ * {@link android.content.res.Configuration#TOUCHSCREEN_STYLUS},
+ * {@link android.content.res.Configuration#TOUCHSCREEN_FINGER}.
+ */
+ public int reqTouchScreen;
+
+ /**
+ * Application's input method preference.
+ * One of: {@link android.content.res.Configuration#KEYBOARD_UNDEFINED},
+ * {@link android.content.res.Configuration#KEYBOARD_NOKEYS},
+ * {@link android.content.res.Configuration#KEYBOARD_QWERTY},
+ * {@link android.content.res.Configuration#KEYBOARD_12KEY}
+ */
+ public int reqKeyboardType;
+
+ /**
+ * A flag indicating whether any keyboard is available.
+ * one of: {@link android.content.res.Configuration#NAVIGATION_UNDEFINED},
+ * {@link android.content.res.Configuration#NAVIGATION_DPAD},
+ * {@link android.content.res.Configuration#NAVIGATION_TRACKBALL},
+ * {@link android.content.res.Configuration#NAVIGATION_WHEEL}
+ */
+ public int reqNavigation;
+
+ /**
+ * Value for {@link #reqInputFeatures}: if set, indicates that the application
+ * requires a hard keyboard
+ */
+ public static final int INPUT_FEATURE_HARD_KEYBOARD = 0x00000001;
+
+ /**
+ * Value for {@link #reqInputFeatures}: if set, indicates that the application
+ * requires a five way navigation device
+ */
+ public static final int INPUT_FEATURE_FIVE_WAY_NAV = 0x00000002;
+
+ /**
+ * Flags associated with the input features. Any combination of
+ * {@link #INPUT_FEATURE_HARD_KEYBOARD},
+ * {@link #INPUT_FEATURE_FIVE_WAY_NAV}
+ */
+ public int reqInputFeatures = 0;
+
+ /**
+ * Default value for {@link #reqGlEsVersion};
+ */
+ public static final int GL_ES_VERSION_UNDEFINED = 0;
+ /**
+ * The GLES version used by an application. The upper order 16 bits represent the
+ * major version and the lower order 16 bits the minor version.
+ */
+ public int reqGlEsVersion;
+
+ public ConfigurationInfo() {
+ }
+
+ public ConfigurationInfo(ConfigurationInfo orig) {
+ reqTouchScreen = orig.reqTouchScreen;
+ reqKeyboardType = orig.reqKeyboardType;
+ reqNavigation = orig.reqNavigation;
+ reqInputFeatures = orig.reqInputFeatures;
+ reqGlEsVersion = orig.reqGlEsVersion;
+ }
+
+ public String toString() {
+ return "ConfigurationInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " touchscreen = " + reqTouchScreen
+ + " inputMethod = " + reqKeyboardType
+ + " navigation = " + reqNavigation
+ + " reqInputFeatures = " + reqInputFeatures
+ + " reqGlEsVersion = " + reqGlEsVersion + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(reqTouchScreen);
+ dest.writeInt(reqKeyboardType);
+ dest.writeInt(reqNavigation);
+ dest.writeInt(reqInputFeatures);
+ dest.writeInt(reqGlEsVersion);
+ }
+
+ public static final @android.annotation.NonNull Creator<ConfigurationInfo> CREATOR =
+ new Creator<ConfigurationInfo>() {
+ public ConfigurationInfo createFromParcel(Parcel source) {
+ return new ConfigurationInfo(source);
+ }
+ public ConfigurationInfo[] newArray(int size) {
+ return new ConfigurationInfo[size];
+ }
+ };
+
+ private ConfigurationInfo(Parcel source) {
+ reqTouchScreen = source.readInt();
+ reqKeyboardType = source.readInt();
+ reqNavigation = source.readInt();
+ reqInputFeatures = source.readInt();
+ reqGlEsVersion = source.readInt();
+ }
+
+ /**
+ * This method extracts the major and minor version of reqGLEsVersion attribute
+ * and returns it as a string. Say reqGlEsVersion value of 0x00010002 is returned
+ * as 1.2
+ * @return String representation of the reqGlEsVersion attribute
+ */
+ public String getGlEsVersion() {
+ int major = ((reqGlEsVersion & 0xffff0000) >> 16);
+ int minor = reqGlEsVersion & 0x0000ffff;
+ return String.valueOf(major)+"."+String.valueOf(minor);
+ }
+}
diff --git a/android/content/pm/ConstrainDisplayApisConfig.java b/android/content/pm/ConstrainDisplayApisConfig.java
new file mode 100644
index 0000000..11ba3d4
--- /dev/null
+++ b/android/content/pm/ConstrainDisplayApisConfig.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2021 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.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class for processing flags in the Device Config namespace 'constrain_display_apis'.
+ *
+ * @hide
+ */
+public final class ConstrainDisplayApisConfig {
+ private static final String TAG = ConstrainDisplayApisConfig.class.getSimpleName();
+
+ /**
+ * A string flag whose value holds a comma separated list of package entries in the format
+ * '<package-name>:<min-version-code>?:<max-version-code>?' for which Display APIs should never
+ * be constrained.
+ */
+ private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS = "never_constrain_display_apis";
+
+ /**
+ * A boolean flag indicating whether Display APIs should never be constrained for all
+ * packages. If true, {@link #FLAG_NEVER_CONSTRAIN_DISPLAY_APIS} is ignored.
+ */
+ private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES =
+ "never_constrain_display_apis_all_packages";
+
+ /**
+ * A string flag whose value holds a comma separated list of package entries in the format
+ * '<package-name>:<min-version-code>?:<max-version-code>?' for which Display APIs should
+ * always be constrained.
+ */
+ private static final String FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS =
+ "always_constrain_display_apis";
+
+ /**
+ * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the
+ * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code
+ * applicationInfo}.
+ *
+ * @param applicationInfo Information about the application/package.
+ */
+ public static boolean neverConstrainDisplayApis(ApplicationInfo applicationInfo) {
+ if (DeviceConfig.getBoolean(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+ FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false)) {
+ return true;
+ }
+
+ return flagHasMatchingPackageEntry(FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, applicationInfo);
+ }
+
+ /**
+ * Returns true if the flag 'always_constrain_display_apis' contains a package entry that
+ * matches the given {@code applicationInfo}.
+ *
+ * @param applicationInfo Information about the application/package.
+ */
+ public static boolean alwaysConstrainDisplayApis(ApplicationInfo applicationInfo) {
+ return flagHasMatchingPackageEntry(FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, applicationInfo);
+ }
+
+ /**
+ * Returns true if the flag with the given {@code flagName} contains a package entry that
+ * matches the given {@code applicationInfo}.
+ *
+ * @param applicationInfo Information about the application/package.
+ */
+ private static boolean flagHasMatchingPackageEntry(String flagName,
+ ApplicationInfo applicationInfo) {
+ String configStr = DeviceConfig.getString(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+ flagName, /* defaultValue= */ "");
+
+ // String#split returns a non-empty array given an empty string.
+ if (configStr.isEmpty()) {
+ return false;
+ }
+
+ for (String packageEntryString : configStr.split(",")) {
+ if (matchesApplicationInfo(packageEntryString, applicationInfo)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses the given {@code packageEntryString} and returns true if {@code
+ * applicationInfo.packageName} matches the package name in the config and {@code
+ * applicationInfo.longVersionCode} is within the version range in the config.
+ *
+ * <p>Logs a warning and returns false in case the given {@code packageEntryString} is invalid.
+ *
+ * @param packageEntryStr A package entry expected to be in the format
+ * '<package-name>:<min-version-code>?:<max-version-code>?'.
+ * @param applicationInfo Information about the application/package.
+ */
+ private static boolean matchesApplicationInfo(String packageEntryStr,
+ ApplicationInfo applicationInfo) {
+ List<String> packageAndVersions = Arrays.asList(packageEntryStr.split(":", 3));
+ if (packageAndVersions.size() != 3) {
+ Slog.w(TAG, "Invalid package entry in flag 'never_constrain_display_apis': "
+ + packageEntryStr);
+ return false;
+ }
+ String packageName = packageAndVersions.get(0);
+ String minVersionCodeStr = packageAndVersions.get(1);
+ String maxVersionCodeStr = packageAndVersions.get(2);
+
+ if (!packageName.equals(applicationInfo.packageName)) {
+ return false;
+ }
+ long version = applicationInfo.longVersionCode;
+ try {
+ if (!minVersionCodeStr.isEmpty() && version < Long.parseLong(minVersionCodeStr)) {
+ return false;
+ }
+ if (!maxVersionCodeStr.isEmpty() && version > Long.parseLong(maxVersionCodeStr)) {
+ return false;
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryStr);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/android/content/pm/CrossProfileApps.java b/android/content/pm/CrossProfileApps.java
new file mode 100644
index 0000000..48b634e
--- /dev/null
+++ b/android/content/pm/CrossProfileApps.java
@@ -0,0 +1,527 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Activity;
+import android.app.AppOpsManager.Mode;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.internal.R;
+import com.android.internal.util.UserIcons;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Class for handling cross profile operations. Apps can use this class to interact with its
+ * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can
+ * use this class to start its main activity in managed profile.
+ */
+public class CrossProfileApps {
+
+ /**
+ * Broadcast signalling that the receiving app's permission to interact across profiles has
+ * changed. This includes the user, admin, or OEM changing their consent such that the
+ * permission for the app to interact across profiles has changed.
+ *
+ * <p>This broadcast is not sent when other circumstances result in a change to being able to
+ * interact across profiles in practice, such as the profile being turned off or removed, apps
+ * being uninstalled, etc. The methods {@link #canInteractAcrossProfiles()} and {@link
+ * #canRequestInteractAcrossProfiles()} can be used by apps prior to attempting to interact
+ * across profiles or attempting to request user consent to interact across profiles.
+ *
+ * <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true}
+ * can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be
+ * received by dynamically-registered broadcast receivers.
+ */
+ public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED =
+ "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED";
+
+ private final Context mContext;
+ private final ICrossProfileApps mService;
+ private final UserManager mUserManager;
+ private final Resources mResources;
+
+ /** @hide */
+ public CrossProfileApps(Context context, ICrossProfileApps service) {
+ mContext = context;
+ mService = service;
+ mUserManager = context.getSystemService(UserManager.class);
+ mResources = context.getResources();
+ }
+
+ /**
+ * Starts the specified main activity of the caller package in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch, it must be exported and has
+ * action {@link android.content.Intent#ACTION_MAIN}, category
+ * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
+ * be thrown.
+ * @param targetUser The UserHandle of the profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ */
+ public void startMainActivity(@NonNull ComponentName component,
+ @NonNull UserHandle targetUser) {
+ try {
+ mService.startActivityAsUser(
+ mContext.getIApplicationThread(),
+ mContext.getPackageName(),
+ mContext.getAttributionTag(),
+ component,
+ targetUser.getIdentifier(),
+ true);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts the specified activity of the caller package in the specified profile.
+ *
+ * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES},
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}, or {@code
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. Both the caller and
+ * target user profiles must be in the same profile group. The target user must be a valid user
+ * returned from {@link #getTargetUserProfiles()}.
+ *
+ * @param intent The intent to launch. A component in the caller package must be specified.
+ * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by
+ * {@link #getTargetUserProfiles()} if different to the calling user, otherwise a
+ * {@link SecurityException} will be thrown.
+ * @param callingActivity The activity to start the new activity from for the purposes of
+ * passing back any result and deciding which task the new activity should belong to. If
+ * {@code null}, the activity will always be started in a new task and no result will be
+ * returned.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public void startActivity(
+ @NonNull Intent intent,
+ @NonNull UserHandle targetUser,
+ @Nullable Activity callingActivity) {
+ startActivity(intent, targetUser, callingActivity, /* options= */ null);
+ }
+
+ /**
+ * Starts the specified activity of the caller package in the specified profile.
+ *
+ * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES},
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}, or {@code
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. Both the caller and
+ * target user profiles must be in the same profile group. The target user must be a valid user
+ * returned from {@link #getTargetUserProfiles()}.
+ *
+ * @param intent The intent to launch. A component in the caller package must be specified.
+ * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by
+ * {@link #getTargetUserProfiles()} if different to the calling user, otherwise a
+ * {@link SecurityException} will be thrown.
+ * @param callingActivity The activity to start the new activity from for the purposes of
+ * passing back any result and deciding which task the new activity should belong to. If
+ * {@code null}, the activity will always be started in a new task and no result will be
+ * returned.
+ * @param options The activity options or {@code null}. See {@link android.app.ActivityOptions}.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public void startActivity(
+ @NonNull Intent intent,
+ @NonNull UserHandle targetUser,
+ @Nullable Activity callingActivity,
+ @Nullable Bundle options) {
+ try {
+ mService.startActivityAsUserByIntent(
+ mContext.getIApplicationThread(),
+ mContext.getPackageName(),
+ mContext.getAttributionTag(),
+ intent,
+ targetUser.getIdentifier(),
+ callingActivity != null ? callingActivity.getActivityToken() : null,
+ options);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts the specified activity of the caller package in the specified profile. Unlike
+ * {@link #startMainActivity}, this can start any activity of the caller package, not just
+ * the main activity.
+ * The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+ * permission and both the caller and target user profiles must be in the same profile group.
+ *
+ * @param component The ComponentName of the activity to launch. It must be exported.
+ * @param targetUser The UserHandle of the profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
+ public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
+ try {
+ mService.startActivityAsUser(mContext.getIApplicationThread(),
+ mContext.getPackageName(), mContext.getAttributionTag(), component,
+ targetUser.getIdentifier(), false);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of user profiles that that the caller can use when calling other APIs in this
+ * class.
+ * <p>
+ * A user profile would be considered as a valid target user profile, provided that:
+ * <ul>
+ * <li>It gets caller app installed</li>
+ * <li>It is not equal to the calling user</li>
+ * <li>It is in the same profile group of calling user profile</li>
+ * <li>It is enabled</li>
+ * </ul>
+ *
+ * @see UserManager#getUserProfiles()
+ */
+ public @NonNull List<UserHandle> getTargetUserProfiles() {
+ try {
+ return mService.getTargetUserProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a label that calling app can show to user for the semantic of profile switching --
+ * launching its own activity in specified user profile. For example, it may return
+ * "Switch to work" if the given user handle is the managed profile one.
+ *
+ * @param userHandle The UserHandle of the target profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ * @return a label that calling app can show user for the semantic of launching its own
+ * activity in the specified user profile.
+ *
+ * @see #startMainActivity(ComponentName, UserHandle)
+ */
+ public @NonNull CharSequence getProfileSwitchingLabel(@NonNull UserHandle userHandle) {
+ verifyCanAccessUser(userHandle);
+
+ final int stringRes = mUserManager.isManagedProfile(userHandle.getIdentifier())
+ ? R.string.managed_profile_label
+ : R.string.user_owner_label;
+ return mResources.getString(stringRes);
+ }
+
+ /**
+ * Return a drawable that calling app can show to user for the semantic of profile switching --
+ * launching its own activity in specified user profile. For example, it may return a briefcase
+ * icon if the given user handle is the managed profile one.
+ *
+ * @param userHandle The UserHandle of the target profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ * @return an icon that calling app can show user for the semantic of launching its own
+ * activity in specified user profile.
+ *
+ * @see #startMainActivity(ComponentName, UserHandle)
+ */
+ public @NonNull Drawable getProfileSwitchingIconDrawable(@NonNull UserHandle userHandle) {
+ verifyCanAccessUser(userHandle);
+
+ final boolean isManagedProfile =
+ mUserManager.isManagedProfile(userHandle.getIdentifier());
+ if (isManagedProfile) {
+ return mResources.getDrawable(R.drawable.ic_corp_badge, null);
+ } else {
+ return UserIcons.getDefaultUserIcon(
+ mResources, UserHandle.USER_SYSTEM, true /* light */);
+ }
+ }
+
+ /**
+ * Returns whether the calling package can request to navigate the user to
+ * the relevant settings page to request user consent to interact across profiles.
+ *
+ * <p>If {@code true}, the navigation intent can be obtained via {@link
+ * #createRequestInteractAcrossProfilesIntent()}. The package can then listen to {@link
+ * #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts.
+ *
+ * <p>Specifically, returns whether the following are all true:
+ * <ul>
+ * <li>{@code UserManager#getEnabledProfileIds(int)} returns at least one other profile for the
+ * calling user.</li>
+ * <li>The calling app has requested
+ * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.</li>
+ * <li>The calling app is not a profile owner within the profile group of the calling user.</li>
+ * </ul>
+ *
+ * <p>Note that in order for the user to be able to grant the consent, the requesting package
+ * must be allowlisted by the admin or the OEM and installed in the other profile. If this is
+ * not the case the user will be shown a message explaining why they can't grant the consent.
+ *
+ * <p>Note that user consent could already be granted if given a return value of {@code true}.
+ * The package's current ability to interact across profiles can be checked with {@link
+ * #canInteractAcrossProfiles()}.
+ *
+ * @return true if the calling package can request to interact across profiles.
+ */
+ public boolean canRequestInteractAcrossProfiles() {
+ try {
+ return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the calling package can interact across profiles.
+
+ * <p>Specifically, returns whether the following are all true:
+ * <ul>
+ * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+ * <li>The user has previously consented to cross-profile communication for the calling
+ * package.</li>
+ * <li>The calling package has either been allowlisted by default by the OEM or has been
+ * explicitly allowlisted by the admin via
+ * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+ * </li>
+ * </ul>
+ *
+ * <p>If {@code false}, the package's current ability to request user consent to interact across
+ * profiles can be checked with {@link #canRequestInteractAcrossProfiles()}. If {@code true},
+ * user consent can be obtained via {@link #createRequestInteractAcrossProfilesIntent()}. The
+ * package can then listen to {@link #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts.
+ *
+ * @return true if the calling package can interact across profiles.
+ * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+ * calling UID.
+ */
+ public boolean canInteractAcrossProfiles() {
+ try {
+ return mService.canInteractAcrossProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+ * the calling app can interact across profiles.
+ *
+ * <p>The method {@link #canRequestInteractAcrossProfiles()} must be returning {@code true}.
+ *
+ * <p>Note that the user may already have given consent and the app may already be able to
+ * interact across profiles, even if {@link #canRequestInteractAcrossProfiles()} is {@code
+ * true}. The current ability to interact across profiles is given by {@link
+ * #canInteractAcrossProfiles()}.
+ *
+ * @return an {@link Intent} to open the settings page that allows the user to decide whether
+ * the app can interact across profiles
+ *
+ * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+ * calling UID, or {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+ */
+ public @NonNull Intent createRequestInteractAcrossProfilesIntent() {
+ if (!canRequestInteractAcrossProfiles()) {
+ throw new SecurityException(
+ "The calling package can not request to interact across profiles.");
+ }
+ final Intent settingsIntent = new Intent();
+ settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+ final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+ settingsIntent.setData(packageUri);
+ return settingsIntent;
+ }
+
+ /**
+ * Sets the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} that is
+ * configurable by users in Settings. This configures it for the profile group of the calling
+ * package.
+ *
+ * <p>Before calling, check {@link #canConfigureInteractAcrossProfiles(String)} and do not call
+ * if it is {@code false}. If presenting a user interface, do not allow the user to configure
+ * the app-op in that case.
+ *
+ * <p>The underlying app-op {@link android.app.AppOpsManager#OP_INTERACT_ACROSS_PROFILES} should
+ * never be set directly. This method ensures that the app-op is kept in sync for the app across
+ * each user in the profile group and that those apps are sent a broadcast when their ability to
+ * interact across profiles changes.
+ *
+ * <p>This method should be used directly whenever a user's action results in a change in an
+ * app's ability to interact across profiles, as defined by the return value of {@link
+ * #canInteractAcrossProfiles()}. This includes user consent changes in Settings or during
+ * provisioning.
+ *
+ * <p>If other changes could have affected the app's ability to interact across profiles, as
+ * defined by the return value of {@link #canInteractAcrossProfiles()}, such as changes to the
+ * admin or OEM consent whitelists, then {@link #resetInteractAcrossProfilesAppOps(Collection,
+ * Set)} should be used.
+ *
+ * <p>If the caller does not have the {@link android.Manifest.permission
+ * #CONFIGURE_INTERACT_ACROSS_PROFILES} permission, then they must have the permissions that
+ * would have been required to call {@link android.app.AppOpsManager#setMode(int, int, String,
+ * int)}, which includes {@link android.Manifest.permission#MANAGE_APP_OPS_MODES}.
+ *
+ * <p>Also requires either {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or {@link
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @hide
+ */
+ @RequiresPermission(
+ allOf={android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) {
+ try {
+ mService.setInteractAcrossProfilesAppOp(packageName, newMode);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the given package can have its ability to interact across profiles configured
+ * by the user. This means that every other condition to interact across profiles has been set.
+ *
+ * <p>This differs from {@link #canRequestInteractAcrossProfiles()} since it will not return
+ * {@code false} simply when the target profile is disabled.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) {
+ try {
+ return mService.canConfigureInteractAcrossProfiles(packageName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if the given package has requested
+ * {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} and the user has at least one
+ * other profile in the same profile group.
+ *
+ * <p>This differs from {@link #canConfigureInteractAcrossProfiles(String)} since it will
+ * not return {@code false} if the app is not allowlisted or not installed in the other profile.
+ *
+ * <p>Note that platform-signed apps that are automatically granted the permission and are not
+ * allowlisted by the OEM will not be included in this list.
+ *
+ * @hide
+ */
+ public boolean canUserAttemptToConfigureInteractAcrossProfiles(String packageName) {
+ try {
+ return mService.canUserAttemptToConfigureInteractAcrossProfiles(packageName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ /**
+ * For each of the packages defined in {@code previousCrossProfilePackages} but not included in
+ * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission
+ * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by
+ * users in Settings, as defined by {@link #canConfigureInteractAcrossProfiles(String)}.
+ *
+ * <p>This method should be used whenever an app's ability to interact across profiles could
+ * have changed as a result of non-user actions, such as changes to admin or OEM consent
+ * whitelists.
+ *
+ * <p>If the caller does not have the {@link android.Manifest.permission
+ * #CONFIGURE_INTERACT_ACROSS_PROFILES} permission, then they must have the permissions that
+ * would have been required to call {@link android.app.AppOpsManager#setMode(int, int, String,
+ * int)}, which includes {@link android.Manifest.permission#MANAGE_APP_OPS_MODES}.
+ *
+ * <p>Also requires either {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or {@link
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @hide
+ */
+ @RequiresPermission(
+ allOf={android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public void resetInteractAcrossProfilesAppOps(
+ @NonNull Collection<String> previousCrossProfilePackages,
+ @NonNull Set<String> newCrossProfilePackages) {
+ if (previousCrossProfilePackages.isEmpty()) {
+ return;
+ }
+ final List<String> unsetCrossProfilePackages =
+ previousCrossProfilePackages.stream()
+ .filter(packageName -> !newCrossProfilePackages.contains(packageName))
+ .collect(Collectors.toList());
+ if (unsetCrossProfilePackages.isEmpty()) {
+ return;
+ }
+ try {
+ mService.resetInteractAcrossProfilesAppOps(unsetCrossProfilePackages);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} back to
+ * its default value for every package on the device.
+ *
+ * <p>This method can be used to ensure that app-op state is not left around on existing users
+ * for previously-configured profiles.
+ *
+ * <p>If the caller does not have the {@link android.Manifest.permission
+ * #CONFIGURE_INTERACT_ACROSS_PROFILES} permission, then they must have the permissions that
+ * would have been required to call {@link android.app.AppOpsManager#setMode(int, int, String,
+ * int)}, which includes {@link android.Manifest.permission#MANAGE_APP_OPS_MODES}.
+ *
+ * <p>Also requires either {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or {@link
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @hide
+ */
+ @RequiresPermission(
+ allOf={android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public void clearInteractAcrossProfilesAppOps() {
+ try {
+ mService.clearInteractAcrossProfilesAppOps();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ private void verifyCanAccessUser(UserHandle userHandle) {
+ if (!getTargetUserProfiles().contains(userHandle)) {
+ throw new SecurityException("Not allowed to access " + userHandle);
+ }
+ }
+}
diff --git a/android/content/pm/CrossProfileAppsInternal.java b/android/content/pm/CrossProfileAppsInternal.java
new file mode 100644
index 0000000..255aeac
--- /dev/null
+++ b/android/content/pm/CrossProfileAppsInternal.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 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.UserIdInt;
+import android.app.AppOpsManager.Mode;
+import android.os.UserHandle;
+
+import java.util.List;
+
+/**
+ * Exposes internal methods from {@link com.android.server.pm.CrossProfileAppsServiceImpl} to other
+ * system server classes.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class CrossProfileAppsInternal {
+ /**
+ * Returns whether the package has the necessary permissions to communicate cross-profile.
+ *
+ * <p>This means having at least one of these conditions:
+ * <ul>
+ * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.
+ * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.
+ * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding
+ * AppOps {@code android:interact_across_profiles} is set to "allow".
+ * </ul>
+ */
+ public abstract boolean verifyPackageHasInteractAcrossProfilePermission(String packageName,
+ @UserIdInt int userId) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Returns whether the package has the necessary permissions to communicate cross-profile.
+ *
+ * <p>This means having at least one of these conditions:
+ * <ul>
+ * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.
+ * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.
+ * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding
+ * AppOps {@code android:interact_across_profiles} is set to "allow".
+ * </ul>
+ */
+ public abstract boolean verifyUidHasInteractAcrossProfilePermission(String packageName,
+ int uid);
+
+ /**
+ * Returns the list of target user profiles for the given package on the given user. See {@link
+ * CrossProfileApps#getTargetUserProfiles()}.
+ */
+ public abstract List<UserHandle> getTargetUserProfiles(
+ String packageName, @UserIdInt int userId);
+
+ /**
+ * Sets the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} that is
+ * configurable by users in Settings. This configures it for the profile group of the given
+ * user.
+ *
+ * @see CrossProfileApps#setInteractAcrossProfilesAppOp(String, int)
+ */
+ public abstract void setInteractAcrossProfilesAppOp(
+ String packageName, @Mode int newMode, @UserIdInt int userId);
+}
diff --git a/android/content/pm/DataLoaderManager.java b/android/content/pm/DataLoaderManager.java
new file mode 100644
index 0000000..4d79936
--- /dev/null
+++ b/android/content/pm/DataLoaderManager.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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.os.RemoteException;
+
+/**
+ * Data loader manager takes care of data loaders of different packages. It provides methods to
+ * initialize a data loader binder service (binding and creating it), to return a binder of the data
+ * loader binder service and to destroy a data loader binder service.
+ * @see com.android.server.pm.DataLoaderManagerService
+ * @hide
+ */
+public class DataLoaderManager {
+ private static final String TAG = "DataLoaderManager";
+ private final IDataLoaderManager mService;
+
+ public DataLoaderManager(IDataLoaderManager service) {
+ mService = service;
+ }
+
+ /**
+ * Finds a data loader binder service and binds to it. This requires PackageManager.
+ *
+ * @param dataLoaderId ID for the new data loader binder service.
+ * @param params DataLoaderParamsParcel object that contains data loader params, including
+ * its package name, class name, and additional parameters.
+ * @param bindDelayMs introduce a delay before actual bind in case we want to avoid busylooping
+ * @param listener Callback for the data loader service to report status back to the
+ * caller.
+ * @return false if 1) target ID collides with a data loader that is already bound to data
+ * loader manager; 2) package name is not specified; 3) fails to find data loader package;
+ * or 4) fails to bind to the specified data loader service, otherwise return true.
+ */
+ public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params,
+ long bindDelayMs, @NonNull IDataLoaderStatusListener listener) {
+ try {
+ return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a binder interface of the data loader binder service, given its ID.
+ */
+ @Nullable
+ public IDataLoader getDataLoader(int dataLoaderId) {
+ try {
+ return mService.getDataLoader(dataLoaderId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unbinds from a data loader binder service, specified by its ID.
+ * DataLoader will receive destroy notification.
+ */
+ @Nullable
+ public void unbindFromDataLoader(int dataLoaderId) {
+ try {
+ mService.unbindFromDataLoader(dataLoaderId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/content/pm/DataLoaderParams.java b/android/content/pm/DataLoaderParams.java
new file mode 100644
index 0000000..a6d3b45
--- /dev/null
+++ b/android/content/pm/DataLoaderParams.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 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.SystemApi;
+import android.content.ComponentName;
+
+/**
+ * This class represents the parameters used to configure a DataLoader.
+ *
+ * {@see android.service.dataloader.DataLoaderService.DataLoader}
+ * @hide
+ */
+@SystemApi
+public class DataLoaderParams {
+ @NonNull
+ private final DataLoaderParamsParcel mData;
+
+ /**
+ * Creates and populates set of DataLoader parameters for Streaming installation.
+ *
+ * @param componentName the component implementing a DataLoaderService that is responsible
+ * for providing data blocks while streaming.
+ * @param arguments free form installation arguments.
+ */
+ public static final @NonNull DataLoaderParams forStreaming(@NonNull ComponentName componentName,
+ @NonNull String arguments) {
+ return new DataLoaderParams(DataLoaderType.STREAMING, componentName, arguments);
+ }
+
+ /**
+ * Creates and populates set of Data Loader parameters for Incremental installation.
+ *
+ * @param componentName DataLoaderService component supporting Incremental installation.
+ * @param arguments free form installation arguments
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final @NonNull DataLoaderParams forIncremental(
+ @NonNull ComponentName componentName, @NonNull String arguments) {
+ return new DataLoaderParams(DataLoaderType.INCREMENTAL, componentName, arguments);
+ }
+
+ /** @hide */
+ public DataLoaderParams(@NonNull @DataLoaderType int type, @NonNull ComponentName componentName,
+ @NonNull String arguments) {
+ DataLoaderParamsParcel data = new DataLoaderParamsParcel();
+ data.type = type;
+ data.packageName = componentName.getPackageName();
+ data.className = componentName.getClassName();
+ data.arguments = arguments;
+ mData = data;
+ }
+
+ /** @hide */
+ DataLoaderParams(@NonNull DataLoaderParamsParcel data) {
+ mData = data;
+ }
+
+ /** @hide */
+ public final @NonNull DataLoaderParamsParcel getData() {
+ return mData;
+ }
+
+ /**
+ * @return data loader type
+ */
+ public final @NonNull @DataLoaderType int getType() {
+ return mData.type;
+ }
+
+ /**
+ * @return data loader's component name
+ */
+ public final @NonNull ComponentName getComponentName() {
+ return new ComponentName(mData.packageName, mData.className);
+ }
+
+ /**
+ * @return data loader's arguments
+ */
+ public final @NonNull String getArguments() {
+ return mData.arguments;
+ }
+}
diff --git a/android/content/pm/FallbackCategoryProvider.java b/android/content/pm/FallbackCategoryProvider.java
new file mode 100644
index 0000000..a0a11aa
--- /dev/null
+++ b/android/content/pm/FallbackCategoryProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.pm;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Class that provides fallback values for {@link ApplicationInfo#category}.
+ *
+ * @hide
+ */
+public class FallbackCategoryProvider {
+ private static final String TAG = "FallbackCategoryProvider";
+
+ private static final ArrayMap<String, Integer> sFallbacks = new ArrayMap<>();
+
+ public static void loadFallbacks() {
+ sFallbacks.clear();
+ if (SystemProperties.getBoolean("fw.ignore_fb_categories", false)) {
+ Log.d(TAG, "Ignoring fallback categories");
+ return;
+ }
+
+ final AssetManager assets = new AssetManager();
+ assets.addAssetPath("/system/framework/framework-res.apk");
+ final Resources res = new Resources(assets, null, null);
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ res.openRawResource(com.android.internal.R.raw.fallback_categories)))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.charAt(0) == '#') continue;
+ final String[] split = line.split(",");
+ if (split.length == 2) {
+ sFallbacks.put(split[0], Integer.parseInt(split[1]));
+ }
+ }
+ Log.d(TAG, "Found " + sFallbacks.size() + " fallback categories");
+ } catch (IOException | NumberFormatException e) {
+ Log.w(TAG, "Failed to read fallback categories", e);
+ }
+ }
+
+ public static int getFallbackCategory(String packageName) {
+ return sFallbacks.getOrDefault(packageName, ApplicationInfo.CATEGORY_UNDEFINED);
+ }
+}
diff --git a/android/content/pm/FeatureGroupInfo.java b/android/content/pm/FeatureGroupInfo.java
new file mode 100644
index 0000000..38c8f74
--- /dev/null
+++ b/android/content/pm/FeatureGroupInfo.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A set of features that can be requested by an application. This corresponds
+ * to information collected from the
+ * AndroidManifest.xml's {@code <feature-group>} tag.
+ */
+public final class FeatureGroupInfo implements Parcelable {
+
+ /**
+ * The list of features that are required by this group.
+ *
+ * @see FeatureInfo#FLAG_REQUIRED
+ */
+ public FeatureInfo[] features;
+
+ public FeatureGroupInfo() {
+ }
+
+ public FeatureGroupInfo(FeatureGroupInfo other) {
+ features = other.features;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedArray(features, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<FeatureGroupInfo> CREATOR = new Creator<FeatureGroupInfo>() {
+ @Override
+ public FeatureGroupInfo createFromParcel(Parcel source) {
+ FeatureGroupInfo group = new FeatureGroupInfo();
+ group.features = source.createTypedArray(FeatureInfo.CREATOR);
+ return group;
+ }
+
+ @Override
+ public FeatureGroupInfo[] newArray(int size) {
+ return new FeatureGroupInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/FeatureInfo.java b/android/content/pm/FeatureInfo.java
new file mode 100644
index 0000000..89269e0
--- /dev/null
+++ b/android/content/pm/FeatureInfo.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2009 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
+
+/**
+ * Definition of a single optional hardware or software feature of an Android
+ * device.
+ * <p>
+ * This object is used to represent both features supported by a device and
+ * features requested by an app. Apps can request that certain features be
+ * available as a prerequisite to being installed through the
+ * {@code uses-feature} tag in their manifests.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N}, features can have a
+ * version, which must always be backwards compatible. That is, a device
+ * claiming to support version 3 of a specific feature must support apps
+ * requesting version 1 of that feature.
+ */
+public class FeatureInfo implements Parcelable {
+ /**
+ * The name of this feature, for example "android.hardware.camera". If
+ * this is null, then this is an OpenGL ES version feature as described
+ * in {@link #reqGlEsVersion}.
+ */
+ public String name;
+
+ /**
+ * If this object represents a feature supported by a device, this is the
+ * maximum version of this feature supported by the device. The device
+ * implicitly supports all older versions of this feature.
+ * <p>
+ * If this object represents a feature requested by an app, this is the
+ * minimum version of the feature required by the app.
+ * <p>
+ * When a feature version is undefined by a device, it's assumed to be
+ * version 0.
+ */
+ public int version;
+
+ /**
+ * Default value for {@link #reqGlEsVersion};
+ */
+ public static final int GL_ES_VERSION_UNDEFINED = 0;
+
+ /**
+ * The GLES version used by an application. The upper order 16 bits represent the
+ * major version and the lower order 16 bits the minor version. Only valid
+ * if {@link #name} is null.
+ */
+ public int reqGlEsVersion;
+
+ /**
+ * Set on {@link #flags} if this feature has been required by the application.
+ */
+ public static final int FLAG_REQUIRED = 0x0001;
+
+ /**
+ * Additional flags. May be zero or more of {@link #FLAG_REQUIRED}.
+ */
+ public int flags;
+
+ public FeatureInfo() {
+ }
+
+ public FeatureInfo(FeatureInfo orig) {
+ name = orig.name;
+ version = orig.version;
+ reqGlEsVersion = orig.reqGlEsVersion;
+ flags = orig.flags;
+ }
+
+ @Override
+ public String toString() {
+ if (name != null) {
+ return "FeatureInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + " v=" + version + " fl=0x" + Integer.toHexString(flags) + "}";
+ } else {
+ return "FeatureInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " glEsVers=" + getGlEsVersion()
+ + " fl=0x" + Integer.toHexString(flags) + "}";
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString8(name);
+ dest.writeInt(version);
+ dest.writeInt(reqGlEsVersion);
+ dest.writeInt(flags);
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (name != null) {
+ proto.write(FeatureInfoProto.NAME, name);
+ }
+ proto.write(FeatureInfoProto.VERSION, version);
+ proto.write(FeatureInfoProto.GLES_VERSION, getGlEsVersion());
+ proto.write(FeatureInfoProto.FLAGS, flags);
+ proto.end(token);
+ }
+
+ public static final @android.annotation.NonNull Creator<FeatureInfo> CREATOR = new Creator<FeatureInfo>() {
+ @Override
+ public FeatureInfo createFromParcel(Parcel source) {
+ return new FeatureInfo(source);
+ }
+ @Override
+ public FeatureInfo[] newArray(int size) {
+ return new FeatureInfo[size];
+ }
+ };
+
+ private FeatureInfo(Parcel source) {
+ name = source.readString8();
+ version = source.readInt();
+ reqGlEsVersion = source.readInt();
+ flags = source.readInt();
+ }
+
+ /**
+ * This method extracts the major and minor version of reqGLEsVersion attribute
+ * and returns it as a string. Say reqGlEsVersion value of 0x00010002 is returned
+ * as 1.2
+ * @return String representation of the reqGlEsVersion attribute
+ */
+ public String getGlEsVersion() {
+ int major = ((reqGlEsVersion & 0xffff0000) >> 16);
+ int minor = reqGlEsVersion & 0x0000ffff;
+ return String.valueOf(major)+"."+String.valueOf(minor);
+ }
+}
diff --git a/android/content/pm/IncrementalStatesInfo.java b/android/content/pm/IncrementalStatesInfo.java
new file mode 100644
index 0000000..0393d34
--- /dev/null
+++ b/android/content/pm/IncrementalStatesInfo.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Info about a package's states in Parcelable format.
+ * @hide
+ */
+public class IncrementalStatesInfo implements Parcelable {
+ private boolean mIsLoading;
+ private float mProgress;
+
+ public IncrementalStatesInfo(boolean isLoading, float progress) {
+ mIsLoading = isLoading;
+ mProgress = progress;
+ }
+
+ private IncrementalStatesInfo(Parcel source) {
+ mIsLoading = source.readBoolean();
+ mProgress = source.readFloat();
+ }
+
+ public boolean isLoading() {
+ return mIsLoading;
+ }
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsLoading);
+ dest.writeFloat(mProgress);
+ }
+
+ public static final @android.annotation.NonNull Creator<IncrementalStatesInfo> CREATOR =
+ new Creator<IncrementalStatesInfo>() {
+ public IncrementalStatesInfo createFromParcel(Parcel source) {
+ return new IncrementalStatesInfo(source);
+ }
+ public IncrementalStatesInfo[] newArray(int size) {
+ return new IncrementalStatesInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/InstallSourceInfo.java b/android/content/pm/InstallSourceInfo.java
new file mode 100644
index 0000000..a45bf79
--- /dev/null
+++ b/android/content/pm/InstallSourceInfo.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about how an app was installed.
+ * @see PackageManager#getInstallSourceInfo(String)
+ */
+public final class InstallSourceInfo implements Parcelable {
+
+ @Nullable private final String mInitiatingPackageName;
+
+ @Nullable private final SigningInfo mInitiatingPackageSigningInfo;
+
+ @Nullable private final String mOriginatingPackageName;
+
+ @Nullable private final String mInstallingPackageName;
+
+ /** @hide */
+ public InstallSourceInfo(@Nullable String initiatingPackageName,
+ @Nullable SigningInfo initiatingPackageSigningInfo,
+ @Nullable String originatingPackageName, @Nullable String installingPackageName) {
+ mInitiatingPackageName = initiatingPackageName;
+ mInitiatingPackageSigningInfo = initiatingPackageSigningInfo;
+ mOriginatingPackageName = originatingPackageName;
+ mInstallingPackageName = installingPackageName;
+ }
+
+ @Override
+ public int describeContents() {
+ return mInitiatingPackageSigningInfo == null
+ ? 0 : mInitiatingPackageSigningInfo.describeContents();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mInitiatingPackageName);
+ dest.writeParcelable(mInitiatingPackageSigningInfo, flags);
+ dest.writeString(mOriginatingPackageName);
+ dest.writeString(mInstallingPackageName);
+ }
+
+ private InstallSourceInfo(Parcel source) {
+ mInitiatingPackageName = source.readString();
+ mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
+ mOriginatingPackageName = source.readString();
+ mInstallingPackageName = source.readString();
+ }
+
+ /**
+ * The name of the package that requested the installation, or null if not available.
+ *
+ * This is normally the same as the installing package name. If the installing package name
+ * is changed, for example by calling
+ * {@link PackageManager#setInstallerPackageName(String, String)}, the initiating package name
+ * remains unchanged. It continues to identify the actual package that performed the install
+ * or update.
+ * <p>
+ * Null may be returned if the app was not installed by a package (e.g. a system app or an app
+ * installed via adb) or if the initiating package has itself been uninstalled.
+ */
+ @Nullable
+ public String getInitiatingPackageName() {
+ return mInitiatingPackageName;
+ }
+
+ /**
+ * Information about the signing certificates used to sign the initiating package, if available.
+ */
+ @Nullable
+ public SigningInfo getInitiatingPackageSigningInfo() {
+ return mInitiatingPackageSigningInfo;
+ }
+
+ /**
+ * The name of the package on behalf of which the initiating package requested the installation,
+ * or null if not available.
+ * <p>
+ * For example if a downloaded APK is installed via the Package Installer this could be the
+ * app that performed the download. This value is provided by the initiating package and not
+ * verified by the framework.
+ * <p>
+ * Note that the {@code InstallSourceInfo} returned by
+ * {@link PackageManager#getInstallSourceInfo(String)} will not have this information
+ * available unless the calling application holds the INSTALL_PACKAGES permission.
+ */
+ @Nullable
+ public String getOriginatingPackageName() {
+ return mOriginatingPackageName;
+ }
+
+ /**
+ * The name of the package responsible for the installation (the installer of record), or null
+ * if not available.
+ * Note that this may differ from the initiating package name and can be modified via
+ * {@link PackageManager#setInstallerPackageName(String, String)}.
+ * <p>
+ * Null may be returned if the app was not installed by a package (e.g. a system app or an app
+ * installed via adb) or if the installing package has itself been uninstalled.
+ */
+ @Nullable
+ public String getInstallingPackageName() {
+ return mInstallingPackageName;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<InstallSourceInfo> CREATOR =
+ new Creator<InstallSourceInfo>() {
+ @Override
+ public InstallSourceInfo createFromParcel(Parcel source) {
+ return new InstallSourceInfo(source);
+ }
+
+ @Override
+ public InstallSourceInfo[] newArray(int size) {
+ return new InstallSourceInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/InstallationFile.java b/android/content/pm/InstallationFile.java
new file mode 100644
index 0000000..7e07c45
--- /dev/null
+++ b/android/content/pm/InstallationFile.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * Definition of a file in a streaming installation session.
+ * You can use this class to retrieve the information of such a file, such as its name, size and
+ * metadata. These file attributes will be consistent with those used in:
+ * {@code PackageInstaller.Session#addFile}, when the file was first added into the session.
+ *
+ * @see android.content.pm.PackageInstaller.Session#addFile
+ * @hide
+ */
+@SystemApi
+public final class InstallationFile {
+ private final @NonNull InstallationFileParcel mParcel;
+
+ /**
+ * Constructor, internal use only
+ * @hide
+ */
+ public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name,
+ long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) {
+ mParcel = new InstallationFileParcel();
+ mParcel.location = location;
+ mParcel.name = name;
+ mParcel.size = lengthBytes;
+ mParcel.metadata = metadata;
+ mParcel.signature = signature;
+ }
+
+ /**
+ * Installation Location of this file. Can be one of the following three locations:
+ * <ul>
+ * <li>(1) {@code PackageInstaller.LOCATION_DATA_APP}</li>
+ * <li>(2) {@code PackageInstaller.LOCATION_MEDIA_OBB}</li>
+ * <li>(3) {@code PackageInstaller.LOCATION_MEDIA_DATA}</li>
+ * </ul>
+ * @see android.content.pm.PackageInstaller
+ * @return Integer that denotes the installation location of the file.
+ */
+ public @PackageInstaller.FileLocation int getLocation() {
+ return mParcel.location;
+ }
+
+ /**
+ * @return Name of the file.
+ */
+ public @NonNull String getName() {
+ return mParcel.name;
+ }
+
+ /**
+ * @return File size in bytes.
+ */
+ public long getLengthBytes() {
+ return mParcel.size;
+ }
+
+ /**
+ * @return File metadata as a byte array
+ */
+ public @Nullable byte[] getMetadata() {
+ return mParcel.metadata;
+ }
+
+ /**
+ * @return File signature info as a byte array
+ */
+ public @Nullable byte[] getSignature() {
+ return mParcel.signature;
+ }
+
+ /** @hide */
+ public @NonNull InstallationFileParcel getData() {
+ return mParcel;
+ }
+}
diff --git a/android/content/pm/InstantAppInfo.java b/android/content/pm/InstantAppInfo.java
new file mode 100644
index 0000000..24d6a07
--- /dev/null
+++ b/android/content/pm/InstantAppInfo.java
@@ -0,0 +1,151 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents the state of an instant app. Instant apps can
+ * be installed or uninstalled. If the app is installed you can call
+ * {@link #getApplicationInfo()} to get the app info, otherwise this
+ * class provides APIs to get basic app info for showing it in the UI,
+ * such as permissions, label, package name.
+ *
+ * @hide
+ */
+@SystemApi
+public final class InstantAppInfo implements Parcelable {
+ private final ApplicationInfo mApplicationInfo;
+
+ private final String mPackageName;
+ private final CharSequence mLabelText;
+
+ private final String[] mRequestedPermissions;
+ private final String[] mGrantedPermissions;
+
+ public InstantAppInfo(ApplicationInfo appInfo,
+ String[] requestedPermissions, String[] grantedPermissions) {
+ mApplicationInfo = appInfo;
+ mPackageName = null;
+ mLabelText = null;
+ mRequestedPermissions = requestedPermissions;
+ mGrantedPermissions = grantedPermissions;
+ }
+
+ public InstantAppInfo(String packageName, CharSequence label,
+ String[] requestedPermissions, String[] grantedPermissions) {
+ mApplicationInfo = null;
+ mPackageName = packageName;
+ mLabelText = label;
+ mRequestedPermissions = requestedPermissions;
+ mGrantedPermissions = grantedPermissions;
+ }
+
+ private InstantAppInfo(Parcel parcel) {
+ mPackageName = parcel.readString();
+ mLabelText = parcel.readCharSequence();
+ mRequestedPermissions = parcel.readStringArray();
+ mGrantedPermissions = parcel.createStringArray();
+ mApplicationInfo = parcel.readParcelable(null);
+ }
+
+ /**
+ * @return The application info if the app is installed,
+ * <code>null</code> otherwise,
+ */
+ public @Nullable ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
+ /**
+ * @return The package name.
+ */
+ public @NonNull String getPackageName() {
+ if (mApplicationInfo != null) {
+ return mApplicationInfo.packageName;
+ }
+ return mPackageName;
+ }
+
+ /**
+ * @param packageManager Package manager for loading resources.
+ * @return Loads the label if the app is installed or returns the cached one otherwise.
+ */
+ public @NonNull CharSequence loadLabel(@NonNull PackageManager packageManager) {
+ if (mApplicationInfo != null) {
+ return mApplicationInfo.loadLabel(packageManager);
+ }
+ return mLabelText;
+ }
+
+ /**
+ * @param packageManager Package manager for loading resources.
+ * @return Loads the icon if the app is installed or returns the cached one otherwise.
+ */
+ public @NonNull Drawable loadIcon(@NonNull PackageManager packageManager) {
+ if (mApplicationInfo != null) {
+ return mApplicationInfo.loadIcon(packageManager);
+ }
+ return packageManager.getInstantAppIcon(mPackageName);
+ }
+
+ /**
+ * @return The requested permissions.
+ */
+ public @Nullable String[] getRequestedPermissions() {
+ return mRequestedPermissions;
+ }
+
+ /**
+ * @return The granted permissions.
+ */
+ public @Nullable String[] getGrantedPermissions() {
+ return mGrantedPermissions;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeCharSequence(mLabelText);
+ parcel.writeStringArray(mRequestedPermissions);
+ parcel.writeStringArray(mGrantedPermissions);
+ parcel.writeParcelable(mApplicationInfo, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<InstantAppInfo> CREATOR =
+ new Creator<InstantAppInfo>() {
+ @Override
+ public InstantAppInfo createFromParcel(Parcel parcel) {
+ return new InstantAppInfo(parcel);
+ }
+
+ @Override
+ public InstantAppInfo[] newArray(int size) {
+ return new InstantAppInfo[0];
+ }
+ };
+}
diff --git a/android/content/pm/InstantAppIntentFilter.java b/android/content/pm/InstantAppIntentFilter.java
new file mode 100644
index 0000000..7c63406
--- /dev/null
+++ b/android/content/pm/InstantAppIntentFilter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.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 instant application intent filter.
+ * @hide
+ */
+@SystemApi
+public final class InstantAppIntentFilter implements Parcelable {
+ private final String mSplitName;
+ /** The filters used to match domain */
+ private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
+
+ public InstantAppIntentFilter(@Nullable String splitName, @NonNull List<IntentFilter> filters) {
+ if (filters == null || filters.size() == 0) {
+ throw new IllegalArgumentException();
+ }
+ mSplitName = splitName;
+ mFilters.addAll(filters);
+ }
+
+ InstantAppIntentFilter(Parcel in) {
+ mSplitName = in.readString();
+ in.readList(mFilters, null /*loader*/);
+ }
+
+ public String getSplitName() {
+ return mSplitName;
+ }
+
+ public List<IntentFilter> getFilters() {
+ return mFilters;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mSplitName);
+ out.writeList(mFilters);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<InstantAppIntentFilter> CREATOR
+ = new Parcelable.Creator<InstantAppIntentFilter>() {
+ @Override
+ public InstantAppIntentFilter createFromParcel(Parcel in) {
+ return new InstantAppIntentFilter(in);
+ }
+ @Override
+ public InstantAppIntentFilter[] newArray(int size) {
+ return new InstantAppIntentFilter[size];
+ }
+ };
+}
diff --git a/android/content/pm/InstantAppRequest.java b/android/content/pm/InstantAppRequest.java
new file mode 100644
index 0000000..5b5d109
--- /dev/null
+++ b/android/content/pm/InstantAppRequest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.UserIdInt;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * 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 */
+ public final Intent origIntent;
+ /** Resolved type of the intent */
+ public final String resolvedType;
+ /** The name of the package requesting the instant application */
+ public final String callingPackage;
+ /** The feature in the package requesting the instant application */
+ public final String callingFeatureId;
+ /** Whether or not the requesting package was an instant app */
+ public final boolean isRequesterInstantApp;
+ /** ID of the user requesting the instant application */
+ public final @UserIdInt int userId;
+ /**
+ * Optional extra bundle provided by the source application to the installer for additional
+ * verification.
+ */
+ public final Bundle verificationBundle;
+ /** Whether resolution occurs because an application is starting */
+ public final boolean resolveForStart;
+ /**
+ * The hash prefix of an instant app's domain or null if no host is defined.
+ * Secure version that should be carried through for external use.
+ */
+ @Nullable
+ public final int[] hostDigestPrefixSecure;
+ /** A unique identifier */
+ @NonNull
+ public final String token;
+
+ public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
+ String resolvedType, String callingPackage, @Nullable String callingFeatureId,
+ boolean isRequesterInstantApp, @UserIdInt int userId, Bundle verificationBundle,
+ boolean resolveForStart, @Nullable int[] hostDigestPrefixSecure,
+ @NonNull String token) {
+ this.responseObj = responseObj;
+ this.origIntent = origIntent;
+ this.resolvedType = resolvedType;
+ this.callingPackage = callingPackage;
+ this.callingFeatureId = callingFeatureId;
+ this.isRequesterInstantApp = isRequesterInstantApp;
+ this.userId = userId;
+ this.verificationBundle = verificationBundle;
+ this.resolveForStart = resolveForStart;
+ this.hostDigestPrefixSecure = hostDigestPrefixSecure;
+ this.token = token;
+ }
+}
diff --git a/android/content/pm/InstantAppRequestInfo.java b/android/content/pm/InstantAppRequestInfo.java
new file mode 100644
index 0000000..c531632
--- /dev/null
+++ b/android/content/pm/InstantAppRequestInfo.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2019 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.Intent;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information exposed to {@link android.app.InstantAppResolverService} to complete
+ * an instant application resolution request.
+ * @hide
+ */
+@SystemApi
+@DataClass(genParcelable = true, genConstructor = true, genAidl = true, genGetters = true)
+public final class InstantAppRequestInfo implements Parcelable {
+
+ /**
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ */
+ @NonNull
+ private final Intent mIntent;
+
+ /** The hash prefix of the instant app's domain or null if no host is defined. */
+ @Nullable
+ private final int[] mHostDigestPrefix;
+
+ /** The user requesting the instant application */
+ @NonNull
+ private final UserHandle mUserHandle;
+
+ /** Whether or not the requesting package was an instant app itself */
+ private final boolean mRequesterInstantApp;
+
+ /** A unique identifier */
+ @NonNull
+ private final String mToken;
+
+
+
+ // Code below generated by codegen v1.0.15.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstantAppRequestInfo.
+ *
+ * @param intent
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ * @param hostDigestPrefix
+ * The hash prefix of the instant app's domain or null if no host is defined.
+ * @param userHandle
+ * The user requesting the instant application
+ * @param requesterInstantApp
+ * Whether or not the requesting package was an instant app itself
+ * @param token
+ * A unique identifier
+ */
+ @DataClass.Generated.Member
+ public InstantAppRequestInfo(
+ @NonNull Intent intent,
+ @Nullable int[] hostDigestPrefix,
+ @NonNull UserHandle userHandle,
+ boolean requesterInstantApp,
+ @NonNull String token) {
+ this.mIntent = intent;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIntent);
+ this.mHostDigestPrefix = hostDigestPrefix;
+ this.mUserHandle = userHandle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUserHandle);
+ this.mRequesterInstantApp = requesterInstantApp;
+ this.mToken = token;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * The hash prefix of the instant app's domain or null if no host is defined.
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getHostDigestPrefix() {
+ return mHostDigestPrefix;
+ }
+
+ /**
+ * The user requesting the instant application
+ */
+ @DataClass.Generated.Member
+ public @NonNull UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
+ * Whether or not the requesting package was an instant app itself
+ */
+ @DataClass.Generated.Member
+ public boolean isRequesterInstantApp() {
+ return mRequesterInstantApp;
+ }
+
+ /**
+ * A unique identifier
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getToken() {
+ return mToken;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mRequesterInstantApp) flg |= 0x8;
+ if (mHostDigestPrefix != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeTypedObject(mIntent, flags);
+ if (mHostDigestPrefix != null) dest.writeIntArray(mHostDigestPrefix);
+ dest.writeTypedObject(mUserHandle, flags);
+ dest.writeString(mToken);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstantAppRequestInfo(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean requesterInstantApp = (flg & 0x8) != 0;
+ Intent intent = (Intent) in.readTypedObject(Intent.CREATOR);
+ int[] hostDigestPrefix = (flg & 0x2) == 0 ? null : in.createIntArray();
+ UserHandle userHandle = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+ String token = in.readString();
+
+ this.mIntent = intent;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIntent);
+ this.mHostDigestPrefix = hostDigestPrefix;
+ this.mUserHandle = userHandle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUserHandle);
+ this.mRequesterInstantApp = requesterInstantApp;
+ this.mToken = token;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstantAppRequestInfo> CREATOR
+ = new Parcelable.Creator<InstantAppRequestInfo>() {
+ @Override
+ public InstantAppRequestInfo[] newArray(int size) {
+ return new InstantAppRequestInfo[size];
+ }
+
+ @Override
+ public InstantAppRequestInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new InstantAppRequestInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1583964236162L,
+ codegenVersion = "1.0.15",
+ sourceFile = "frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java",
+ inputSignatures = "private final @android.annotation.NonNull android.content.Intent mIntent\nprivate final @android.annotation.Nullable int[] mHostDigestPrefix\nprivate final @android.annotation.NonNull android.os.UserHandle mUserHandle\nprivate final boolean mRequesterInstantApp\nprivate final @android.annotation.NonNull java.lang.String mToken\nclass InstantAppRequestInfo extends java.lang.Object implements [android.os.Parcelable]\[email protected](genParcelable=true, genConstructor=true, genAidl=true, genGetters=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/InstantAppResolveInfo.java b/android/content/pm/InstantAppResolveInfo.java
new file mode 100644
index 0000000..4c963a6
--- /dev/null
+++ b/android/content/pm/InstantAppResolveInfo.java
@@ -0,0 +1,401 @@
+/*
+ * 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.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;
+
+/**
+ * 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
+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 */
+ private final List<InstantAppIntentFilter> mFilters;
+ /** The version code of the app that this class resolves to */
+ 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))) {
+ throw new IllegalArgumentException();
+ }
+ mDigest = digest;
+ if (filters != null) {
+ mFilters = new ArrayList<>(filters.size());
+ mFilters.addAll(filters);
+ } else {
+ mFilters = null;
+ }
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ mExtras = extras;
+ mShouldLetInstallerDecide = shouldLetInstallerDecide;
+ }
+
+ InstantAppResolveInfo(Parcel in) {
+ 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.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST;
+ }
+
+ public int getDigestPrefix() {
+ return mDigest.getDigestPrefix()[0];
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public List<InstantAppIntentFilter> getIntentFilters() {
+ return mFilters;
+ }
+
+ /**
+ * @deprecated Use {@link #getLongVersionCode} instead.
+ */
+ @Deprecated
+ public int getVersionCode() {
+ return (int) (mVersionCode & 0xffffffff);
+ }
+
+ public long getLongVersionCode() {
+ return mVersionCode;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @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);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<InstantAppResolveInfo> CREATOR
+ = new Parcelable.Creator<InstantAppResolveInfo>() {
+ public InstantAppResolveInfo createFromParcel(Parcel in) {
+ return new InstantAppResolveInfo(in);
+ }
+
+ public InstantAppResolveInfo[] newArray(int size) {
+ return new InstantAppResolveInfo[size];
+ }
+ };
+
+ /**
+ * Helper class to generate and store each of the digests and prefixes
+ * sent to the Instant App 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 InstantAppDigest implements Parcelable {
+ 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 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*/);
+ }
+
+ /** @hide */
+ public InstantAppDigest(@NonNull String hostName, int maxDigests) {
+ if (hostName == null) {
+ throw new IllegalArgumentException();
+ }
+ mDigestBytes = generateDigest(hostName.toLowerCase(Locale.ENGLISH), maxDigests);
+ mDigestPrefix = new int[mDigestBytes.length];
+ for (int i = 0; i < mDigestBytes.length; i++) {
+ mDigestPrefix[i] =
+ ((mDigestBytes[i][0] & 0xFF) << 24
+ | (mDigestBytes[i][1] & 0xFF) << 16
+ | (mDigestBytes[i][2] & 0xFF) << 8
+ | (mDigestBytes[i][3] & 0xFF) << 0)
+ & DIGEST_MASK;
+ }
+ }
+
+ 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 {
+ final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+ if (maxDigests <= 0) {
+ final byte[] hostBytes = hostName.getBytes();
+ digests.add(digest.digest(hostBytes));
+ } else {
+ int prevDot = hostName.lastIndexOf('.');
+ prevDot = hostName.lastIndexOf('.', prevDot - 1);
+ // shortcut for short URLs
+ if (prevDot < 0) {
+ digests.add(digest.digest(hostName.getBytes()));
+ } else {
+ byte[] hostBytes =
+ hostName.substring(prevDot + 1, hostName.length()).getBytes();
+ digests.add(digest.digest(hostBytes));
+ int digestCount = 1;
+ while (prevDot >= 0 && digestCount < maxDigests) {
+ prevDot = hostName.lastIndexOf('.', prevDot - 1);
+ hostBytes =
+ hostName.substring(prevDot + 1, hostName.length()).getBytes();
+ digests.add(digest.digest(hostBytes));
+ digestCount++;
+ }
+ }
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("could not find digest algorithm");
+ }
+ return digests.toArray(new byte[digests.size()][]);
+ }
+
+ InstantAppDigest(Parcel in) {
+ final int digestCount = in.readInt();
+ if (digestCount == -1) {
+ mDigestBytes = null;
+ } else {
+ mDigestBytes = new byte[digestCount][];
+ for (int i = 0; i < digestCount; i++) {
+ mDigestBytes[i] = in.createByteArray();
+ }
+ }
+ mDigestPrefix = in.createIntArray();
+ mDigestPrefixSecure = in.createIntArray();
+ }
+
+ public byte[][] getDigestBytes() {
+ return mDigestBytes;
+ }
+
+ public int[] getDigestPrefix() {
+ 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;
+ }
+
+ @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 {
+ out.writeInt(mDigestBytes.length);
+ for (int i = 0; i < mDigestBytes.length; i++) {
+ out.writeByteArray(mDigestBytes[i]);
+ }
+ }
+ out.writeIntArray(mDigestPrefix);
+ out.writeIntArray(mDigestPrefixSecure);
+ }
+
+ @SuppressWarnings("hiding")
+ public static final @android.annotation.NonNull Parcelable.Creator<InstantAppDigest> CREATOR =
+ new Parcelable.Creator<InstantAppDigest>() {
+ @Override
+ public InstantAppDigest createFromParcel(Parcel in) {
+ if (in.readBoolean() /* is undefined */) {
+ return UNDEFINED;
+ }
+ return new InstantAppDigest(in);
+ }
+ @Override
+ public InstantAppDigest[] newArray(int size) {
+ return new InstantAppDigest[size];
+ }
+ };
+ }
+}
diff --git a/android/content/pm/InstrumentationInfo.java b/android/content/pm/InstrumentationInfo.java
new file mode 100644
index 0000000..bfbd4c6
--- /dev/null
+++ b/android/content/pm/InstrumentationInfo.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2007 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+/**
+ * Information you can retrieve about a particular piece of test
+ * instrumentation. This corresponds to information collected
+ * from the AndroidManifest.xml's <instrumentation> tag.
+ */
+public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
+ /**
+ * The name of the application package being instrumented. From the
+ * "package" attribute.
+ */
+ public String targetPackage;
+
+ /**
+ * Names of the process(es) this instrumentation will run in. If not specified, only
+ * runs in the main process of the targetPackage. Can either be a comma-separated list
+ * of process names or '*' for any process that launches to run targetPackage code.
+ */
+ public String targetProcesses;
+
+ /**
+ * Full path to the base APK for this application.
+ */
+ public String sourceDir;
+
+ /**
+ * Full path to the publicly available parts of {@link #sourceDir},
+ * including resources and manifest. This may be different from
+ * {@link #sourceDir} if an application is forward locked.
+ */
+ public String publicSourceDir;
+
+ /**
+ * The names of all installed split APKs, ordered lexicographically.
+ */
+ public String[] splitNames;
+
+ /**
+ * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
+ */
+ public String[] splitSourceDirs;
+
+ /**
+ * Full path to the publicly available parts of {@link #splitSourceDirs},
+ * including resources and manifest. This may be different from
+ * {@link #splitSourceDirs} if an application is forward locked.
+ *
+ * @see #splitSourceDirs
+ */
+ public String[] splitPublicSourceDirs;
+
+ /**
+ * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+ *
+ * Available since platform version O.
+ *
+ * Only populated if the application opts in to isolated split loading via the
+ * {@link android.R.attr.isolatedSplits} attribute in the <manifest> tag of the app's
+ * AndroidManifest.xml.
+ *
+ * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
+ * and {@link #splitPublicSourceDirs} arrays.
+ * Each key represents a split and its value is an array of splits. The first element of this
+ * array is the parent split, and the rest are configuration splits. These configuration splits
+ * have no dependencies themselves.
+ * Cycles do not exist because they are illegal and screened for during installation.
+ *
+ * May be null if no splits are installed, or if no dependencies exist between them.
+ * @hide
+ */
+ public SparseArray<int[]> splitDependencies;
+
+ /**
+ * Full path to a directory assigned to the package for its persistent data.
+ */
+ public String dataDir;
+
+ /** {@hide} */
+ public String deviceProtectedDataDir;
+ /** {@hide} */
+ public String credentialProtectedDataDir;
+
+ /** {@hide} */
+ public String primaryCpuAbi;
+
+ /** {@hide} */
+ public String secondaryCpuAbi;
+
+ /** {@hide} Full path to the directory containing primary ABI native libraries. */
+ public String nativeLibraryDir;
+
+ /** {@hide} Full path to the directory containing secondary ABI native libraries. */
+ public String secondaryNativeLibraryDir;
+
+ /**
+ * Specifies whether or not this instrumentation will handle profiling.
+ */
+ public boolean handleProfiling;
+
+ /** Specifies whether or not to run this instrumentation as a functional test */
+ public boolean functionalTest;
+
+ public InstrumentationInfo() {
+ }
+
+ public InstrumentationInfo(InstrumentationInfo orig) {
+ super(orig);
+ targetPackage = orig.targetPackage;
+ targetProcesses = orig.targetProcesses;
+ sourceDir = orig.sourceDir;
+ publicSourceDir = orig.publicSourceDir;
+ splitNames = orig.splitNames;
+ splitSourceDirs = orig.splitSourceDirs;
+ splitPublicSourceDirs = orig.splitPublicSourceDirs;
+ splitDependencies = orig.splitDependencies;
+ dataDir = orig.dataDir;
+ deviceProtectedDataDir = orig.deviceProtectedDataDir;
+ credentialProtectedDataDir = orig.credentialProtectedDataDir;
+ primaryCpuAbi = orig.primaryCpuAbi;
+ secondaryCpuAbi = orig.secondaryCpuAbi;
+ nativeLibraryDir = orig.nativeLibraryDir;
+ secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir;
+ handleProfiling = orig.handleProfiling;
+ functionalTest = orig.functionalTest;
+ }
+
+ public String toString() {
+ return "InstrumentationInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeString8(targetPackage);
+ dest.writeString8(targetProcesses);
+ dest.writeString8(sourceDir);
+ dest.writeString8(publicSourceDir);
+ dest.writeString8Array(splitNames);
+ dest.writeString8Array(splitSourceDirs);
+ dest.writeString8Array(splitPublicSourceDirs);
+ dest.writeSparseArray((SparseArray) splitDependencies);
+ dest.writeString8(dataDir);
+ dest.writeString8(deviceProtectedDataDir);
+ dest.writeString8(credentialProtectedDataDir);
+ dest.writeString8(primaryCpuAbi);
+ dest.writeString8(secondaryCpuAbi);
+ dest.writeString8(nativeLibraryDir);
+ dest.writeString8(secondaryNativeLibraryDir);
+ dest.writeInt((handleProfiling == false) ? 0 : 1);
+ dest.writeInt((functionalTest == false) ? 0 : 1);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<InstrumentationInfo> CREATOR
+ = new Parcelable.Creator<InstrumentationInfo>() {
+ public InstrumentationInfo createFromParcel(Parcel source) {
+ return new InstrumentationInfo(source);
+ }
+ public InstrumentationInfo[] newArray(int size) {
+ return new InstrumentationInfo[size];
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ private InstrumentationInfo(Parcel source) {
+ super(source);
+ targetPackage = source.readString8();
+ targetProcesses = source.readString8();
+ sourceDir = source.readString8();
+ publicSourceDir = source.readString8();
+ splitNames = source.createString8Array();
+ splitSourceDirs = source.createString8Array();
+ splitPublicSourceDirs = source.createString8Array();
+ splitDependencies = source.readSparseArray(null);
+ dataDir = source.readString8();
+ deviceProtectedDataDir = source.readString8();
+ credentialProtectedDataDir = source.readString8();
+ primaryCpuAbi = source.readString8();
+ secondaryCpuAbi = source.readString8();
+ nativeLibraryDir = source.readString8();
+ secondaryNativeLibraryDir = source.readString8();
+ handleProfiling = source.readInt() != 0;
+ functionalTest = source.readInt() != 0;
+ }
+
+ /** {@hide} */
+ public void copyTo(ApplicationInfo ai) {
+ ai.packageName = packageName;
+ ai.sourceDir = sourceDir;
+ ai.publicSourceDir = publicSourceDir;
+ ai.splitNames = splitNames;
+ ai.splitSourceDirs = splitSourceDirs;
+ ai.splitPublicSourceDirs = splitPublicSourceDirs;
+ ai.splitDependencies = splitDependencies;
+ ai.dataDir = dataDir;
+ ai.deviceProtectedDataDir = deviceProtectedDataDir;
+ ai.credentialProtectedDataDir = credentialProtectedDataDir;
+ ai.primaryCpuAbi = primaryCpuAbi;
+ ai.secondaryCpuAbi = secondaryCpuAbi;
+ ai.nativeLibraryDir = nativeLibraryDir;
+ ai.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
+ }
+}
diff --git a/android/content/pm/IntentFilterVerificationInfo.java b/android/content/pm/IntentFilterVerificationInfo.java
new file mode 100644
index 0000000..56b8bd8
--- /dev/null
+++ b/android/content/pm/IntentFilterVerificationInfo.java
@@ -0,0 +1,251 @@
+/*
+ * 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 static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * The {@link com.android.server.pm.PackageManagerService} maintains some
+ * {@link IntentFilterVerificationInfo}s for each domain / package name.
+ *
+ * @hide
+ */
+@SystemApi
+public final class IntentFilterVerificationInfo implements Parcelable {
+ private static final String TAG = IntentFilterVerificationInfo.class.getName();
+
+ private static final String TAG_DOMAIN = "domain";
+ private static final String ATTR_DOMAIN_NAME = "name";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_STATUS = "status";
+
+ private ArraySet<String> mDomains = new ArraySet<>();
+ private String mPackageName;
+ private int mStatus;
+
+ /** @hide */
+ public IntentFilterVerificationInfo() {
+ mPackageName = null;
+ mStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ /** @hide */
+ public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) {
+ mPackageName = packageName;
+ mDomains = domains;
+ mStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ /** @hide */
+ public IntentFilterVerificationInfo(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ readFromXml(parser);
+ }
+
+ /** @hide */
+ public IntentFilterVerificationInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /** @hide */
+ public void setStatus(int s) {
+ if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
+ s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ mStatus = s;
+ } else {
+ Log.w(TAG, "Trying to set a non supported status: " + s);
+ }
+ }
+
+ public Set<String> getDomains() {
+ return mDomains;
+ }
+
+ /** @hide */
+ public void setDomains(ArraySet<String> list) {
+ mDomains = list;
+ }
+
+ /** @hide */
+ public String getDomainsString() {
+ StringBuilder sb = new StringBuilder();
+ for (String str : mDomains) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(str);
+ }
+ return sb.toString();
+ }
+
+ String getStringFromXml(TypedXmlPullParser parser, String attribute, String defaultValue) {
+ String value = parser.getAttributeValue(null, attribute);
+ if (value == null) {
+ String msg = "Missing element under " + TAG +": " + attribute + " at " +
+ parser.getPositionDescription();
+ Log.w(TAG, msg);
+ return defaultValue;
+ } else {
+ return value;
+ }
+ }
+
+ int getIntFromXml(TypedXmlPullParser parser, String attribute, int defaultValue) {
+ return parser.getAttributeInt(null, attribute, defaultValue);
+ }
+
+ /** @hide */
+ public void readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null);
+ if (mPackageName == null) {
+ Log.e(TAG, "Package name cannot be null!");
+ }
+ int status = getIntFromXml(parser, ATTR_STATUS, -1);
+ if (status == -1) {
+ Log.e(TAG, "Unknown status value: " + status);
+ }
+ mStatus = status;
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_DOMAIN)) {
+ String name = getStringFromXml(parser, ATTR_DOMAIN_NAME, null);
+ if (!TextUtils.isEmpty(name)) {
+ mDomains.add(name);
+ }
+ } else {
+ Log.w(TAG, "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ /** @hide */
+ public void writeToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
+ serializer.attributeInt(null, ATTR_STATUS, mStatus);
+ for (String str : mDomains) {
+ serializer.startTag(null, TAG_DOMAIN);
+ serializer.attribute(null, ATTR_DOMAIN_NAME, str);
+ serializer.endTag(null, TAG_DOMAIN);
+ }
+ }
+
+ /** @hide */
+ public String getStatusString() {
+ return getStatusStringFromValue(((long) mStatus) << 32);
+ }
+
+ /** @hide */
+ public static String getStatusStringFromValue(long val) {
+ StringBuilder sb = new StringBuilder();
+ switch ((int)(val >> 32)) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+ sb.append("always : ");
+ sb.append(Long.toHexString(val & 0x00000000FFFFFFFF));
+ break;
+
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ sb.append("ask");
+ break;
+
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+ sb.append("never");
+ break;
+
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+ sb.append("always-ask");
+ break;
+
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ default:
+ sb.append("undefined");
+ break;
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void readFromParcel(Parcel source) {
+ mPackageName = source.readString();
+ mStatus = source.readInt();
+ ArrayList<String> list = new ArrayList<>();
+ source.readStringList(list);
+ mDomains.addAll(list);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mStatus);
+ dest.writeStringList(new ArrayList<>(mDomains));
+ }
+
+ public static final @android.annotation.NonNull Creator<IntentFilterVerificationInfo> CREATOR =
+ new Creator<IntentFilterVerificationInfo>() {
+ public IntentFilterVerificationInfo createFromParcel(Parcel source) {
+ return new IntentFilterVerificationInfo(source);
+ }
+ public IntentFilterVerificationInfo[] newArray(int size) {
+ return new IntentFilterVerificationInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/KeySet.java b/android/content/pm/KeySet.java
new file mode 100644
index 0000000..fd459e6
--- /dev/null
+++ b/android/content/pm/KeySet.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2012 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.Nullable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a {@code KeySet} that has been declared in the AndroidManifest.xml
+ * file for the application. A {@code KeySet} can be used explicitly to
+ * represent a trust relationship with other applications on the device.
+ * @hide
+ */
+public class KeySet implements Parcelable {
+
+ private IBinder token;
+
+ /** @hide */
+ public KeySet(IBinder token) {
+ if (token == null) {
+ throw new NullPointerException("null value for KeySet IBinder token");
+ }
+ this.token = token;
+ }
+
+ /** @hide */
+ public IBinder getToken() {
+ return token;
+ }
+
+ /** @hide */
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof KeySet) {
+ KeySet ks = (KeySet) o;
+ return token == ks.token;
+ }
+ return false;
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return token.hashCode();
+ }
+
+ /**
+ * Implement Parcelable
+ * @hide
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<KeySet> CREATOR
+ = new Parcelable.Creator<KeySet>() {
+
+ /**
+ * Create a KeySet from a Parcel
+ *
+ * @param in The parcel containing the KeySet
+ */
+ public KeySet createFromParcel(Parcel source) {
+ return readFromParcel(source);
+ }
+
+ /**
+ * Create an array of null KeySets
+ */
+ public KeySet[] newArray(int size) {
+ return new KeySet[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ private static KeySet readFromParcel(Parcel in) {
+ IBinder token = in.readStrongBinder();
+ return new KeySet(token);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(token);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/android/content/pm/LabeledIntent.java b/android/content/pm/LabeledIntent.java
new file mode 100644
index 0000000..ae41252
--- /dev/null
+++ b/android/content/pm/LabeledIntent.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2009 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.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+/**
+ * A special subclass of Intent that can have a custom label/icon
+ * associated with it. Primarily for use with {@link Intent#ACTION_CHOOSER}.
+ */
+public class LabeledIntent extends Intent {
+ private String mSourcePackage;
+ private int mLabelRes;
+ private CharSequence mNonLocalizedLabel;
+ private int mIcon;
+
+ /**
+ * Create a labeled intent from the given intent, supplying the label
+ * and icon resources for it.
+ *
+ * @param origIntent The original Intent to copy.
+ * @param sourcePackage The package in which the label and icon live.
+ * @param labelRes Resource containing the label, or 0 if none.
+ * @param icon Resource containing the icon, or 0 if none.
+ */
+ public LabeledIntent(Intent origIntent, String sourcePackage,
+ int labelRes, int icon) {
+ super(origIntent);
+ mSourcePackage = sourcePackage;
+ mLabelRes = labelRes;
+ mNonLocalizedLabel = null;
+ mIcon = icon;
+ }
+
+ /**
+ * Create a labeled intent from the given intent, supplying a textual
+ * label and icon resource for it.
+ *
+ * @param origIntent The original Intent to copy.
+ * @param sourcePackage The package in which the label and icon live.
+ * @param nonLocalizedLabel Concrete text to use for the label.
+ * @param icon Resource containing the icon, or 0 if none.
+ */
+ public LabeledIntent(Intent origIntent, String sourcePackage,
+ CharSequence nonLocalizedLabel, int icon) {
+ super(origIntent);
+ mSourcePackage = sourcePackage;
+ mLabelRes = 0;
+ mNonLocalizedLabel = nonLocalizedLabel;
+ mIcon = icon;
+ }
+
+ /**
+ * Create a labeled intent with no intent data but supplying the label
+ * and icon resources for it.
+ *
+ * @param sourcePackage The package in which the label and icon live.
+ * @param labelRes Resource containing the label, or 0 if none.
+ * @param icon Resource containing the icon, or 0 if none.
+ */
+ public LabeledIntent(String sourcePackage, int labelRes, int icon) {
+ mSourcePackage = sourcePackage;
+ mLabelRes = labelRes;
+ mNonLocalizedLabel = null;
+ mIcon = icon;
+ }
+
+ /**
+ * Create a labeled intent with no intent data but supplying a textual
+ * label and icon resource for it.
+ *
+ * @param sourcePackage The package in which the label and icon live.
+ * @param nonLocalizedLabel Concrete text to use for the label.
+ * @param icon Resource containing the icon, or 0 if none.
+ */
+ public LabeledIntent(String sourcePackage,
+ CharSequence nonLocalizedLabel, int icon) {
+ mSourcePackage = sourcePackage;
+ mLabelRes = 0;
+ mNonLocalizedLabel = nonLocalizedLabel;
+ mIcon = icon;
+ }
+
+ /**
+ * Return the name of the package holding label and icon resources.
+ */
+ public String getSourcePackage() {
+ return mSourcePackage;
+ }
+
+ /**
+ * Return any resource identifier that has been given for the label text.
+ */
+ public int getLabelResource() {
+ return mLabelRes;
+ }
+
+ /**
+ * Return any concrete text that has been given for the label text.
+ */
+ public CharSequence getNonLocalizedLabel() {
+ return mNonLocalizedLabel;
+ }
+
+ /**
+ * Return any resource identifier that has been given for the label icon.
+ */
+ public int getIconResource() {
+ return mIcon;
+ }
+
+ /**
+ * Retrieve the label associated with this object. If the object does
+ * not have a label, null will be returned, in which case you will probably
+ * want to load the label from the underlying resolved info for the Intent.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ if (mNonLocalizedLabel != null) {
+ return mNonLocalizedLabel;
+ }
+ if (mLabelRes != 0 && mSourcePackage != null) {
+ CharSequence label = pm.getText(mSourcePackage, mLabelRes, null);
+ if (label != null) {
+ return label;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the icon associated with this object. If the object does
+ * not have a icon, null will be returned, in which case you will probably
+ * want to load the icon from the underlying resolved info for the Intent.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ if (mIcon != 0 && mSourcePackage != null) {
+ Drawable icon = pm.getDrawable(mSourcePackage, mIcon, null);
+ if (icon != null) {
+ return icon;
+ }
+ }
+ return null;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeString(mSourcePackage);
+ dest.writeInt(mLabelRes);
+ TextUtils.writeToParcel(mNonLocalizedLabel, dest, parcelableFlags);
+ dest.writeInt(mIcon);
+ }
+
+ /** @hide */
+ protected LabeledIntent(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ super.readFromParcel(in);
+ mSourcePackage = in.readString();
+ mLabelRes = in.readInt();
+ mNonLocalizedLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mIcon = in.readInt();
+ }
+
+ public static final @android.annotation.NonNull Creator<LabeledIntent> CREATOR
+ = new Creator<LabeledIntent>() {
+ public LabeledIntent createFromParcel(Parcel source) {
+ return new LabeledIntent(source);
+ }
+ public LabeledIntent[] newArray(int size) {
+ return new LabeledIntent[size];
+ }
+ };
+
+}
diff --git a/android/content/pm/LauncherActivityInfo.java b/android/content/pm/LauncherActivityInfo.java
new file mode 100644
index 0000000..16e720e
--- /dev/null
+++ b/android/content/pm/LauncherActivityInfo.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.DisplayMetrics;
+
+/**
+ * A representation of an activity that can belong to this user or a managed
+ * profile associated with this user. It can be used to query the label, icon
+ * and badged icon for the activity.
+ */
+public class LauncherActivityInfo {
+ private final PackageManager mPm;
+ private UserHandle mUser;
+ private final LauncherActivityInfoInternal mInternal;
+
+ /**
+ * Create a launchable activity object for a given ResolveInfo and user.
+ *
+ * @param context The context for fetching resources.
+
+ */
+ LauncherActivityInfo(Context context, UserHandle user, LauncherActivityInfoInternal internal) {
+ mPm = context.getPackageManager();
+ mUser = user;
+ mInternal = internal;
+ }
+
+ /**
+ * Returns the component name of this activity.
+ *
+ * @return ComponentName of the activity
+ */
+ public ComponentName getComponentName() {
+ return mInternal.getComponentName();
+ }
+
+ /**
+ * Returns the user handle of the user profile that this activity belongs to. In order to
+ * persist the identity of the profile, do not store the UserHandle. Instead retrieve its
+ * serial number from UserManager. You can convert the serial number back to a UserHandle
+ * for later use.
+ *
+ * @see UserManager#getSerialNumberForUser(UserHandle)
+ * @see UserManager#getUserForSerialNumber(long)
+ *
+ * @return The UserHandle of the profile.
+ */
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Retrieves the label for the activity.
+ *
+ * @return The label for the activity.
+ */
+ public CharSequence getLabel() {
+ // TODO: Go through LauncherAppsService
+ return getActivityInfo().loadLabel(mPm);
+ }
+
+ /**
+ * @return Package loading progress, range between [0, 1].
+ */
+ public @FloatRange(from = 0.0, to = 1.0) float getLoadingProgress() {
+ return mInternal.getIncrementalStatesInfo().getProgress();
+ }
+
+ /**
+ * Returns the icon for this activity, without any badging for the profile.
+ * @param density The preferred density of the icon, zero for default density. Use
+ * density DPI values from {@link DisplayMetrics}.
+ * @see #getBadgedIcon(int)
+ * @see DisplayMetrics
+ * @return The drawable associated with the activity.
+ */
+ public Drawable getIcon(int density) {
+ // TODO: Go through LauncherAppsService
+ final int iconRes = getActivityInfo().getIconResource();
+ Drawable icon = null;
+ // Get the preferred density icon from the app's resources
+ if (density != 0 && iconRes != 0) {
+ try {
+ final Resources resources = mPm.getResourcesForApplication(
+ getActivityInfo().applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (NameNotFoundException | Resources.NotFoundException exc) {
+ }
+ }
+ // Get the default density icon
+ if (icon == null) {
+ icon = getActivityInfo().loadIcon(mPm);
+ }
+ return icon;
+ }
+
+ /**
+ * Returns the application flags from the ApplicationInfo of the activity.
+ *
+ * @return Application flags
+ * @hide remove before shipping
+ */
+ public int getApplicationFlags() {
+ return getActivityInfo().flags;
+ }
+
+ /**
+ * Returns the ActivityInfo of the activity.
+ *
+ * @return Activity Info
+ */
+ @NonNull
+ public ActivityInfo getActivityInfo() {
+ return mInternal.getActivityInfo();
+ }
+
+ /**
+ * Returns the application info for the application this activity belongs to.
+ * @return
+ */
+ public ApplicationInfo getApplicationInfo() {
+ return getActivityInfo().applicationInfo;
+ }
+
+ /**
+ * Returns the time at which the package was first installed.
+ *
+ * @return The time of installation of the package, in milliseconds.
+ */
+ public long getFirstInstallTime() {
+ try {
+ // TODO: Go through LauncherAppsService
+ return mPm.getPackageInfo(getActivityInfo().packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the name for the activity from android:name in the manifest.
+ * @return the name from android:name for the activity.
+ */
+ public String getName() {
+ return getActivityInfo().name;
+ }
+
+ /**
+ * Returns the activity icon with badging appropriate for the profile.
+ * @param density Optional density for the icon, or 0 to use the default density. Use
+ * {@link DisplayMetrics} for DPI values.
+ * @see DisplayMetrics
+ * @return A badged icon for the activity.
+ */
+ public Drawable getBadgedIcon(int density) {
+ Drawable originalIcon = getIcon(density);
+
+ return mPm.getUserBadgedIcon(originalIcon, mUser);
+ }
+}
diff --git a/android/content/pm/LauncherActivityInfoInternal.java b/android/content/pm/LauncherActivityInfoInternal.java
new file mode 100644
index 0000000..417f168
--- /dev/null
+++ b/android/content/pm/LauncherActivityInfoInternal.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 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.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class LauncherActivityInfoInternal implements Parcelable {
+ @UnsupportedAppUsage
+ @NonNull private ActivityInfo mActivityInfo;
+ @NonNull private ComponentName mComponentName;
+ @NonNull private IncrementalStatesInfo mIncrementalStatesInfo;
+
+ /**
+ * @param info ActivityInfo from which to create the LauncherActivityInfo.
+ * @param incrementalStatesInfo The package's states.
+ */
+ public LauncherActivityInfoInternal(@NonNull ActivityInfo info,
+ @NonNull IncrementalStatesInfo incrementalStatesInfo) {
+ mActivityInfo = info;
+ mComponentName = new ComponentName(info.packageName, info.name);
+ mIncrementalStatesInfo = incrementalStatesInfo;
+ }
+
+ public LauncherActivityInfoInternal(Parcel source) {
+ mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader());
+ mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
+ mIncrementalStatesInfo = source.readParcelable(
+ IncrementalStatesInfo.class.getClassLoader());
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ public ActivityInfo getActivityInfo() {
+ return mActivityInfo;
+ }
+
+ public IncrementalStatesInfo getIncrementalStatesInfo() {
+ return mIncrementalStatesInfo;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mActivityInfo, 0);
+ dest.writeParcelable(mIncrementalStatesInfo, 0);
+ }
+
+ public static final @android.annotation.NonNull Creator<LauncherActivityInfoInternal> CREATOR =
+ new Creator<LauncherActivityInfoInternal>() {
+ public LauncherActivityInfoInternal createFromParcel(Parcel source) {
+ return new LauncherActivityInfoInternal(source);
+ }
+ public LauncherActivityInfoInternal[] newArray(int size) {
+ return new LauncherActivityInfoInternal[size];
+ }
+ };
+}
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
new file mode 100644
index 0000000..a8a5837
--- /dev/null
+++ b/android/content/pm/LauncherApps.java
@@ -0,0 +1,2295 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.Manifest.permission;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.LocusId;
+import android.content.pm.PackageInstaller.SessionCallback;
+import android.content.pm.PackageInstaller.SessionCallbackDelegate;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Class for retrieving a list of launchable activities for the current user and any associated
+ * managed profiles that are visible to the current user, which can be retrieved with
+ * {@link #getProfiles}. This is mainly for use by launchers.
+ *
+ * Apps can be queried for each user profile.
+ * Since the PackageManager will not deliver package broadcasts for other profiles, you can register
+ * for package changes here.
+ * <p>
+ * To watch for managed profiles being added or removed, register for the following broadcasts:
+ * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}.
+ * <p>
+ * Note as of Android O, apps on a managed profile are no longer allowed to access apps on the
+ * main profile. Apps can only access profiles returned by {@link #getProfiles()}.
+ */
+@SystemService(Context.LAUNCHER_APPS_SERVICE)
+public class LauncherApps {
+
+ static final String TAG = "LauncherApps";
+ static final boolean DEBUG = false;
+
+ /**
+ * Activity Action: For the default launcher to show the confirmation dialog to create
+ * a pinned shortcut.
+ *
+ * <p>See the {@link ShortcutManager} javadoc for details.
+ *
+ * <p>
+ * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
+ * and call {@link PinItemRequest#accept(Bundle)}
+ * if the user accepts. If the user doesn't accept, no further action is required.
+ *
+ * @see #EXTRA_PIN_ITEM_REQUEST
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CONFIRM_PIN_SHORTCUT =
+ "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
+
+ /**
+ * Activity Action: For the default launcher to show the confirmation dialog to create
+ * a pinned app widget.
+ *
+ * <p>See the {@link android.appwidget.AppWidgetManager#requestPinAppWidget} javadoc for
+ * details.
+ *
+ * <p>
+ * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
+ * and call {@link PinItemRequest#accept(Bundle)}
+ * if the user accepts. If the user doesn't accept, no further action is required.
+ *
+ * @see #EXTRA_PIN_ITEM_REQUEST
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CONFIRM_PIN_APPWIDGET =
+ "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+
+ /**
+ * An extra for {@link #ACTION_CONFIRM_PIN_SHORTCUT} & {@link #ACTION_CONFIRM_PIN_APPWIDGET}
+ * containing a {@link PinItemRequest} of appropriate type asked to pin.
+ *
+ * <p>A helper function {@link #getPinItemRequest(Intent)} can be used
+ * instead of using this constant directly.
+ *
+ * @see #ACTION_CONFIRM_PIN_SHORTCUT
+ * @see #ACTION_CONFIRM_PIN_APPWIDGET
+ */
+ public static final String EXTRA_PIN_ITEM_REQUEST =
+ "android.content.pm.extra.PIN_ITEM_REQUEST";
+
+ /**
+ * Cache shortcuts which are used in notifications.
+ * @hide
+ */
+ public static final int FLAG_CACHE_NOTIFICATION_SHORTCUTS = 0;
+
+ /**
+ * Cache shortcuts which are used in bubbles.
+ * @hide
+ */
+ public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1;
+
+ /**
+ * Cache shortcuts which are used in People Tile.
+ * @hide
+ */
+ public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
+ FLAG_CACHE_NOTIFICATION_SHORTCUTS,
+ FLAG_CACHE_BUBBLE_SHORTCUTS,
+ FLAG_CACHE_PEOPLE_TILE_SHORTCUTS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShortcutCacheFlags {}
+
+ private final Context mContext;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final ILauncherApps mService;
+ @UnsupportedAppUsage
+ private final PackageManager mPm;
+ private final UserManager mUserManager;
+
+ private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>();
+ private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
+
+ private final Map<ShortcutChangeCallback, Pair<Executor, IShortcutChangeCallback>>
+ mShortcutChangeCallbacks = new HashMap<>();
+
+ /**
+ * Callbacks for package changes to this and related managed profiles.
+ */
+ public static abstract class Callback {
+ /**
+ * Indicates that a package was removed from the specified profile.
+ *
+ * If a package is removed while being updated onPackageChanged will be
+ * called instead.
+ *
+ * @param packageName The name of the package that was removed.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ abstract public void onPackageRemoved(String packageName, UserHandle user);
+
+ /**
+ * Indicates that a package was added to the specified profile.
+ *
+ * If a package is added while being updated then onPackageChanged will be
+ * called instead.
+ *
+ * @param packageName The name of the package that was added.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ abstract public void onPackageAdded(String packageName, UserHandle user);
+
+ /**
+ * Indicates that a package was modified in the specified profile.
+ * This can happen, for example, when the package is updated or when
+ * one or more components are enabled or disabled.
+ *
+ * @param packageName The name of the package that has changed.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ abstract public void onPackageChanged(String packageName, UserHandle user);
+
+ /**
+ * Indicates that one or more packages have become available. For
+ * example, this can happen when a removable storage card has
+ * reappeared.
+ *
+ * @param packageNames The names of the packages that have become
+ * available.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param replacing Indicates whether these packages are replacing
+ * existing ones.
+ */
+ abstract public void onPackagesAvailable(String[] packageNames, UserHandle user,
+ boolean replacing);
+
+ /**
+ * Indicates that one or more packages have become unavailable. For
+ * example, this can happen when a removable storage card has been
+ * removed.
+ *
+ * @param packageNames The names of the packages that have become
+ * unavailable.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param replacing Indicates whether the packages are about to be
+ * replaced with new versions.
+ */
+ abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing);
+
+ /**
+ * Indicates that one or more packages have been suspended. For
+ * 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[], UserHandle, Bundle)} 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.
+ */
+ public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+ }
+
+ /**
+ * Indicates that one or more packages have been suspended. A device administrator or an app
+ * with {@code android.permission.SUSPEND_APPS} can do this.
+ *
+ * <p>A suspending app with the permission {@code android.permission.SUSPEND_APPS} can
+ * optionally provide a {@link Bundle} of extra information that it deems helpful for the
+ * launcher to handle the suspended state of these packages. The contents of this
+ * {@link Bundle} are supposed to be a contract between the suspending app and the launcher.
+ *
+ * @param packageNames The names of the packages that have just been suspended.
+ * @param user the user for which the given packages were suspended.
+ * @param launcherExtras A {@link Bundle} of extras for the launcher, if provided to the
+ * system, {@code null} otherwise.
+ * @see PackageManager#isPackageSuspended()
+ * @see #getSuspendedPackageLauncherExtras(String, UserHandle)
+ * @deprecated {@code launcherExtras} should be obtained by using
+ * {@link #getSuspendedPackageLauncherExtras(String, UserHandle)}. For all other cases,
+ * {@link #onPackagesSuspended(String[], UserHandle)} should be used.
+ */
+ @Deprecated
+ public void onPackagesSuspended(String[] packageNames, UserHandle user,
+ @Nullable Bundle launcherExtras) {
+ onPackagesSuspended(packageNames, user);
+ }
+
+ /**
+ * Indicates that one or more packages have been unsuspended. For
+ * example, this can happen when a Device Administrator unsuspends
+ * an applicaton.
+ *
+ * @param packageNames The names of the packages that have just been
+ * unsuspended.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ }
+
+ /**
+ * Indicates that one or more shortcuts of any kind (dynamic, pinned, or manifest)
+ * have been added, updated or removed.
+ *
+ * <p>Only the applications that are allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+ *
+ * @param packageName The name of the package that has the shortcuts.
+ * @param shortcuts All shortcuts from the package (dynamic, manifest and/or pinned).
+ * Only "key" information will be provided, as defined in
+ * {@link ShortcutInfo#hasKeyFieldsOnly()}.
+ * @param user The UserHandle of the profile that generated the change.
+ *
+ * @see ShortcutManager
+ */
+ public void onShortcutsChanged(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+ }
+
+ /**
+ * Indicates that the loading progress of an installed package has changed.
+ *
+ * @param packageName The name of the package that has changed.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param progress The new progress value, between [0, 1].
+ */
+ public void onPackageLoadingProgressChanged(@NonNull String packageName,
+ @NonNull UserHandle user, float progress) {}
+ }
+
+ /**
+ * Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
+ */
+ public static class ShortcutQuery {
+ /**
+ * Include dynamic shortcuts in the result.
+ */
+ public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
+
+ /** @hide kept for unit tests */
+ @Deprecated
+ public static final int FLAG_GET_DYNAMIC = FLAG_MATCH_DYNAMIC;
+
+ /**
+ * Include pinned shortcuts in the result.
+ *
+ * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
+ * user owns on the launcher (or by other launchers, in case the user has multiple), use
+ * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
+ *
+ * <p>If you're a regular launcher app, there's no way to get shortcuts pinned by other
+ * launchers, and {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} will be ignored. So use this
+ * flag to get own pinned shortcuts.
+ */
+ public static final int FLAG_MATCH_PINNED = 1 << 1;
+
+ /** @hide kept for unit tests */
+ @Deprecated
+ public static final int FLAG_GET_PINNED = FLAG_MATCH_PINNED;
+
+ /**
+ * Include manifest shortcuts in the result.
+ */
+ public static final int FLAG_MATCH_MANIFEST = 1 << 3;
+
+ /**
+ * Include cached shortcuts in the result.
+ */
+ public static final int FLAG_MATCH_CACHED = 1 << 4;
+
+ /** @hide kept for unit tests */
+ @Deprecated
+ public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
+
+ /**
+ * Include all pinned shortcuts by any launchers, not just by the caller,
+ * in the result.
+ *
+ * <p>The caller must be the selected assistant app to use this flag, or have the system
+ * {@code ACCESS_SHORTCUTS} permission.
+ *
+ * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
+ * user owns on the launcher (or by other launchers, in case the user has multiple), use
+ * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
+ *
+ * <p>If you're a regular launcher app (or any app that's not the selected assistant app)
+ * then this flag will be ignored.
+ */
+ public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
+
+ /**
+ * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_CACHED
+ * @hide
+ */
+ public static final int FLAG_MATCH_ALL_KINDS =
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_CACHED;
+
+ /**
+ * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED
+ * @hide
+ */
+ public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
+ FLAG_MATCH_ALL_KINDS | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
+
+ /** @hide kept for unit tests */
+ @Deprecated
+ public static final int FLAG_GET_ALL_KINDS = FLAG_MATCH_ALL_KINDS;
+
+ /**
+ * Requests "key" fields only. See {@link ShortcutInfo#hasKeyFieldsOnly()}'s javadoc to
+ * see which fields fields "key".
+ * This allows quicker access to shortcut information in order to
+ * determine whether the caller's in-memory cache needs to be updated.
+ *
+ * <p>Typically, launcher applications cache all or most shortcut information
+ * in memory in order to show shortcuts without a delay.
+ *
+ * When a given launcher application wants to update its cache, such as when its process
+ * restarts, it can fetch shortcut information with this flag.
+ * The application can then check {@link ShortcutInfo#getLastChangedTimestamp()} for each
+ * shortcut, fetching a shortcut's non-key information only if that shortcut has been
+ * updated.
+ *
+ * @see ShortcutManager
+ */
+ public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
+
+ /**
+ * Populate the persons field in the result. See {@link ShortcutInfo#getPersons()}.
+ *
+ * <p>The caller must have the system {@code ACCESS_SHORTCUTS} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+ public static final int FLAG_GET_PERSONS_DATA = 1 << 11;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_MATCH_DYNAMIC,
+ FLAG_MATCH_PINNED,
+ FLAG_MATCH_MANIFEST,
+ FLAG_MATCH_CACHED,
+ FLAG_MATCH_PINNED_BY_ANY_LAUNCHER,
+ FLAG_GET_KEY_FIELDS_ONLY,
+ FLAG_GET_PERSONS_DATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface QueryFlags {}
+
+ long mChangedSince;
+
+ @Nullable
+ String mPackage;
+
+ @Nullable
+ List<String> mShortcutIds;
+
+ @Nullable
+ List<LocusId> mLocusIds;
+
+ @Nullable
+ ComponentName mActivity;
+
+ @QueryFlags
+ int mQueryFlags;
+
+ public ShortcutQuery() {
+ }
+
+ /**
+ * If non-zero, returns only shortcuts that have been added or updated
+ * since the given timestamp, expressed in milliseconds since the Epoch—see
+ * {@link System#currentTimeMillis()}.
+ */
+ public ShortcutQuery setChangedSince(long changedSince) {
+ mChangedSince = changedSince;
+ return this;
+ }
+
+ /**
+ * If non-null, returns only shortcuts from the package.
+ */
+ public ShortcutQuery setPackage(@Nullable String packageName) {
+ mPackage = packageName;
+ return this;
+ }
+
+ /**
+ * If non-null, return only the specified shortcuts by ID. When setting this field,
+ * a package name must also be set with {@link #setPackage}.
+ */
+ public ShortcutQuery setShortcutIds(@Nullable List<String> shortcutIds) {
+ mShortcutIds = shortcutIds;
+ return this;
+ }
+
+ /**
+ * If non-null, return only the specified shortcuts by locus ID. When setting this field,
+ * a package name must also be set with {@link #setPackage}.
+ */
+ @NonNull
+ public ShortcutQuery setLocusIds(@Nullable List<LocusId> locusIds) {
+ mLocusIds = locusIds;
+ return this;
+ }
+
+ /**
+ * If non-null, returns only shortcuts associated with the activity; i.e.
+ * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
+ * to {@code activity}.
+ */
+ public ShortcutQuery setActivity(@Nullable ComponentName activity) {
+ mActivity = activity;
+ return this;
+ }
+
+ /**
+ * Set query options. At least one of the {@code MATCH} flags should be set. Otherwise,
+ * no shortcuts will be returned.
+ *
+ * <ul>
+ * <li>{@link #FLAG_MATCH_DYNAMIC}
+ * <li>{@link #FLAG_MATCH_PINNED}
+ * <li>{@link #FLAG_MATCH_MANIFEST}
+ * <li>{@link #FLAG_MATCH_CACHED}
+ * <li>{@link #FLAG_GET_KEY_FIELDS_ONLY}
+ * </ul>
+ */
+ public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
+ mQueryFlags = queryFlags;
+ return this;
+ }
+ }
+
+ /**
+ * Callbacks for shortcut changes to this and related managed profiles.
+ *
+ * @hide
+ */
+ public interface ShortcutChangeCallback {
+ /**
+ * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to
+ * register this callback, have been added or updated.
+ * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery,
+ * Executor)
+ *
+ * <p>Only the applications that are allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+ *
+ * @param packageName The name of the package that has the shortcuts.
+ * @param shortcuts Shortcuts from the package that have updated or added. Only "key"
+ * information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+ * @param user The UserHandle of the profile that generated the change.
+ *
+ * @see ShortcutManager
+ */
+ default void onShortcutsAddedOrUpdated(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
+
+ /**
+ * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to
+ * register this callback, have been removed.
+ * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery,
+ * Executor)
+ *
+ * <p>Only the applications that are allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+ *
+ * @param packageName The name of the package that has the shortcuts.
+ * @param shortcuts Shortcuts from the package that have been removed. Only "key"
+ * information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+ * @param user The UserHandle of the profile that generated the change.
+ *
+ * @see ShortcutManager
+ */
+ default void onShortcutsRemoved(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
+ }
+
+ /**
+ * Callback proxy class for {@link ShortcutChangeCallback}
+ *
+ * @hide
+ */
+ private static class ShortcutChangeCallbackProxy extends
+ android.content.pm.IShortcutChangeCallback.Stub {
+ private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences;
+
+ ShortcutChangeCallbackProxy(Executor executor, ShortcutChangeCallback callback) {
+ mRemoteReferences = new WeakReference<>(new Pair<>(executor, callback));
+ }
+
+ @Override
+ public void onShortcutsAddedOrUpdated(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+ Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get();
+ if (remoteReferences == null) {
+ // Binder is dead.
+ return;
+ }
+
+ final Executor executor = remoteReferences.first;
+ final ShortcutChangeCallback callback = remoteReferences.second;
+ executor.execute(
+ PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsAddedOrUpdated,
+ callback, packageName, shortcuts, user).recycleOnUse());
+ }
+
+ @Override
+ public void onShortcutsRemoved(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+ Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get();
+ if (remoteReferences == null) {
+ // Binder is dead.
+ return;
+ }
+
+ final Executor executor = remoteReferences.first;
+ final ShortcutChangeCallback callback = remoteReferences.second;
+ executor.execute(
+ PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsRemoved,
+ callback, packageName, shortcuts, user).recycleOnUse());
+ }
+ }
+
+ /** @hide */
+ public LauncherApps(Context context, ILauncherApps service) {
+ mContext = context;
+ mService = service;
+ mPm = context.getPackageManager();
+ mUserManager = context.getSystemService(UserManager.class);
+ }
+
+ /** @hide */
+ @TestApi
+ public LauncherApps(Context context) {
+ this(context, ILauncherApps.Stub.asInterface(
+ ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE)));
+ }
+
+ /**
+ * Show an error log on logcat, when the calling user is a managed profile, the target
+ * user is different from the calling user, and it is not called from a package that has the
+ * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help
+ * developers to detect it.
+ */
+ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) {
+ if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()
+ && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed.");
+ }
+ }
+
+ /**
+ * Return a list of profiles that the caller can access via the {@link LauncherApps} APIs.
+ *
+ * <p>If the caller is running on a managed profile, it'll return only the current profile.
+ * Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
+ */
+ public List<UserHandle> getProfiles() {
+ if (mUserManager.isManagedProfile()) {
+ // If it's a managed profile, only return the current profile.
+ final List result = new ArrayList(1);
+ result.add(android.os.Process.myUserHandle());
+ return result;
+ } else {
+ return mUserManager.getUserProfiles();
+ }
+ }
+
+ /**
+ * Retrieves a list of activities that specify {@link Intent#ACTION_MAIN} and
+ * {@link Intent#CATEGORY_LAUNCHER}, across all apps, for a specified user. If an app doesn't
+ * have any activities that specify <code>ACTION_MAIN</code> or <code>CATEGORY_LAUNCHER</code>,
+ * the system adds a synthesized activity to the list. This synthesized activity represents the
+ * app's details page within system settings.
+ *
+ * <p class="note"><b>Note: </b>It's possible for system apps, such as app stores, to prevent
+ * the system from adding synthesized activities to the returned list.</p>
+ *
+ * <p>As of <a href="/reference/android/os/Build.VERSION_CODES.html#Q">Android Q</a>, at least
+ * one of the app's activities or synthesized activities appears in the returned list unless the
+ * app satisfies at least one of the following conditions:</p>
+ * <ul>
+ * <li>The app is a system app.</li>
+ * <li>The app doesn't request any <a href="/guide/topics/permissions/overview">permissions</a>.
+ * </li>
+ * <li>The app doesn't have a <em>launcher activity</em> that is enabled by default. A launcher
+ * activity has an intent containing the <code>ACTION_MAIN</code> action and the
+ * <code>CATEGORY_LAUNCHER</code> category.</li>
+ * </ul>
+ *
+ * <p>Additionally, the system hides synthesized activities for some or all apps in the
+ * following enterprise-related cases:</p>
+ * <ul>
+ * <li>If the device is a
+ * <a href="https://developers.google.com/android/work/overview#company-owned-devices-for-knowledge-workers">fully
+ * managed device</a>, no synthesized activities for any app appear in the returned list.</li>
+ * <li>If the current user has a
+ * <a href="https://developers.google.com/android/work/overview#employee-owned-devices-byod">work
+ * profile</a>, no synthesized activities for the user's work apps appear in the returned
+ * list.</li>
+ * </ul>
+ *
+ * @param packageName The specific package to query. If null, it checks all installed packages
+ * in the profile.
+ * @param user The UserHandle of the profile.
+ * @return List of launchable activities. Can be an empty list but will not be null.
+ */
+ public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
+ packageName, user), user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a PendingIntent that would start the same activity started from
+ * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param startActivityOptions Options to pass to startActivity
+ * @param user The UserHandle of the profile
+ * @hide
+ */
+ @Nullable
+ public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
+ @Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ if (DEBUG) {
+ Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
+ }
+ try {
+ return mService.getActivityLaunchIntent(component, startActivityOptions, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
+ * returns null.
+ *
+ * @param intent The intent to find a match for.
+ * @param user The profile to look in for a match.
+ * @return An activity info object if there is a match.
+ */
+ public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ LauncherActivityInfoInternal ai = mService.resolveLauncherActivityInternal(
+ mContext.getPackageName(), intent.getComponent(), user);
+ if (ai == null) {
+ return null;
+ }
+ return new LauncherActivityInfo(mContext, user, ai);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts a Main activity in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param user The UserHandle of the profile
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ */
+ public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds,
+ Bundle opts) {
+ logErrorForInvalidProfileAccess(user);
+ if (DEBUG) {
+ Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier());
+ }
+ try {
+ mService.startActivityAsUser(mContext.getIApplicationThread(),
+ mContext.getPackageName(), mContext.getAttributionTag(),
+ component, sourceBounds, opts, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts an activity to show the details of the specified session.
+ *
+ * @param sessionInfo The SessionInfo of the session
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ */
+ public void startPackageInstallerSessionDetailsActivity(@NonNull SessionInfo sessionInfo,
+ @Nullable Rect sourceBounds, @Nullable Bundle opts) {
+ try {
+ mService.startSessionDetailsActivityAsUser(mContext.getIApplicationThread(),
+ mContext.getPackageName(), mContext.getAttributionTag(), sessionInfo,
+ sourceBounds, opts, sessionInfo.getUser());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts the settings activity to show the application details for a
+ * package in the specified profile.
+ *
+ * @param component The ComponentName of the package to launch settings for.
+ * @param user The UserHandle of the profile
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ */
+ public void startAppDetailsActivity(ComponentName component, UserHandle user,
+ Rect sourceBounds, Bundle opts) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.showAppDetailsAsUser(mContext.getIApplicationThread(),
+ mContext.getPackageName(), mContext.getAttributionTag(),
+ component, sourceBounds, opts, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns PendingIntent associated with specified shortcut.
+ *
+ * @param packageName The packageName of the shortcut
+ * @param shortcutId The id of the shortcut
+ * @param opts Options to pass to the PendingIntent
+ * @param user The UserHandle of the profile
+ */
+ @Nullable
+ public PendingIntent getShortcutIntent(@NonNull final String packageName,
+ @NonNull final String shortcutId, @Nullable final Bundle opts,
+ @NonNull final UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ if (DEBUG) {
+ Log.i(TAG, "GetShortcutIntent " + packageName + "/" + shortcutId + " " + user);
+ }
+ try {
+ return mService.getShortcutIntent(
+ mContext.getPackageName(), packageName, shortcutId, opts, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieves a list of config activities for creating {@link ShortcutInfo}.
+ *
+ * @param packageName The specific package to query. If null, it checks all installed packages
+ * in the profile.
+ * @param user The UserHandle of the profile.
+ * @return List of config activities. Can be an empty list but will not be null.
+ *
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ * @see #getShortcutConfigActivityIntent(LauncherActivityInfo)
+ */
+ public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName,
+ @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ return convertToActivityList(mService.getShortcutConfigActivities(
+ mContext.getPackageName(), packageName, user),
+ user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ private List<LauncherActivityInfo> convertToActivityList(
+ @Nullable ParceledListSlice<LauncherActivityInfoInternal> internals, UserHandle user) {
+ if (internals == null || internals.getList().isEmpty()) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
+ for (LauncherActivityInfoInternal internal : internals.getList()) {
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, user, internal);
+ if (DEBUG) {
+ Log.v(TAG, "Returning activity for profile " + user + " : "
+ + lai.getComponentName());
+ }
+ lais.add(lai);
+ }
+ return lais;
+ }
+
+ /**
+ * Returns an intent sender which can be used to start the configure activity for creating
+ * custom shortcuts. Use this method if the provider is in another profile as you are not
+ * allowed to start an activity in another profile.
+ *
+ * <p>The caller should receive {@link PinItemRequest} in onActivityResult on
+ * {@link android.app.Activity#RESULT_OK}.
+ *
+ * <p>Callers must be allowed to access the shortcut information, as defined in {@link
+ * #hasShortcutHostPermission()}.
+ *
+ * @param info a configuration activity returned by {@link #getShortcutConfigActivityList}
+ *
+ * @throws IllegalStateException when the user is locked or not running.
+ * @throws SecurityException if {@link #hasShortcutHostPermission()} is false.
+ *
+ * @see #getPinItemRequest(Intent)
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ * @see android.app.Activity#startIntentSenderForResult
+ */
+ @Nullable
+ public IntentSender getShortcutConfigActivityIntent(@NonNull LauncherActivityInfo info) {
+ try {
+ return mService.getShortcutConfigActivityIntent(
+ mContext.getPackageName(), info.getComponentName(), info.getUser());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the package is installed and enabled for a profile.
+ *
+ * @param packageName The package to check.
+ * @param user The UserHandle of the profile.
+ *
+ * @return true if the package exists and is enabled.
+ */
+ public boolean isPackageEnabled(String packageName, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ return mService.isPackageEnabled(mContext.getPackageName(), packageName, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the launcher extras supplied to the system when the given package was suspended via
+ * {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+ * PersistableBundle, String)}.
+ *
+ * <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending
+ * app and the launcher.
+ *
+ * <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[], UserHandle, Bundle)
+ * @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 whether a package should be hidden from suggestions to the user. Currently, this
+ * could be done because the package was marked as distracting to the user via
+ * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
+ *
+ * @param packageName The package for which to check.
+ * @param user the {@link UserHandle} of the profile.
+ * @return
+ */
+ public boolean shouldHideFromSuggestions(@NonNull String packageName,
+ @NonNull UserHandle user) {
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(user, "user");
+ try {
+ return mService.shouldHideFromSuggestions(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
+ * @param flags Additional option flags {@link PackageManager#getApplicationInfo}
+ * @param user The UserHandle of the profile.
+ *
+ * @return {@link ApplicationInfo} containing information about the package. Returns
+ * {@code null} if the package isn't installed for the given profile, or the profile
+ * isn't enabled.
+ */
+ public ApplicationInfo getApplicationInfo(@NonNull String packageName,
+ @ApplicationInfoFlags int flags, @NonNull UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(user, "user");
+ logErrorForInvalidProfileAccess(user);
+ try {
+ final ApplicationInfo ai = mService
+ .getApplicationInfo(mContext.getPackageName(), packageName, flags, user);
+ if (ai == null) {
+ throw new NameNotFoundException("Package " + packageName + " not found for user "
+ + user.getIdentifier());
+ }
+ return ai;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an object describing the app usage limit for the given package.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user of the package
+ *
+ * @return an {@link AppUsageLimit} object describing the app time limit containing
+ * the given package with the smallest time remaining, or {@code null} if none exist.
+ * @throws SecurityException when the caller is not the recents app.
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public LauncherApps.AppUsageLimit getAppUsageLimit(@NonNull String packageName,
+ @NonNull UserHandle user) {
+ try {
+ return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the activity exists and it enabled for a profile.
+ *
+ * <p>The activity may still not be exported, in which case {@link #startMainActivity} will
+ * throw a {@link SecurityException} unless the caller has the same UID as the target app's.
+ *
+ * @param component The activity to check.
+ * @param user The UserHandle of the profile.
+ *
+ * @return true if the activity exists and is enabled.
+ */
+ public boolean isActivityEnabled(ComponentName component, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ return mService.isActivityEnabled(mContext.getPackageName(), component, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the caller can access the shortcut information. Access is currently
+ * available to:
+ *
+ * <ul>
+ * <li>The current launcher (or default launcher if there is no set current launcher).</li>
+ * <li>The currently active voice interaction service.</li>
+ * </ul>
+ *
+ * <p>Note when this method returns {@code false}, it may be a temporary situation because
+ * the user is trying a new launcher application. The user may decide to change the default
+ * launcher back to the calling application again, so even if a launcher application loses
+ * this permission, it does <b>not</b> have to purge pinned shortcut information.
+ * If the calling launcher application contains pinned shortcuts, they will still work,
+ * even though the caller no longer has the shortcut host permission.
+ *
+ * @throws IllegalStateException when the user is locked.
+ *
+ * @see ShortcutManager
+ */
+ public boolean hasShortcutHostPermission() {
+ try {
+ return mService.hasShortcutHostPermission(mContext.getPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) {
+ if (shortcuts == null) {
+ return null;
+ }
+ for (int i = shortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = shortcuts.get(i);
+ final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext,
+ si.getDisabledReason());
+ if (message != null) {
+ si.setDisabledMessage(message);
+ }
+ }
+ return shortcuts;
+ }
+
+ /**
+ * Returns {@link ShortcutInfo}s that match {@code query}.
+ *
+ * <p>Callers must be allowed to access the shortcut information, as defined in {@link
+ * #hasShortcutHostPermission()}.
+ *
+ * @param query result includes shortcuts matching this query.
+ * @param user The UserHandle of the profile.
+ *
+ * @return the IDs of {@link ShortcutInfo}s that match the query.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ */
+ @Nullable
+ public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
+ @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ // Note this is the only case we need to update the disabled message for shortcuts
+ // that weren't restored.
+ // The restore problem messages are only shown by the user, and publishers will never
+ // see them. The only other API that the launcher gets shortcuts is the shortcut
+ // changed callback, but that only returns shortcuts with the "key" information, so
+ // that won't return disabled message.
+ return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
+ new ShortcutQueryWrapper(query), user)
+ .getList());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide // No longer used. Use getShortcuts() instead. Kept for unit tests.
+ */
+ @Nullable
+ @Deprecated
+ public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName,
+ @NonNull List<String> ids, @NonNull UserHandle user) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setPackage(packageName);
+ q.setShortcutIds(ids);
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
+ return getShortcuts(q, user);
+ }
+
+ /**
+ * Pin shortcuts on a package.
+ *
+ * <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package.
+ * However, different launchers may have different set of pinned shortcuts.
+ *
+ * <p>The calling launcher application must be allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}.
+ *
+ * @param packageName The target package name.
+ * @param shortcutIds The IDs of the shortcut to be pinned.
+ * @param user The UserHandle of the profile.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ */
+ public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+ @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Mark shortcuts as cached for a package.
+ *
+ * <p>Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts
+ * in the list will be ignored.
+ *
+ * <p>Unlike pinned shortcuts, where different callers can have different sets of pinned
+ * shortcuts, cached state is per shortcut only, and even if multiple callers cache the same
+ * shortcut, it can be uncached by any valid caller.
+ *
+ * @param packageName The target package name.
+ * @param shortcutIds The IDs of the shortcut to be cached.
+ * @param user The UserHandle of the profile.
+ * @param cacheFlags One of the values in:
+ * <ul>
+ * <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
+ * </ul>
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+ public void cacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+ @NonNull UserHandle user, @ShortcutCacheFlags int cacheFlags) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.cacheShortcuts(
+ mContext.getPackageName(), packageName, shortcutIds, user, cacheFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove cached flag from shortcuts for a package.
+ *
+ * @param packageName The target package name.
+ * @param shortcutIds The IDs of the shortcut to be uncached.
+ * @param user The UserHandle of the profile.
+ * @param cacheFlags One of the values in:
+ * <ul>
+ * <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
+ * </ul>
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
+ public void uncacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
+ @NonNull UserHandle user, @ShortcutCacheFlags int cacheFlags) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.uncacheShortcuts(
+ mContext.getPackageName(), packageName, shortcutIds, user, cacheFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide kept for testing.
+ */
+ @Deprecated
+ public int getShortcutIconResId(@NonNull ShortcutInfo shortcut) {
+ return shortcut.getIconResourceId();
+ }
+
+ /**
+ * @hide kept for testing.
+ */
+ @Deprecated
+ public int getShortcutIconResId(@NonNull String packageName, @NonNull String shortcutId,
+ @NonNull UserHandle user) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setPackage(packageName);
+ q.setShortcutIds(Arrays.asList(shortcutId));
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
+ final List<ShortcutInfo> shortcuts = getShortcuts(q, user);
+
+ return shortcuts.size() > 0 ? shortcuts.get(0).getIconResourceId() : 0;
+ }
+
+ /**
+ * @hide internal/unit tests only
+ */
+ public ParcelFileDescriptor getShortcutIconFd(
+ @NonNull ShortcutInfo shortcut) {
+ return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
+ shortcut.getUserId());
+ }
+
+ /**
+ * @hide internal/unit tests only
+ */
+ public ParcelFileDescriptor getShortcutIconFd(
+ @NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) {
+ return getShortcutIconFd(packageName, shortcutId, user.getIdentifier());
+ }
+
+ private ParcelFileDescriptor getShortcutIconFd(
+ @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ try {
+ return mService.getShortcutIconFd(mContext.getPackageName(),
+ packageName, shortcutId, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide internal/unit tests only
+ */
+ @VisibleForTesting
+ public ParcelFileDescriptor getUriShortcutIconFd(@NonNull ShortcutInfo shortcut) {
+ return getUriShortcutIconFd(shortcut.getPackage(), shortcut.getId(), shortcut.getUserId());
+ }
+
+ private ParcelFileDescriptor getUriShortcutIconFd(@NonNull String packageName,
+ @NonNull String shortcutId, int userId) {
+ String uri = getShortcutIconUri(packageName, shortcutId, userId);
+ if (uri == null) {
+ return null;
+ }
+ try {
+ return mContext.getContentResolver().openFileDescriptor(Uri.parse(uri), "r");
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Icon file not found: " + uri);
+ return null;
+ }
+ }
+
+ private String getShortcutIconUri(@NonNull String packageName,
+ @NonNull String shortcutId, int userId) {
+ String uri = null;
+ try {
+ uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId,
+ userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return uri;
+ }
+
+ /**
+ * Returns the icon for this shortcut, without any badging for the profile.
+ *
+ * <p>The calling launcher application must be allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}.
+ *
+ * @param density The preferred density of the icon, zero for default density. Use
+ * density DPI values from {@link DisplayMetrics}.
+ *
+ * @return The drawable associated with the shortcut.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ * @see #getShortcutBadgedIconDrawable(ShortcutInfo, int)
+ * @see DisplayMetrics
+ */
+ public Drawable getShortcutIconDrawable(@NonNull ShortcutInfo shortcut, int density) {
+ if (shortcut.hasIconFile()) {
+ final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
+ return loadDrawableFromFileDescriptor(pfd, shortcut.hasAdaptiveBitmap());
+ } else if (shortcut.hasIconUri()) {
+ final ParcelFileDescriptor pfd = getUriShortcutIconFd(shortcut);
+ return loadDrawableFromFileDescriptor(pfd, shortcut.hasAdaptiveBitmap());
+ } else if (shortcut.hasIconResource()) {
+ return loadDrawableResourceFromPackage(shortcut.getPackage(),
+ shortcut.getIconResourceId(), shortcut.getUserHandle(), density);
+ } else if (shortcut.getIcon() != null) {
+ // This happens if a shortcut is pending-approval.
+ final Icon icon = shortcut.getIcon();
+ switch (icon.getType()) {
+ case Icon.TYPE_RESOURCE: {
+ return loadDrawableResourceFromPackage(shortcut.getPackage(),
+ icon.getResId(), shortcut.getUserHandle(), density);
+ }
+ case Icon.TYPE_BITMAP:
+ case Icon.TYPE_ADAPTIVE_BITMAP: {
+ return icon.loadDrawable(mContext);
+ }
+ default:
+ return null; // Shouldn't happen though.
+ }
+ } else {
+ return null; // Has no icon.
+ }
+ }
+
+ private Drawable loadDrawableFromFileDescriptor(ParcelFileDescriptor pfd, boolean adaptive) {
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+ if (bmp != null) {
+ BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
+ if (adaptive) {
+ return new AdaptiveIconDrawable(null, dr);
+ } else {
+ return dr;
+ }
+ }
+ return null;
+ } finally {
+ try {
+ pfd.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Icon getShortcutIcon(@NonNull ShortcutInfo shortcut) {
+ if (shortcut.hasIconFile()) {
+ final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+ if (bmp != null) {
+ if (shortcut.hasAdaptiveBitmap()) {
+ return Icon.createWithAdaptiveBitmap(bmp);
+ } else {
+ return Icon.createWithBitmap(bmp);
+ }
+ }
+ return null;
+ } finally {
+ try {
+ pfd.close();
+ } catch (IOException ignore) {
+ }
+ }
+ } else if (shortcut.hasIconUri()) {
+ String uri = getShortcutIconUri(shortcut.getPackage(), shortcut.getId(),
+ shortcut.getUserId());
+ if (uri == null) {
+ return null;
+ }
+ if (shortcut.hasAdaptiveBitmap()) {
+ return Icon.createWithAdaptiveBitmapContentUri(uri);
+ } else {
+ return Icon.createWithContentUri(uri);
+ }
+ } else if (shortcut.hasIconResource()) {
+ return Icon.createWithResource(shortcut.getPackage(), shortcut.getIconResourceId());
+ } else {
+ return shortcut.getIcon();
+ }
+ }
+
+ private Drawable loadDrawableResourceFromPackage(String packageName, int resId,
+ UserHandle user, int density) {
+ try {
+ if (resId == 0) {
+ return null; // Shouldn't happen but just in case.
+ }
+ final ApplicationInfo ai = getApplicationInfo(packageName, /* flags =*/ 0, user);
+ final Resources res = mContext.getPackageManager().getResourcesForApplication(ai);
+ return res.getDrawableForDensity(resId, density);
+ } catch (NameNotFoundException | Resources.NotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the shortcut icon with badging appropriate for the profile.
+ *
+ * <p>The calling launcher application must be allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}.
+ *
+ * @param density Optional density for the icon, or 0 to use the default density. Use
+ * @return A badged icon for the shortcut.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @see ShortcutManager
+ * @see #getShortcutIconDrawable(ShortcutInfo, int)
+ * @see DisplayMetrics
+ */
+ public Drawable getShortcutBadgedIconDrawable(ShortcutInfo shortcut, int density) {
+ final Drawable originalIcon = getShortcutIconDrawable(shortcut, density);
+
+ return (originalIcon == null) ? null : mContext.getPackageManager().getUserBadgedIcon(
+ originalIcon, shortcut.getUserHandle());
+ }
+
+ /**
+ * Starts a shortcut.
+ *
+ * <p>The calling launcher application must be allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}.
+ *
+ * @param packageName The target shortcut package name.
+ * @param shortcutId The target shortcut ID.
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon.
+ * @param startActivityOptions Options to pass to startActivity.
+ * @param user The UserHandle of the profile.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
+ * the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
+ */
+ public void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
+ @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
+ @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+
+ startShortcut(packageName, shortcutId, sourceBounds, startActivityOptions,
+ user.getIdentifier());
+ }
+
+ /**
+ * Launches a shortcut.
+ *
+ * <p>The calling launcher application must be allowed to access the shortcut information,
+ * as defined in {@link #hasShortcutHostPermission()}.
+ *
+ * @param shortcut The target shortcut.
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon.
+ * @param startActivityOptions Options to pass to startActivity.
+ * @throws IllegalStateException when the user is locked, or when the {@code user} user
+ * is locked or not running.
+ *
+ * @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
+ * the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
+ */
+ public void startShortcut(@NonNull ShortcutInfo shortcut,
+ @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+ startShortcut(shortcut.getPackage(), shortcut.getId(),
+ sourceBounds, startActivityOptions,
+ shortcut.getUserId());
+ }
+
+ @UnsupportedAppUsage
+ private void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
+ @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
+ int userId) {
+ try {
+ final boolean success = mService.startShortcut(mContext.getPackageName(), packageName,
+ null /* default featureId */, shortcutId, sourceBounds, startActivityOptions,
+ userId);
+ if (!success) {
+ throw new ActivityNotFoundException("Shortcut could not be started");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a callback for changes to packages in this user and managed profiles.
+ *
+ * @param callback The callback to register.
+ */
+ public void registerCallback(Callback callback) {
+ registerCallback(callback, null);
+ }
+
+ /**
+ * Registers a callback for changes to packages in this user and managed profiles.
+ *
+ * @param callback The callback to register.
+ * @param handler that should be used to post callbacks on, may be null.
+ */
+ public void registerCallback(Callback callback, Handler handler) {
+ synchronized (this) {
+ if (callback != null && findCallbackLocked(callback) < 0) {
+ boolean addedFirstCallback = mCallbacks.size() == 0;
+ addCallbackLocked(callback, handler);
+ if (addedFirstCallback) {
+ try {
+ mService.addOnAppsChangedListener(mContext.getPackageName(),
+ mAppsChangedListener);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregisters a callback that was previously registered.
+ *
+ * @param callback The callback to unregister.
+ * @see #registerCallback(Callback)
+ */
+ public void unregisterCallback(Callback callback) {
+ synchronized (this) {
+ removeCallbackLocked(callback);
+ if (mCallbacks.size() == 0) {
+ try {
+ mService.removeOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /** @return position in mCallbacks for callback or -1 if not present. */
+ private int findCallbackLocked(Callback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback cannot be null");
+ }
+ final int size = mCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ if (mCallbacks.get(i).mCallback == callback) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void removeCallbackLocked(Callback callback) {
+ int pos = findCallbackLocked(callback);
+ if (pos >= 0) {
+ mCallbacks.remove(pos);
+ }
+ }
+
+ private void addCallbackLocked(Callback callback, Handler handler) {
+ // Remove if already present.
+ removeCallbackLocked(callback);
+ if (handler == null) {
+ handler = new Handler();
+ }
+ CallbackMessageHandler toAdd = new CallbackMessageHandler(handler.getLooper(), callback);
+ mCallbacks.add(toAdd);
+ }
+
+ private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
+
+ @Override
+ public void onPackageRemoved(UserHandle user, String packageName)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackageRemoved(packageName, user);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackageChanged(packageName, user);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackageAdded(packageName, user);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackagesAvailable(packageNames, user, replacing);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackagesUnavailable(packageNames, user, replacing);
+ }
+ }
+ }
+
+ @Override
+ 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, launcherExtras, user);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesUnsuspended(UserHandle user, String[] packageNames)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackagesUnsuspended(packageNames, user);
+ }
+ }
+ }
+
+ @Override
+ public void onShortcutChanged(UserHandle user, String packageName,
+ ParceledListSlice shortcuts) {
+ if (DEBUG) {
+ Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName);
+ }
+ final List<ShortcutInfo> list = shortcuts.getList();
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnShortcutChanged(packageName, user, list);
+ }
+ }
+ }
+
+ public void onPackageLoadingProgressChanged(UserHandle user, String packageName,
+ float progress) {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageLoadingProgressChanged " + user.getIdentifier() + ","
+ + packageName + "," + progress);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackageLoadingProgressChanged(user, packageName, progress);
+ }
+ }
+ }
+ };
+
+ private static class CallbackMessageHandler extends Handler {
+ private static final int MSG_ADDED = 1;
+ private static final int MSG_REMOVED = 2;
+ private static final int MSG_CHANGED = 3;
+ private static final int MSG_AVAILABLE = 4;
+ private static final int MSG_UNAVAILABLE = 5;
+ private static final int MSG_SUSPENDED = 6;
+ private static final int MSG_UNSUSPENDED = 7;
+ private static final int MSG_SHORTCUT_CHANGED = 8;
+ private static final int MSG_LOADING_PROGRESS_CHANGED = 9;
+
+ private LauncherApps.Callback mCallback;
+
+ private static class CallbackInfo {
+ String[] packageNames;
+ String packageName;
+ Bundle launcherExtras;
+ boolean replacing;
+ UserHandle user;
+ List<ShortcutInfo> shortcuts;
+ float mLoadingProgress;
+ }
+
+ public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
+ super(looper, null, true);
+ mCallback = callback;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mCallback == null || !(msg.obj instanceof CallbackInfo)) {
+ return;
+ }
+ CallbackInfo info = (CallbackInfo) msg.obj;
+ switch (msg.what) {
+ case MSG_ADDED:
+ mCallback.onPackageAdded(info.packageName, info.user);
+ break;
+ case MSG_REMOVED:
+ mCallback.onPackageRemoved(info.packageName, info.user);
+ break;
+ case MSG_CHANGED:
+ mCallback.onPackageChanged(info.packageName, info.user);
+ break;
+ case MSG_AVAILABLE:
+ mCallback.onPackagesAvailable(info.packageNames, info.user, info.replacing);
+ break;
+ case MSG_UNAVAILABLE:
+ mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing);
+ break;
+ case MSG_SUSPENDED:
+ mCallback.onPackagesSuspended(info.packageNames, info.user, info.launcherExtras
+ );
+ break;
+ case MSG_UNSUSPENDED:
+ mCallback.onPackagesUnsuspended(info.packageNames, info.user);
+ break;
+ case MSG_SHORTCUT_CHANGED:
+ mCallback.onShortcutsChanged(info.packageName, info.shortcuts, info.user);
+ break;
+ case MSG_LOADING_PROGRESS_CHANGED:
+ mCallback.onPackageLoadingProgressChanged(info.packageName, info.user,
+ info.mLoadingProgress);
+ break;
+ }
+ }
+
+ public void postOnPackageAdded(String packageName, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageName = packageName;
+ info.user = user;
+ obtainMessage(MSG_ADDED, info).sendToTarget();
+ }
+
+ public void postOnPackageRemoved(String packageName, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageName = packageName;
+ info.user = user;
+ obtainMessage(MSG_REMOVED, info).sendToTarget();
+ }
+
+ public void postOnPackageChanged(String packageName, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageName = packageName;
+ info.user = user;
+ obtainMessage(MSG_CHANGED, info).sendToTarget();
+ }
+
+ public void postOnPackagesAvailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageNames = packageNames;
+ info.replacing = replacing;
+ info.user = user;
+ obtainMessage(MSG_AVAILABLE, info).sendToTarget();
+ }
+
+ public void postOnPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageNames = packageNames;
+ info.replacing = replacing;
+ info.user = user;
+ obtainMessage(MSG_UNAVAILABLE, info).sendToTarget();
+ }
+
+ 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();
+ }
+
+ public void postOnPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageNames = packageNames;
+ info.user = user;
+ obtainMessage(MSG_UNSUSPENDED, info).sendToTarget();
+ }
+
+ public void postOnShortcutChanged(String packageName, UserHandle user,
+ List<ShortcutInfo> shortcuts) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageName = packageName;
+ info.user = user;
+ info.shortcuts = shortcuts;
+ obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget();
+ }
+
+ public void postOnPackageLoadingProgressChanged(UserHandle user, String packageName,
+ float progress) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageName = packageName;
+ info.user = user;
+ info.mLoadingProgress = progress;
+ obtainMessage(MSG_LOADING_PROGRESS_CHANGED, info).sendToTarget();
+ }
+ }
+
+ /**
+ * Register a callback to watch for session lifecycle events in this user and managed profiles.
+ * @param callback The callback to register.
+ * @param executor {@link Executor} to handle the callbacks, cannot be null.
+ *
+ * @see PackageInstaller#registerSessionCallback(SessionCallback)
+ */
+ public void registerPackageInstallerSessionCallback(
+ @NonNull @CallbackExecutor Executor executor, @NonNull SessionCallback callback) {
+ if (executor == null) {
+ throw new NullPointerException("Executor must not be null");
+ }
+
+ synchronized (mDelegates) {
+ final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback,
+ executor);
+ try {
+ mService.registerPackageInstallerCallback(mContext.getPackageName(),
+ delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mDelegates.add(delegate);
+ }
+ }
+
+ /**
+ * Unregisters a callback that was previously registered.
+ *
+ * @param callback The callback to unregister.
+ * @see #registerPackageInstallerSessionCallback(Executor, SessionCallback)
+ */
+ public void unregisterPackageInstallerSessionCallback(@NonNull SessionCallback callback) {
+ synchronized (mDelegates) {
+ for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ final SessionCallbackDelegate delegate = i.next();
+ if (delegate.mCallback == callback) {
+ mPm.getPackageInstaller().unregisterSessionCallback(delegate.mCallback);
+ i.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Return list of all known install sessions in this user and managed profiles, regardless
+ * of the installer.
+ *
+ * @see PackageInstaller#getAllSessions()
+ */
+ public @NonNull List<SessionInfo> getAllPackageInstallerSessions() {
+ try {
+ return mService.getAllSessions(mContext.getPackageName()).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register a callback to watch for shortcut change events in this user and managed profiles.
+ *
+ * @param callback The callback to register.
+ * @param query {@link ShortcutQuery} to match and filter the shortcut events. Only matching
+ * shortcuts will be returned by the callback.
+ * @param executor {@link Executor} to handle the callbacks. To dispatch callbacks to the main
+ * thread of your application, you can use {@link android.content.Context#getMainExecutor()}.
+ *
+ * @hide
+ */
+ public void registerShortcutChangeCallback(@NonNull ShortcutChangeCallback callback,
+ @NonNull ShortcutQuery query, @NonNull @CallbackExecutor Executor executor) {
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ Objects.requireNonNull(query, "Query cannot be null");
+ Objects.requireNonNull(executor, "Executor cannot be null");
+
+ synchronized (mShortcutChangeCallbacks) {
+ IShortcutChangeCallback proxy = new ShortcutChangeCallbackProxy(executor, callback);
+ mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy));
+ try {
+ mService.registerShortcutChangeCallback(mContext.getPackageName(),
+ new ShortcutQueryWrapper(query), proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters a callback that was previously registered.
+ * @see #registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, Executor)
+ *
+ * @param callback Callback to be unregistered.
+ *
+ * @hide
+ */
+ public void unregisterShortcutChangeCallback(@NonNull ShortcutChangeCallback callback) {
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (mShortcutChangeCallbacks) {
+ if (mShortcutChangeCallbacks.containsKey(callback)) {
+ IShortcutChangeCallback proxy = mShortcutChangeCallbacks.remove(callback).second;
+ try {
+ mService.unregisterShortcutChangeCallback(mContext.getPackageName(), proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * A helper method to extract a {@link PinItemRequest} set to
+ * the {@link #EXTRA_PIN_ITEM_REQUEST} extra.
+ */
+ public PinItemRequest getPinItemRequest(Intent intent) {
+ return intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
+ }
+
+ /**
+ * Represents a "pin shortcut" or a "pin appwidget" request made by an app, which is sent with
+ * an {@link #ACTION_CONFIRM_PIN_SHORTCUT} or {@link #ACTION_CONFIRM_PIN_APPWIDGET} intent
+ * respectively to the default launcher app.
+ *
+ * <h3>Request of the {@link #REQUEST_TYPE_SHORTCUT} type.</h3>
+ *
+ * <p>A {@link #REQUEST_TYPE_SHORTCUT} request represents a request to pin a
+ * {@link ShortcutInfo}. If the launcher accepts a request, call {@link #accept()},
+ * or {@link #accept(Bundle)} with a null or empty Bundle. No options are defined for
+ * pin-shortcuts requests.
+ *
+ * <p>{@link #getShortcutInfo()} always returns a non-null {@link ShortcutInfo} for this type.
+ *
+ * <p>The launcher may receive a request with a {@link ShortcutInfo} that is already pinned, in
+ * which case {@link ShortcutInfo#isPinned()} returns true. This means the user wants to create
+ * another pinned shortcut for a shortcut that's already pinned. If the launcher accepts it,
+ * {@link #accept()} must still be called even though the shortcut is already pinned, and
+ * create a new pinned shortcut icon for it.
+ *
+ * <p>See also {@link ShortcutManager} for more details.
+ *
+ * <h3>Request of the {@link #REQUEST_TYPE_APPWIDGET} type.</h3>
+ *
+ * <p>A {@link #REQUEST_TYPE_SHORTCUT} request represents a request to pin a
+ * an AppWidget. If the launcher accepts a request, call {@link #accept(Bundle)} with
+ * the appwidget integer ID set to the
+ * {@link android.appwidget.AppWidgetManager#EXTRA_APPWIDGET_ID} extra.
+ *
+ * <p>{@link #getAppWidgetProviderInfo(Context)} always returns a non-null
+ * {@link AppWidgetProviderInfo} for this type.
+ *
+ * <p>See also {@link AppWidgetManager} for more details.
+ *
+ * @see #EXTRA_PIN_ITEM_REQUEST
+ * @see #getPinItemRequest(Intent)
+ */
+ public static final class PinItemRequest implements Parcelable {
+
+ /** This is a request to pin shortcut. */
+ public static final int REQUEST_TYPE_SHORTCUT = 1;
+
+ /** This is a request to pin app widget. */
+ public static final int REQUEST_TYPE_APPWIDGET = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "REQUEST_TYPE_" }, value = {
+ REQUEST_TYPE_SHORTCUT,
+ REQUEST_TYPE_APPWIDGET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestType {}
+
+ private final int mRequestType;
+ private final IPinItemRequest mInner;
+
+ /**
+ * @hide
+ */
+ public PinItemRequest(IPinItemRequest inner, int type) {
+ mInner = inner;
+ mRequestType = type;
+ }
+
+ /**
+ * Represents the type of a request, which is one of the {@code REQUEST_TYPE_} constants.
+ *
+ * @return one of the {@code REQUEST_TYPE_} constants.
+ */
+ @RequestType
+ public int getRequestType() {
+ return mRequestType;
+ }
+
+ /**
+ * {@link ShortcutInfo} sent by the requesting app.
+ * Always non-null for a {@link #REQUEST_TYPE_SHORTCUT} request, and always null for a
+ * different request type.
+ *
+ * @return requested {@link ShortcutInfo} when a request is of the
+ * {@link #REQUEST_TYPE_SHORTCUT} type. Null otherwise.
+ */
+ @Nullable
+ public ShortcutInfo getShortcutInfo() {
+ try {
+ return mInner.getShortcutInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * {@link AppWidgetProviderInfo} sent by the requesting app.
+ * 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.
+ */
+ @Nullable
+ public AppWidgetProviderInfo getAppWidgetProviderInfo(Context context) {
+ try {
+ final AppWidgetProviderInfo info = mInner.getAppWidgetProviderInfo();
+ if (info == null) {
+ return null;
+ }
+ info.updateDimensions(context.getResources().getDisplayMetrics());
+ return info;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Any extras sent by the requesting app.
+ *
+ * @return For a shortcut request, this method always return null. For an AppWidget
+ * request, this method returns the extras passed to the
+ * {@link android.appwidget.AppWidgetManager#requestPinAppWidget(
+ * ComponentName, Bundle, PendingIntent)} API. See {@link AppWidgetManager} for details.
+ */
+ @Nullable
+ public Bundle getExtras() {
+ try {
+ return mInner.getExtras();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Return whether a request is still valid.
+ *
+ * @return {@code TRUE} if a request is valid and {@link #accept(Bundle)} may be called.
+ */
+ public boolean isValid() {
+ try {
+ return mInner.isValid();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Called by the receiving launcher app when the user accepts the request.
+ *
+ * @param options must be set for a {@link #REQUEST_TYPE_APPWIDGET} request.
+ *
+ * @return {@code TRUE} if the shortcut or the AppWidget has actually been pinned.
+ * {@code FALSE} if the item hasn't been pinned, for example, because the request had
+ * already been canceled, in which case the launcher must not pin the requested item.
+ */
+ public boolean accept(@Nullable Bundle options) {
+ try {
+ return mInner.accept(options);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by the receiving launcher app when the user accepts the request, with no options.
+ *
+ * @return {@code TRUE} if the shortcut or the AppWidget has actually been pinned.
+ * {@code FALSE} if the item hasn't been pinned, for example, because the request had
+ * already been canceled, in which case the launcher must not pin the requested item.
+ */
+ public boolean accept() {
+ return accept(/* options= */ null);
+ }
+
+ private PinItemRequest(Parcel source) {
+ final ClassLoader cl = getClass().getClassLoader();
+
+ mRequestType = source.readInt();
+ mInner = IPinItemRequest.Stub.asInterface(source.readStrongBinder());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRequestType);
+ dest.writeStrongBinder(mInner.asBinder());
+ }
+
+ public static final @android.annotation.NonNull Creator<PinItemRequest> CREATOR =
+ new Creator<PinItemRequest>() {
+ public PinItemRequest createFromParcel(Parcel source) {
+ return new PinItemRequest(source);
+ }
+ public PinItemRequest[] newArray(int size) {
+ return new PinItemRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
+
+ /**
+ * A class that encapsulates information about the usage limit set for an app or
+ * a group of apps.
+ *
+ * <p>The launcher can query specifics about the usage limit such as how much usage time
+ * the limit has and how much of the total usage time is remaining via the APIs available
+ * in this class.
+ *
+ * @see #getAppUsageLimit(String, UserHandle)
+ * @hide
+ */
+ @SystemApi
+ public static final class AppUsageLimit implements Parcelable {
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ /** @hide */
+ public AppUsageLimit(long totalUsageLimit, long usageRemaining) {
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ /**
+ * Returns the total usage limit in milliseconds set for an app or a group of apps.
+ *
+ * @return the total usage limit in milliseconds
+ */
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+
+ /**
+ * Returns the usage remaining in milliseconds for an app or the group of apps
+ * this limit refers to.
+ *
+ * @return the usage remaining in milliseconds
+ */
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+
+ private AppUsageLimit(Parcel source) {
+ mTotalUsageLimit = source.readLong();
+ mUsageRemaining = source.readLong();
+ }
+
+ public static final @android.annotation.NonNull Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
+ @Override
+ public AppUsageLimit createFromParcel(Parcel source) {
+ return new AppUsageLimit(source);
+ }
+
+ @Override
+ public AppUsageLimit[] newArray(int size) {
+ return new AppUsageLimit[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mTotalUsageLimit);
+ dest.writeLong(mUsageRemaining);
+ }
+ }
+}
diff --git a/android/content/pm/LimitedLengthInputStream.java b/android/content/pm/LimitedLengthInputStream.java
new file mode 100644
index 0000000..05089f6
--- /dev/null
+++ b/android/content/pm/LimitedLengthInputStream.java
@@ -0,0 +1,95 @@
+package android.content.pm;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A class that limits the amount of data that is read from an InputStream. When
+ * the specified length is reached, the stream returns an EOF even if the
+ * underlying stream still has more data.
+ *
+ * @hide
+ */
+public class LimitedLengthInputStream extends FilterInputStream {
+ /**
+ * The end of the stream where we don't want to allow more data to be read.
+ */
+ private final long mEnd;
+
+ /**
+ * Current offset in the stream.
+ */
+ private long mOffset;
+
+ /**
+ * @param in underlying stream to wrap
+ * @param offset offset into stream where data starts
+ * @param length length of data at offset
+ * @throws IOException if an error occurred with the underlying stream
+ */
+ public LimitedLengthInputStream(InputStream in, long offset, long length) throws IOException {
+ super(in);
+
+ if (in == null) {
+ throw new IOException("in == null");
+ }
+
+ if (offset < 0) {
+ throw new IOException("offset < 0");
+ }
+
+ if (length < 0) {
+ throw new IOException("length < 0");
+ }
+
+ if (length > Long.MAX_VALUE - offset) {
+ throw new IOException("offset + length > Long.MAX_VALUE");
+ }
+
+ mEnd = offset + length;
+
+ skip(offset);
+ mOffset = offset;
+ }
+
+ @Override
+ public synchronized int read() throws IOException {
+ if (mOffset >= mEnd) {
+ return -1;
+ }
+
+ mOffset++;
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int byteCount) throws IOException {
+ if (mOffset >= mEnd) {
+ return -1;
+ }
+
+ final int arrayLength = buffer.length;
+ ArrayUtils.throwsIfOutOfBounds(arrayLength, offset, byteCount);
+
+ if (mOffset > Long.MAX_VALUE - byteCount) {
+ throw new IOException("offset out of bounds: " + mOffset + " + " + byteCount);
+ }
+
+ if (mOffset + byteCount > mEnd) {
+ byteCount = (int) (mEnd - mOffset);
+ }
+
+ final int numRead = super.read(buffer, offset, byteCount);
+ mOffset += numRead;
+
+ return numRead;
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return read(buffer, 0, buffer.length);
+ }
+}
diff --git a/android/content/pm/MacAuthenticatedInputStream.java b/android/content/pm/MacAuthenticatedInputStream.java
new file mode 100644
index 0000000..11f4b94
--- /dev/null
+++ b/android/content/pm/MacAuthenticatedInputStream.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 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 java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.crypto.Mac;
+
+/**
+ * An input stream filter that applies a MAC to the data passing through it. At
+ * the end of the data that should be authenticated, the tag can be calculated.
+ * After that, the stream should not be used.
+ *
+ * @hide
+ */
+public class MacAuthenticatedInputStream extends FilterInputStream {
+ private final Mac mMac;
+
+ public MacAuthenticatedInputStream(InputStream in, Mac mac) {
+ super(in);
+
+ mMac = mac;
+ }
+
+ public boolean isTagEqual(byte[] tag) {
+ final byte[] actualTag = mMac.doFinal();
+
+ if (tag == null || actualTag == null || tag.length != actualTag.length) {
+ return false;
+ }
+
+ /*
+ * Attempt to prevent timing attacks by doing the same amount of work
+ * whether the first byte matches or not. Do not change this to a loop
+ * that exits early when a byte does not match.
+ */
+ int value = 0;
+ for (int i = 0; i < tag.length; i++) {
+ value |= tag[i] ^ actualTag[i];
+ }
+
+ return value == 0;
+ }
+
+ @Override
+ public int read() throws IOException {
+ final int b = super.read();
+ if (b >= 0) {
+ mMac.update((byte) b);
+ }
+ return b;
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ int numRead = super.read(buffer, offset, count);
+ if (numRead > 0) {
+ mMac.update(buffer, offset, numRead);
+ }
+ return numRead;
+ }
+}
diff --git a/android/content/pm/ModuleInfo.java b/android/content/pm/ModuleInfo.java
new file mode 100644
index 0000000..a7306a3
--- /dev/null
+++ b/android/content/pm/ModuleInfo.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information you can retrieve about a particular system
+ * module.
+ */
+public final class ModuleInfo implements Parcelable {
+
+ // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
+ // constructor, and writeToParcel.
+
+ /** Public name of this module. */
+ private CharSequence mName;
+
+ /** The package name of this module. */
+ private String mPackageName;
+
+ /**
+ * The name of the APEX this module is distributed as, or null if it is not distributed via
+ * APEX.
+ */
+ @Nullable private String mApexModuleName;
+
+ /** Whether or not this module is hidden from the user. */
+ private boolean mHidden;
+
+ // TODO: Decide whether we need an additional metadata bundle to support out of band
+ // updates to ModuleInfo.
+ //
+ // private Bundle mMetadata;
+
+ /** @hide */
+ public ModuleInfo() {
+ }
+
+ /** @hide */
+ public ModuleInfo(ModuleInfo orig) {
+ mName = orig.mName;
+ mPackageName = orig.mPackageName;
+ mHidden = orig.mHidden;
+ mApexModuleName = orig.mApexModuleName;
+ }
+
+ /** @hide Sets the public name of this module. */
+ public ModuleInfo setName(CharSequence name) {
+ mName = name;
+ return this;
+ }
+
+ /** Gets the public name of this module. */
+ public @Nullable CharSequence getName() {
+ return mName;
+ }
+
+ /** @hide Sets the package name of this module. */
+ public ModuleInfo setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /** Gets the package name of this module. */
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /** @hide Sets whether or not this package is hidden. */
+ public ModuleInfo setHidden(boolean hidden) {
+ mHidden = hidden;
+ return this;
+ }
+
+ /** Gets whether or not this package is hidden. */
+ public boolean isHidden() {
+ return mHidden;
+ }
+
+ /** @hide Sets the apex module name. */
+ public ModuleInfo setApexModuleName(@Nullable String apexModuleName) {
+ mApexModuleName = apexModuleName;
+ return this;
+ }
+
+ /** @hide Gets the apex module name. */
+ public @Nullable String getApexModuleName() {
+ return mApexModuleName;
+ }
+
+ /** Returns a string representation of this object. */
+ public String toString() {
+ return "ModuleInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mName + "}";
+ }
+
+ /** Describes the kinds of special objects contained in this object. */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 0;
+ hashCode = 31 * hashCode + Objects.hashCode(mName);
+ hashCode = 31 * hashCode + Objects.hashCode(mPackageName);
+ hashCode = 31 * hashCode + Objects.hashCode(mApexModuleName);
+ hashCode = 31 * hashCode + Boolean.hashCode(mHidden);
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof ModuleInfo)) {
+ return false;
+ }
+ final ModuleInfo other = (ModuleInfo) obj;
+ return Objects.equals(mName, other.mName)
+ && Objects.equals(mPackageName, other.mPackageName)
+ && Objects.equals(mApexModuleName, other.mApexModuleName)
+ && mHidden == other.mHidden;
+ }
+
+ /** Flattens this object into the given {@link Parcel}. */
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeCharSequence(mName);
+ dest.writeString(mPackageName);
+ dest.writeBoolean(mHidden);
+ dest.writeString(mApexModuleName);
+ }
+
+ private ModuleInfo(Parcel source) {
+ mName = source.readCharSequence();
+ mPackageName = source.readString();
+ mHidden = source.readBoolean();
+ mApexModuleName = source.readString();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ModuleInfo> CREATOR =
+ new Parcelable.Creator<ModuleInfo>() {
+ public ModuleInfo createFromParcel(Parcel source) {
+ return new ModuleInfo(source);
+ }
+ public ModuleInfo[] newArray(int size) {
+ return new ModuleInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/PackageInfo.java b/android/content/pm/PackageInfo.java
new file mode 100644
index 0000000..0462a4b
--- /dev/null
+++ b/android/content/pm/PackageInfo.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2007 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.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Overall information about the contents of a package. This corresponds
+ * to all of the information collected from AndroidManifest.xml.
+ */
+public class PackageInfo implements Parcelable {
+ /**
+ * The name of this package. From the <manifest> tag's "name"
+ * attribute.
+ */
+ public String packageName;
+
+ /**
+ * The names of any installed split APKs for this package.
+ */
+ public String[] splitNames;
+
+ /**
+ * @deprecated Use {@link #getLongVersionCode()} instead, which includes both
+ * this and the additional
+ * {@link android.R.styleable#AndroidManifest_versionCodeMajor versionCodeMajor} attribute.
+ * The version number of this package, as specified by the <manifest>
+ * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
+ * attribute.
+ * @see #getLongVersionCode()
+ */
+ @Deprecated
+ public int versionCode;
+
+ /**
+ * @hide
+ * The major version number of this package, as specified by the <manifest>
+ * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor}
+ * attribute.
+ * @see #getLongVersionCode()
+ */
+ public int versionCodeMajor;
+
+ /**
+ * Return {@link android.R.styleable#AndroidManifest_versionCode versionCode} and
+ * {@link android.R.styleable#AndroidManifest_versionCodeMajor versionCodeMajor} combined
+ * together as a single long value. The
+ * {@link android.R.styleable#AndroidManifest_versionCodeMajor versionCodeMajor} is placed in
+ * the upper 32 bits.
+ */
+ public long getLongVersionCode() {
+ return composeLongVersionCode(versionCodeMajor, versionCode);
+ }
+
+ /**
+ * Set the full version code in this PackageInfo, updating {@link #versionCode}
+ * with the lower bits.
+ * @see #getLongVersionCode()
+ */
+ public void setLongVersionCode(long longVersionCode) {
+ versionCodeMajor = (int) (longVersionCode>>32);
+ versionCode = (int) longVersionCode;
+ }
+
+ /**
+ * @hide Internal implementation for composing a minor and major version code in to
+ * a single long version code.
+ */
+ public static long composeLongVersionCode(int major, int minor) {
+ return (((long) major) << 32) | (((long) minor) & 0xffffffffL);
+ }
+
+ /**
+ * The version name of this package, as specified by the <manifest>
+ * tag's {@link android.R.styleable#AndroidManifest_versionName versionName}
+ * attribute.
+ */
+ public String versionName;
+
+ /**
+ * The revision number of the base APK for this package, as specified by the
+ * <manifest> tag's
+ * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode}
+ * attribute.
+ */
+ public int baseRevisionCode;
+
+ /**
+ * The revision number of any split APKs for this package, as specified by
+ * the <manifest> tag's
+ * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode}
+ * attribute. Indexes are a 1:1 mapping against {@link #splitNames}.
+ */
+ public int[] splitRevisionCodes;
+
+ /**
+ * The shared user ID name of this package, as specified by the <manifest>
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
+ * attribute.
+ */
+ public String sharedUserId;
+
+ /**
+ * The shared user ID label of this package, as specified by the <manifest>
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
+ * attribute.
+ */
+ public int sharedUserLabel;
+
+ /**
+ * Information collected from the <application> tag, or null if
+ * there was none.
+ */
+ public ApplicationInfo applicationInfo;
+
+ /**
+ * The time at which the app was first installed. Units are as
+ * per {@link System#currentTimeMillis()}.
+ */
+ public long firstInstallTime;
+
+ /**
+ * The time at which the app was last updated. Units are as
+ * per {@link System#currentTimeMillis()}.
+ */
+ public long lastUpdateTime;
+
+ /**
+ * All kernel group-IDs that have been assigned to this package.
+ * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set.
+ */
+ public int[] gids;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestActivity
+ * <activity>} tags included under <application>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_ACTIVITIES} was set.
+ */
+ public ActivityInfo[] activities;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestReceiver
+ * <receiver>} tags included under <application>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_RECEIVERS} was set.
+ */
+ public ActivityInfo[] receivers;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestService
+ * <service>} tags included under <application>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_SERVICES} was set.
+ */
+ public ServiceInfo[] services;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestProvider
+ * <provider>} tags included under <application>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_PROVIDERS} was set.
+ */
+ public ProviderInfo[] providers;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestInstrumentation
+ * <instrumentation>} tags included under <manifest>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_INSTRUMENTATION} was set.
+ */
+ public InstrumentationInfo[] instrumentation;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestPermission
+ * <permission>} tags included under <manifest>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_PERMISSIONS} was set.
+ */
+ public PermissionInfo[] permissions;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestUsesPermission
+ * <uses-permission>} tags included under <manifest>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_PERMISSIONS} was set. This list includes
+ * all permissions requested, even those that were not granted or known
+ * by the system at install time.
+ */
+ public String[] requestedPermissions;
+
+ /**
+ * Array of flags of all {@link android.R.styleable#AndroidManifestUsesPermission
+ * <uses-permission>} tags included under <manifest>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_PERMISSIONS} was set. Each value matches
+ * the corresponding entry in {@link #requestedPermissions}, and will have
+ * the flag {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate.
+ */
+ public int[] requestedPermissionsFlags;
+
+ /**
+ * Array of all {@link android.R.styleable#AndroidManifestAttribution
+ * <attribution>} tags included under <manifest>, or null if there were none. This
+ * is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS} was set.
+ */
+ @SuppressWarnings({"ArrayReturn", "NullableCollection"})
+ public @Nullable Attribution[] attributions;
+
+ /**
+ * Flag for {@link #requestedPermissionsFlags}: the requested permission
+ * is required for the application to run; the user can not optionally
+ * disable it. Currently all permissions are required.
+ *
+ * @removed We do not support required permissions.
+ */
+ public static final int REQUESTED_PERMISSION_REQUIRED = 0x00000001;
+
+ /**
+ * Flag for {@link #requestedPermissionsFlags}: the requested permission
+ * is currently granted to the application.
+ */
+ public static final int REQUESTED_PERMISSION_GRANTED = 0x00000002;
+
+ /**
+ * Flag for {@link #requestedPermissionsFlags}: the requested permission has
+ * declared {@code neverForLocation} in their manifest as a strong assertion
+ * by a developer that they will never use this permission to derive the
+ * physical location of the device, regardless of
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and/or
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} being granted.
+ */
+ public static final int REQUESTED_PERMISSION_NEVER_FOR_LOCATION = 0x00010000;
+
+ /**
+ * Array of all signatures read from the package file. This is only filled
+ * in if the flag {@link PackageManager#GET_SIGNATURES} was set. A package
+ * must be signed with at least one certificate which is at position zero.
+ * The package can be signed with additional certificates which appear as
+ * subsequent entries.
+ *
+ * <strong>Note:</strong> Signature ordering is not guaranteed to be
+ * stable which means that a package signed with certificates A and B is
+ * equivalent to being signed with certificates B and A. This means that
+ * in case multiple signatures are reported you cannot assume the one at
+ * the first position to be the same across updates.
+ *
+ * <strong>Deprecated</strong> This has been replaced by the
+ * {@link PackageInfo#signingInfo} field, which takes into
+ * account signing certificate rotation. For backwards compatibility in
+ * the event of signing certificate rotation, this will return the oldest
+ * reported signing certificate, so that an application will appear to
+ * callers as though no rotation occurred.
+ *
+ * @deprecated use {@code signingInfo} instead
+ */
+ @Deprecated
+ public Signature[] signatures;
+
+ /**
+ * Signing information read from the package file, potentially
+ * including past signing certificates no longer used after signing
+ * certificate rotation. This is only filled in if
+ * the flag {@link PackageManager#GET_SIGNING_CERTIFICATES} was set.
+ *
+ * Use this field instead of the deprecated {@code signatures} field.
+ * See {@link SigningInfo} for more information on its contents.
+ */
+ public SigningInfo signingInfo;
+
+ /**
+ * Application specified preferred configuration
+ * {@link android.R.styleable#AndroidManifestUsesConfiguration
+ * <uses-configuration>} tags included under <manifest>,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_CONFIGURATIONS} was set.
+ */
+ public ConfigurationInfo[] configPreferences;
+
+ /**
+ * Features that this application has requested.
+ *
+ * @see FeatureInfo#FLAG_REQUIRED
+ */
+ public FeatureInfo[] reqFeatures;
+
+ /**
+ * Groups of features that this application has requested.
+ * Each group contains a set of features that are required.
+ * A device must match the features listed in {@link #reqFeatures} and one
+ * or more FeatureGroups in order to have satisfied the feature requirement.
+ *
+ * @see FeatureInfo#FLAG_REQUIRED
+ */
+ public FeatureGroupInfo[] featureGroups;
+
+ /**
+ * Constant corresponding to <code>auto</code> in
+ * the {@link android.R.attr#installLocation} attribute.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int INSTALL_LOCATION_UNSPECIFIED = -1;
+
+ /**
+ * Constant corresponding to <code>auto</code> in the
+ * {@link android.R.attr#installLocation} attribute.
+ */
+ public static final int INSTALL_LOCATION_AUTO = 0;
+
+ /**
+ * Constant corresponding to <code>internalOnly</code> in the
+ * {@link android.R.attr#installLocation} attribute.
+ */
+ public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
+
+ /**
+ * Constant corresponding to <code>preferExternal</code> in the
+ * {@link android.R.attr#installLocation} attribute.
+ */
+ public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
+
+ /**
+ * The install location requested by the package. From the
+ * {@link android.R.attr#installLocation} attribute, one of
+ * {@link #INSTALL_LOCATION_AUTO}, {@link #INSTALL_LOCATION_INTERNAL_ONLY},
+ * {@link #INSTALL_LOCATION_PREFER_EXTERNAL}
+ */
+ public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
+
+ /** @hide */
+ public boolean isStub;
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public boolean coreApp;
+
+ /** @hide */
+ public boolean requiredForAllUsers;
+
+ /** @hide */
+ public String restrictedAccountType;
+
+ /** @hide */
+ public String requiredAccountType;
+
+ /**
+ * What package, if any, this package will overlay.
+ *
+ * Package name of target package, or null.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String overlayTarget;
+
+ /**
+ * The name of the overlayable set of elements package, if any, this package will overlay.
+ *
+ * Overlayable name defined within the target package, or null.
+ * @hide
+ */
+ public String targetOverlayableName;
+
+ /**
+ * The overlay category, if any, of this package
+ *
+ * @hide
+ */
+ public String overlayCategory;
+
+ /** @hide */
+ public int overlayPriority;
+
+ /**
+ * Whether the overlay is static, meaning it cannot be enabled/disabled at runtime.
+ * @hide
+ */
+ public boolean mOverlayIsStatic;
+
+ /**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
+ /**
+ * Whether the package is an APEX package.
+ */
+ public boolean isApex;
+
+ public PackageInfo() {
+ }
+
+ /**
+ * Returns true if the package is a valid Runtime Overlay package.
+ * @hide
+ */
+ public boolean isOverlayPackage() {
+ return overlayTarget != null;
+ }
+
+ /**
+ * Returns true if the package is a valid static Runtime Overlay package. Static overlays
+ * are not updatable outside of a system update and are safe to load in the system process.
+ * @hide
+ */
+ public boolean isStaticOverlayPackage() {
+ return overlayTarget != null && mOverlayIsStatic;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ // Allow ApplicationInfo to be squashed.
+ final boolean prevAllowSquashing = dest.allowSquashing();
+ dest.writeString8(packageName);
+ dest.writeString8Array(splitNames);
+ dest.writeInt(versionCode);
+ dest.writeInt(versionCodeMajor);
+ dest.writeString8(versionName);
+ dest.writeInt(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
+ dest.writeString8(sharedUserId);
+ dest.writeInt(sharedUserLabel);
+ if (applicationInfo != null) {
+ dest.writeInt(1);
+ applicationInfo.writeToParcel(dest, parcelableFlags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeLong(firstInstallTime);
+ dest.writeLong(lastUpdateTime);
+ dest.writeIntArray(gids);
+ dest.writeTypedArray(activities, parcelableFlags);
+ dest.writeTypedArray(receivers, parcelableFlags);
+ dest.writeTypedArray(services, parcelableFlags);
+ dest.writeTypedArray(providers, parcelableFlags);
+ dest.writeTypedArray(instrumentation, parcelableFlags);
+ dest.writeTypedArray(permissions, parcelableFlags);
+ dest.writeString8Array(requestedPermissions);
+ dest.writeIntArray(requestedPermissionsFlags);
+ dest.writeTypedArray(signatures, parcelableFlags);
+ dest.writeTypedArray(configPreferences, parcelableFlags);
+ dest.writeTypedArray(reqFeatures, parcelableFlags);
+ dest.writeTypedArray(featureGroups, parcelableFlags);
+ dest.writeTypedArray(attributions, parcelableFlags);
+ dest.writeInt(installLocation);
+ dest.writeInt(isStub ? 1 : 0);
+ dest.writeInt(coreApp ? 1 : 0);
+ dest.writeInt(requiredForAllUsers ? 1 : 0);
+ dest.writeString8(restrictedAccountType);
+ dest.writeString8(requiredAccountType);
+ dest.writeString8(overlayTarget);
+ dest.writeString8(overlayCategory);
+ dest.writeInt(overlayPriority);
+ dest.writeBoolean(mOverlayIsStatic);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString8(compileSdkVersionCodename);
+ if (signingInfo != null) {
+ dest.writeInt(1);
+ signingInfo.writeToParcel(dest, parcelableFlags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeBoolean(isApex);
+ dest.restoreAllowSquashing(prevAllowSquashing);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<PackageInfo> CREATOR
+ = new Parcelable.Creator<PackageInfo>() {
+ @Override
+ public PackageInfo createFromParcel(Parcel source) {
+ return new PackageInfo(source);
+ }
+
+ @Override
+ public PackageInfo[] newArray(int size) {
+ return new PackageInfo[size];
+ }
+ };
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private PackageInfo(Parcel source) {
+ packageName = source.readString8();
+ splitNames = source.createString8Array();
+ versionCode = source.readInt();
+ versionCodeMajor = source.readInt();
+ versionName = source.readString8();
+ baseRevisionCode = source.readInt();
+ splitRevisionCodes = source.createIntArray();
+ sharedUserId = source.readString8();
+ sharedUserLabel = source.readInt();
+ int hasApp = source.readInt();
+ if (hasApp != 0) {
+ applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+ }
+ firstInstallTime = source.readLong();
+ lastUpdateTime = source.readLong();
+ gids = source.createIntArray();
+ activities = source.createTypedArray(ActivityInfo.CREATOR);
+ receivers = source.createTypedArray(ActivityInfo.CREATOR);
+ services = source.createTypedArray(ServiceInfo.CREATOR);
+ providers = source.createTypedArray(ProviderInfo.CREATOR);
+ instrumentation = source.createTypedArray(InstrumentationInfo.CREATOR);
+ permissions = source.createTypedArray(PermissionInfo.CREATOR);
+ requestedPermissions = source.createString8Array();
+ requestedPermissionsFlags = source.createIntArray();
+ signatures = source.createTypedArray(Signature.CREATOR);
+ configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
+ reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
+ featureGroups = source.createTypedArray(FeatureGroupInfo.CREATOR);
+ attributions = source.createTypedArray(Attribution.CREATOR);
+ installLocation = source.readInt();
+ isStub = source.readInt() != 0;
+ coreApp = source.readInt() != 0;
+ requiredForAllUsers = source.readInt() != 0;
+ restrictedAccountType = source.readString8();
+ requiredAccountType = source.readString8();
+ overlayTarget = source.readString8();
+ overlayCategory = source.readString8();
+ overlayPriority = source.readInt();
+ mOverlayIsStatic = source.readBoolean();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString8();
+ int hasSigningInfo = source.readInt();
+ if (hasSigningInfo != 0) {
+ signingInfo = SigningInfo.CREATOR.createFromParcel(source);
+ }
+ isApex = source.readBoolean();
+ }
+}
diff --git a/android/content/pm/PackageInfoLite.java b/android/content/pm/PackageInfoLite.java
new file mode 100644
index 0000000..9735f81
--- /dev/null
+++ b/android/content/pm/PackageInfoLite.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2007 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.content.PackageHelper;
+
+/**
+ * Basic information about a package as specified in its manifest.
+ * Utility class used in PackageManager methods
+ * @hide
+ */
+public class PackageInfoLite implements Parcelable {
+ /**
+ * The name of this package. From the <manifest> tag's "name"
+ * attribute.
+ */
+ public String packageName;
+
+ /** Names of any split APKs, ordered by parsed splitName */
+ public String[] splitNames;
+
+ /**
+ * The android:versionCode of the package.
+ * @deprecated Use {@link #getLongVersionCode()} instead, which includes both
+ * this and the additional
+ * {@link android.R.styleable#AndroidManifest_versionCode versionCodeMajor} attribute.
+ */
+ @Deprecated
+ public int versionCode;
+
+ /**
+ * @hide
+ * The android:versionCodeMajor of the package.
+ */
+ public int versionCodeMajor;
+
+ /**
+ * Return {@link #versionCode} and {@link #versionCodeMajor} combined together as a
+ * single long value. The {@link #versionCodeMajor} is placed in the upper 32 bits.
+ */
+ public long getLongVersionCode() {
+ return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode);
+ }
+
+ /** Revision code of base APK */
+ public int baseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ public int[] splitRevisionCodes;
+
+ /**
+ * The android:multiArch flag from the package manifest. If set,
+ * we will extract all native libraries for the given app, not just those
+ * from the preferred ABI.
+ */
+ public boolean multiArch;
+
+ /**
+ * The android:debuggable flag from the package manifest.
+ */
+ public boolean debuggable;
+
+ /**
+ * Specifies the recommended install location. Can be one of
+ * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
+ * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
+ * {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
+ * or {@link PackageHelper#RECOMMEND_FAILED_INVALID_APK} for parse errors.
+ */
+ public int recommendedInstallLocation;
+ public int installLocation;
+
+ public VerifierInfo[] verifiers;
+
+ public PackageInfoLite() {
+ }
+
+ public String toString() {
+ return "PackageInfoLite{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString(packageName);
+ dest.writeStringArray(splitNames);
+ dest.writeInt(versionCode);
+ dest.writeInt(versionCodeMajor);
+ dest.writeInt(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
+ dest.writeInt(recommendedInstallLocation);
+ dest.writeInt(installLocation);
+ dest.writeInt(multiArch ? 1 : 0);
+ dest.writeInt(debuggable ? 1 : 0);
+
+ if (verifiers == null || verifiers.length == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(verifiers.length);
+ dest.writeTypedArray(verifiers, parcelableFlags);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static final @android.annotation.NonNull Parcelable.Creator<PackageInfoLite> CREATOR
+ = new Parcelable.Creator<PackageInfoLite>() {
+ public PackageInfoLite createFromParcel(Parcel source) {
+ return new PackageInfoLite(source);
+ }
+
+ public PackageInfoLite[] newArray(int size) {
+ return new PackageInfoLite[size];
+ }
+ };
+
+ private PackageInfoLite(Parcel source) {
+ packageName = source.readString();
+ splitNames = source.createStringArray();
+ versionCode = source.readInt();
+ versionCodeMajor = source.readInt();
+ baseRevisionCode = source.readInt();
+ splitRevisionCodes = source.createIntArray();
+ recommendedInstallLocation = source.readInt();
+ installLocation = source.readInt();
+ multiArch = (source.readInt() != 0);
+ debuggable = (source.readInt() != 0);
+
+ final int verifiersLength = source.readInt();
+ if (verifiersLength == 0) {
+ verifiers = new VerifierInfo[0];
+ } else {
+ verifiers = new VerifierInfo[verifiersLength];
+ source.readTypedArray(verifiers, VerifierInfo.CREATOR);
+ }
+ }
+}
diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java
new file mode 100644
index 0000000..3f8aedb
--- /dev/null
+++ b/android/content/pm/PackageInstaller.java
@@ -0,0 +1,3026 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
+import android.Manifest;
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageManager.DeleteFlags;
+import android.content.pm.PackageManager.InstallReason;
+import android.content.pm.PackageManager.InstallScenario;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.FileBridge;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.ExceptionUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Offers the ability to install, upgrade, and remove applications on the
+ * device. This includes support for apps packaged either as a single
+ * "monolithic" APK, or apps packaged as multiple "split" APKs.
+ * <p>
+ * An app is delivered for installation through a
+ * {@link PackageInstaller.Session}, which any app can create. Once the session
+ * is created, the installer can stream one or more APKs into place until it
+ * decides to either commit or destroy the session. Committing may require user
+ * intervention to complete the installation, unless the caller falls into one of the
+ * following categories, in which case the installation will complete automatically.
+ * <ul>
+ * <li>the device owner
+ * <li>the affiliated profile owner
+ * </ul>
+ * <p>
+ * Sessions can install brand new apps, upgrade existing apps, or add new splits
+ * into an existing app.
+ * <p>
+ * Apps packaged as multiple split APKs always consist of a single "base" APK
+ * (with a {@code null} split name) and zero or more "split" APKs (with unique
+ * split names). Any subset of these APKs can be installed together, as long as
+ * the following constraints are met:
+ * <ul>
+ * <li>All APKs must have the exact same package name, version code, and signing
+ * certificates.
+ * <li>All APKs must have unique split names.
+ * <li>All installations must contain a single base APK.
+ * </ul>
+ * <p>
+ * The ApiDemos project contains examples of using this API:
+ * <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>.
+ */
+public class PackageInstaller {
+ private static final String TAG = "PackageInstaller";
+
+ /** {@hide} */
+ public static final boolean ENABLE_REVOCABLE_FD =
+ SystemProperties.getBoolean("fw.revocable_fd", false);
+
+ /**
+ * Activity Action: Show details about a particular install session. This
+ * may surface actions such as pause, resume, or cancel.
+ * <p>
+ * This should always be scoped to the installer package that owns the
+ * session. Clients should use {@link SessionInfo#createDetailsIntent()} to
+ * build this intent correctly.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
+ * <p>
+ * The session to show details for is defined in {@link #EXTRA_SESSION_ID}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
+
+ /**
+ * Broadcast Action: Explicit broadcast sent to the last known default launcher when a session
+ * for a new install is committed. For managed profile, this is sent to the default launcher
+ * of the primary profile.
+ * <p>
+ * The associated session is defined in {@link #EXTRA_SESSION} and the user for which this
+ * session was created in {@link Intent#EXTRA_USER}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SESSION_COMMITTED =
+ "android.content.pm.action.SESSION_COMMITTED";
+
+ /**
+ * Broadcast Action: Send information about a staged install session when its state is updated.
+ * <p>
+ * The associated session information is defined in {@link #EXTRA_SESSION}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SESSION_UPDATED =
+ "android.content.pm.action.SESSION_UPDATED";
+
+ /** {@hide} */
+ public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
+
+ /**
+ * An integer session ID that an operation is working with.
+ *
+ * @see Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
+
+ /**
+ * {@link SessionInfo} that an operation is working with.
+ *
+ * @see Intent#getParcelableExtra(String)
+ */
+ public static final String EXTRA_SESSION = "android.content.pm.extra.SESSION";
+
+ /**
+ * Package name that an operation is working with.
+ *
+ * @see Intent#getStringExtra(String)
+ */
+ public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
+
+ /**
+ * Current status of an operation. Will be one of
+ * {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS},
+ * {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED},
+ * {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT},
+ * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or
+ * {@link #STATUS_FAILURE_STORAGE}.
+ * <p>
+ * More information about a status may be available through additional
+ * extras; see the individual status documentation for details.
+ *
+ * @see Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
+
+ /**
+ * Detailed string representation of the status, including raw details that
+ * are useful for debugging.
+ *
+ * @see Intent#getStringExtra(String)
+ */
+ public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE";
+
+ /**
+ * Another package name relevant to a status. This is typically the package
+ * responsible for causing an operation failure.
+ *
+ * @see Intent#getStringExtra(String)
+ */
+ public static final String
+ EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
+
+ /**
+ * Storage path relevant to a status.
+ *
+ * @see Intent#getStringExtra(String)
+ */
+ public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH";
+
+ /** {@hide} */
+ @Deprecated
+ public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
+
+ /** {@hide} */
+ public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
+ /** {@hide} */
+ public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE";
+ /** {@hide} */
+ public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
+
+ /**
+ * Type of DataLoader for this session. Will be one of
+ * {@link #DATA_LOADER_TYPE_NONE}, {@link #DATA_LOADER_TYPE_STREAMING},
+ * {@link #DATA_LOADER_TYPE_INCREMENTAL}.
+ * <p>
+ * See the individual types documentation for details.
+ *
+ * @see Intent#getIntExtra(String, int)
+ * {@hide}
+ */
+ @SystemApi
+ public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+
+ /**
+ * Streaming installation pending.
+ * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
+ *
+ * @see #EXTRA_SESSION_ID
+ * {@hide}
+ */
+ public static final int STATUS_PENDING_STREAMING = -2;
+
+ /**
+ * User action is currently required to proceed. You can launch the intent
+ * activity described by {@link Intent#EXTRA_INTENT} to involve the user and
+ * continue.
+ * <p>
+ * You may choose to immediately launch the intent if the user is actively
+ * using your app. Otherwise, you should use a notification to guide the
+ * user back into your app before launching.
+ *
+ * @see Intent#getParcelableExtra(String)
+ */
+ public static final int STATUS_PENDING_USER_ACTION = -1;
+
+ /**
+ * The operation succeeded.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The operation failed in a generic way. The system will always try to
+ * provide a more specific failure reason, but in some rare cases this may
+ * be delivered.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE = 1;
+
+ /**
+ * The operation failed because it was blocked. For example, a device policy
+ * may be blocking the operation, a package verifier may have blocked the
+ * operation, or the app may be required for core system operation.
+ * <p>
+ * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the
+ * specific package blocking the install.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ * @see #EXTRA_OTHER_PACKAGE_NAME
+ */
+ public static final int STATUS_FAILURE_BLOCKED = 2;
+
+ /**
+ * The operation failed because it was actively aborted. For example, the
+ * user actively declined requested permissions, or the session was
+ * abandoned.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_ABORTED = 3;
+
+ /**
+ * The operation failed because one or more of the APKs was invalid. For
+ * example, they might be malformed, corrupt, incorrectly signed,
+ * mismatched, etc.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_INVALID = 4;
+
+ /**
+ * The operation failed because it conflicts (or is inconsistent with) with
+ * another package already installed on the device. For example, an existing
+ * permission, incompatible certificates, etc. The user may be able to
+ * uninstall another app to fix the issue.
+ * <p>
+ * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the
+ * specific package identified as the cause of the conflict.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ * @see #EXTRA_OTHER_PACKAGE_NAME
+ */
+ public static final int STATUS_FAILURE_CONFLICT = 5;
+
+ /**
+ * The operation failed because of storage issues. For example, the device
+ * may be running low on space, or external media may be unavailable. The
+ * user may be able to help free space or insert different external media.
+ * <p>
+ * The result may also contain {@link #EXTRA_STORAGE_PATH} with the path to
+ * the storage device that caused the failure.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ * @see #EXTRA_STORAGE_PATH
+ */
+ public static final int STATUS_FAILURE_STORAGE = 6;
+
+ /**
+ * The operation failed because it is fundamentally incompatible with this
+ * device. For example, the app may require a hardware feature that doesn't
+ * exist, it may be missing native code for the ABIs supported by the
+ * device, or it requires a newer SDK version, etc.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
+
+ /**
+ * Default value, non-streaming installation session.
+ *
+ * @see #EXTRA_DATA_LOADER_TYPE
+ * {@hide}
+ */
+ @SystemApi
+ public static final int DATA_LOADER_TYPE_NONE = DataLoaderType.NONE;
+
+ /**
+ * Streaming installation using data loader.
+ *
+ * @see #EXTRA_DATA_LOADER_TYPE
+ * {@hide}
+ */
+ @SystemApi
+ public static final int DATA_LOADER_TYPE_STREAMING = DataLoaderType.STREAMING;
+
+ /**
+ * Streaming installation using Incremental FileSystem.
+ *
+ * @see #EXTRA_DATA_LOADER_TYPE
+ * {@hide}
+ */
+ @SystemApi
+ public static final int DATA_LOADER_TYPE_INCREMENTAL = DataLoaderType.INCREMENTAL;
+
+ /**
+ * Target location for the file in installation session is /data/app/<packageName>-<id>.
+ * This is the intended location for APKs.
+ * Requires permission to install packages.
+ * {@hide}
+ */
+ @SystemApi
+ public static final int LOCATION_DATA_APP = InstallationFileLocation.DATA_APP;
+
+ /**
+ * Target location for the file in installation session is
+ * /data/media/<userid>/Android/obb/<packageName>. This is the intended location for OBBs.
+ * {@hide}
+ */
+ @SystemApi
+ public static final int LOCATION_MEDIA_OBB = InstallationFileLocation.MEDIA_OBB;
+
+ /**
+ * Target location for the file in installation session is
+ * /data/media/<userid>/Android/data/<packageName>.
+ * This is the intended location for application data.
+ * Can only be used by an app itself running under specific user.
+ * {@hide}
+ */
+ @SystemApi
+ public static final int LOCATION_MEDIA_DATA = InstallationFileLocation.MEDIA_DATA;
+
+ /** @hide */
+ @IntDef(prefix = { "LOCATION_" }, value = {
+ LOCATION_DATA_APP,
+ LOCATION_MEDIA_OBB,
+ LOCATION_MEDIA_DATA})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FileLocation{}
+
+ private final IPackageInstaller mInstaller;
+ private final int mUserId;
+ private final String mInstallerPackageName;
+ private final String mAttributionTag;
+
+ private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>();
+
+ /** {@hide} */
+ public PackageInstaller(IPackageInstaller installer,
+ String installerPackageName, String installerAttributionTag, int userId) {
+ mInstaller = installer;
+ mInstallerPackageName = installerPackageName;
+ mAttributionTag = installerAttributionTag;
+ mUserId = userId;
+ }
+
+ /**
+ * Create a new session using the given parameters, returning a unique ID
+ * that represents the session. Once created, the session can be opened
+ * multiple times across multiple device boots.
+ * <p>
+ * The system may automatically destroy sessions that have not been
+ * finalized (either committed or abandoned) within a reasonable period of
+ * time, typically on the order of a day.
+ *
+ * @throws IOException if parameters were unsatisfiable, such as lack of
+ * disk space or unavailable media.
+ * @throws SecurityException when installation services are unavailable,
+ * such as when called from a restricted user.
+ * @throws IllegalArgumentException when {@link SessionParams} is invalid.
+ * @return positive, non-zero unique ID that represents the created session.
+ * This ID remains consistent across device reboots until the
+ * session is finalized. IDs are not reused during a given boot.
+ */
+ public int createSession(@NonNull SessionParams params) throws IOException {
+ try {
+ return mInstaller.createSession(params, mInstallerPackageName, mAttributionTag,
+ mUserId);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Open an existing session to actively perform work. To succeed, the caller
+ * must be the owner of the install session.
+ *
+ * @throws IOException if parameters were unsatisfiable, such as lack of
+ * disk space or unavailable media.
+ * @throws SecurityException when the caller does not own the session, or
+ * the session is invalid.
+ */
+ public @NonNull Session openSession(int sessionId) throws IOException {
+ try {
+ try {
+ return new Session(mInstaller.openSession(sessionId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Update the icon representing the app being installed in a specific
+ * session. This should be roughly
+ * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions.
+ *
+ * @throws SecurityException when the caller does not own the session, or
+ * the session is invalid.
+ */
+ public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) {
+ try {
+ mInstaller.updateSessionAppIcon(sessionId, appIcon);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Update the label representing the app being installed in a specific
+ * session.
+ *
+ * @throws SecurityException when the caller does not own the session, or
+ * the session is invalid.
+ */
+ public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) {
+ try {
+ final String val = (appLabel != null) ? appLabel.toString() : null;
+ mInstaller.updateSessionAppLabel(sessionId, val);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Completely abandon the given session, destroying all staged data and
+ * rendering it invalid. Abandoned sessions will be reported to
+ * {@link SessionCallback} listeners as failures. This is equivalent to
+ * opening the session and calling {@link Session#abandon()}.
+ *
+ * @throws SecurityException when the caller does not own the session, or
+ * the session is invalid.
+ */
+ public void abandonSession(int sessionId) {
+ try {
+ mInstaller.abandonSession(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return details for a specific session. No special permissions are
+ * required to retrieve these details.
+ *
+ * @return details for the requested session, or {@code null} if the session
+ * does not exist.
+ */
+ public @Nullable SessionInfo getSessionInfo(int sessionId) {
+ try {
+ return mInstaller.getSessionInfo(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return list of all known install sessions, regardless of the installer.
+ */
+ public @NonNull List<SessionInfo> getAllSessions() {
+ try {
+ return mInstaller.getAllSessions(mUserId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return list of all known install sessions owned by the calling app.
+ */
+ public @NonNull List<SessionInfo> getMySessions() {
+ try {
+ return mInstaller.getMySessions(mInstallerPackageName, mUserId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return list of all staged install sessions.
+ */
+ public @NonNull List<SessionInfo> getStagedSessions() {
+ try {
+ // TODO: limit this to the mUserId?
+ return mInstaller.getStagedSessions().getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns first active staged session, or {@code null} if there is none.
+ *
+ * <p>For more information on what sessions are considered active see
+ * {@link SessionInfo#isStagedSessionActive()}.
+ *
+ * @deprecated Use {@link #getActiveStagedSessions} as there can be more than one active staged
+ * session
+ */
+ @Deprecated
+ public @Nullable SessionInfo getActiveStagedSession() {
+ List<SessionInfo> activeSessions = getActiveStagedSessions();
+ return activeSessions.isEmpty() ? null : activeSessions.get(0);
+ }
+
+ /**
+ * Returns list of active staged sessions. Returns empty list if there is none.
+ *
+ * <p>For more information on what sessions are considered active see
+ * * {@link SessionInfo#isStagedSessionActive()}.
+ */
+ public @NonNull List<SessionInfo> getActiveStagedSessions() {
+ final List<SessionInfo> activeStagedSessions = new ArrayList<>();
+ final List<SessionInfo> stagedSessions = getStagedSessions();
+ for (int i = 0; i < stagedSessions.size(); i++) {
+ final SessionInfo sessionInfo = stagedSessions.get(i);
+ if (sessionInfo.isStagedSessionActive()) {
+ activeStagedSessions.add(sessionInfo);
+ }
+ }
+ return activeStagedSessions;
+ }
+
+ /**
+ * Uninstall the given package, removing it completely from the device. 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 packageName The package to uninstall.
+ * @param statusReceiver Where to deliver the result.
+ *
+ * @see android.app.admin.DevicePolicyManager
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
+ uninstall(packageName, 0 /*flags*/, statusReceiver);
+ }
+
+ /**
+ * Uninstall the given package, removing it completely from the device. This
+ * method is only available to the current "installer of record" for the
+ * package.
+ *
+ * @param packageName The package to uninstall.
+ * @param flags Flags for uninstall.
+ * @param statusReceiver Where to deliver the result.
+ *
+ * @hide
+ */
+ public void uninstall(@NonNull String packageName, @DeleteFlags int flags,
+ @NonNull IntentSender statusReceiver) {
+ uninstall(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ flags, statusReceiver);
+ }
+
+ /**
+ * Uninstall the given package with a specific version code, removing it
+ * 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,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ public void uninstall(@NonNull VersionedPackage versionedPackage,
+ @NonNull IntentSender statusReceiver) {
+ uninstall(versionedPackage, 0 /*flags*/, statusReceiver);
+ }
+
+ /**
+ * 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
+ * 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.
+ *
+ * @param versionedPackage The versioned package to uninstall.
+ * @param flags Flags for uninstall.
+ * @param statusReceiver Where to deliver the result.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ public void uninstall(@NonNull VersionedPackage versionedPackage, @DeleteFlags int flags,
+ @NonNull IntentSender statusReceiver) {
+ Objects.requireNonNull(versionedPackage, "versionedPackage cannot be null");
+ try {
+ mInstaller.uninstall(versionedPackage, mInstallerPackageName,
+ flags, statusReceiver, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Install the given package, which already exists on the device, for the user for which this
+ * installer was created.
+ *
+ * <p>This will
+ * {@link PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set) allowlist
+ * all restricted permissions}.
+ *
+ * @param packageName The package to install.
+ * @param installReason Reason for install.
+ * @param statusReceiver Where to deliver the result.
+ */
+ @RequiresPermission(allOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.INSTALL_EXISTING_PACKAGES})
+ public void installExistingPackage(@NonNull String packageName,
+ @InstallReason int installReason,
+ @Nullable IntentSender statusReceiver) {
+ Objects.requireNonNull(packageName, "packageName cannot be null");
+ try {
+ mInstaller.installExistingPackage(packageName,
+ PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, installReason,
+ statusReceiver, mUserId, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Uninstall the given package for the user for which this installer was created if the package
+ * will still exist for other users on the device.
+ *
+ * @param packageName The package to install.
+ * @param statusReceiver Where to deliver the result.
+ */
+ @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
+ public void uninstallExistingPackage(@NonNull String packageName,
+ @Nullable IntentSender statusReceiver) {
+ Objects.requireNonNull(packageName, "packageName cannot be null");
+ try {
+ mInstaller.uninstallExistingPackage(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ mInstallerPackageName, statusReceiver, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
+ public void setPermissionsResult(int sessionId, boolean accepted) {
+ try {
+ mInstaller.setPermissionsResult(sessionId, accepted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Events for observing session lifecycle.
+ * <p>
+ * A typical session lifecycle looks like this:
+ * <ul>
+ * <li>An installer creates a session to indicate pending app delivery. All
+ * install details are available at this point.
+ * <li>The installer opens the session to deliver APK data. Note that a
+ * session may be opened and closed multiple times as network connectivity
+ * changes. The installer may deliver periodic progress updates.
+ * <li>The installer commits or abandons the session, resulting in the
+ * session being finished.
+ * </ul>
+ */
+ public static abstract class SessionCallback {
+ /**
+ * New session has been created. Details about the session can be
+ * obtained from {@link PackageInstaller#getSessionInfo(int)}.
+ */
+ public abstract void onCreated(int sessionId);
+
+ /**
+ * Badging details for an existing session has changed. For example, the
+ * app icon or label has been updated.
+ */
+ public abstract void onBadgingChanged(int sessionId);
+
+ /**
+ * Active state for session has been changed.
+ * <p>
+ * A session is considered active whenever there is ongoing forward
+ * progress being made, such as the installer holding an open
+ * {@link Session} instance while streaming data into place, or the
+ * system optimizing code as the result of
+ * {@link Session#commit(IntentSender)}.
+ * <p>
+ * If the installer closes the {@link Session} without committing, the
+ * session is considered inactive until the installer opens the session
+ * again.
+ */
+ public abstract void onActiveChanged(int sessionId, boolean active);
+
+ /**
+ * Progress for given session has been updated.
+ * <p>
+ * Note that this progress may not directly correspond to the value
+ * reported by
+ * {@link PackageInstaller.Session#setStagingProgress(float)}, as the
+ * system may carve out a portion of the overall progress to represent
+ * its own internal installation work.
+ */
+ public abstract void onProgressChanged(int sessionId, float progress);
+
+ /**
+ * Session has completely finished, either with success or failure.
+ */
+ public abstract void onFinished(int sessionId, boolean success);
+ }
+
+ /** {@hide} */
+ static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub {
+ private static final int MSG_SESSION_CREATED = 1;
+ private static final int MSG_SESSION_BADGING_CHANGED = 2;
+ private static final int MSG_SESSION_ACTIVE_CHANGED = 3;
+ private static final int MSG_SESSION_PROGRESS_CHANGED = 4;
+ private static final int MSG_SESSION_FINISHED = 5;
+
+ final SessionCallback mCallback;
+ final Executor mExecutor;
+
+ SessionCallbackDelegate(SessionCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onSessionCreated(int sessionId) {
+ mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onCreated, mCallback,
+ sessionId).recycleOnUse());
+ }
+
+ @Override
+ public void onSessionBadgingChanged(int sessionId) {
+ mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onBadgingChanged,
+ mCallback, sessionId).recycleOnUse());
+ }
+
+ @Override
+ public void onSessionActiveChanged(int sessionId, boolean active) {
+ mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onActiveChanged,
+ mCallback, sessionId, active).recycleOnUse());
+ }
+
+ @Override
+ public void onSessionProgressChanged(int sessionId, float progress) {
+ mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onProgressChanged,
+ mCallback, sessionId, progress).recycleOnUse());
+ }
+
+ @Override
+ public void onSessionFinished(int sessionId, boolean success) {
+ mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onFinished,
+ mCallback, sessionId, success).recycleOnUse());
+ }
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public void addSessionCallback(@NonNull SessionCallback callback) {
+ registerSessionCallback(callback);
+ }
+
+ /**
+ * Register to watch for session lifecycle events. No special permissions
+ * are required to watch for these events.
+ */
+ public void registerSessionCallback(@NonNull SessionCallback callback) {
+ registerSessionCallback(callback, new Handler());
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
+ registerSessionCallback(callback, handler);
+ }
+
+ /**
+ * Register to watch for session lifecycle events. No special permissions
+ * are required to watch for these events.
+ *
+ * @param handler to dispatch callback events through, otherwise uses
+ * calling thread.
+ */
+ public void registerSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
+ synchronized (mDelegates) {
+ final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback,
+ new HandlerExecutor(handler));
+ try {
+ mInstaller.registerCallback(delegate, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mDelegates.add(delegate);
+ }
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public void removeSessionCallback(@NonNull SessionCallback callback) {
+ unregisterSessionCallback(callback);
+ }
+
+ /**
+ * Unregister a previously registered callback.
+ */
+ public void unregisterSessionCallback(@NonNull SessionCallback callback) {
+ synchronized (mDelegates) {
+ for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ final SessionCallbackDelegate delegate = i.next();
+ if (delegate.mCallback == callback) {
+ try {
+ mInstaller.unregisterCallback(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ i.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * An installation that is being actively staged. For an install to succeed,
+ * all existing and new packages must have identical package names, version
+ * codes, and signing certificates.
+ * <p>
+ * A session may contain any number of split packages. If the application
+ * does not yet exist, this session must include a base package.
+ * <p>
+ * If an APK included in this session is already defined by the existing
+ * installation (for example, the same split name), the APK in this session
+ * will replace the existing APK.
+ * <p>
+ * In such a case that multiple packages need to be committed simultaneously,
+ * multiple sessions can be referenced by a single multi-package session.
+ * This session is created with no package name and calling
+ * {@link SessionParams#setMultiPackage()}. The individual session IDs can be
+ * added with {@link #addChildSessionId(int)} and commit of the multi-package
+ * session will result in all child sessions being committed atomically.
+ */
+ public static class Session implements Closeable {
+ /** {@hide} */
+ protected final IPackageInstallerSession mSession;
+
+ /** {@hide} */
+ public Session(IPackageInstallerSession session) {
+ mSession = session;
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public void setProgress(float progress) {
+ setStagingProgress(progress);
+ }
+
+ /**
+ * Set current progress of staging this session. Valid values are
+ * anywhere between 0 and 1.
+ * <p>
+ * Note that this progress may not directly correspond to the value
+ * reported by {@link SessionCallback#onProgressChanged(int, float)}, as
+ * the system may carve out a portion of the overall progress to
+ * represent its own internal installation work.
+ */
+ public void setStagingProgress(float progress) {
+ try {
+ mSession.setClientProgress(progress);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public void addProgress(float progress) {
+ try {
+ mSession.addClientProgress(progress);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Open a stream to write an APK file into the session.
+ * <p>
+ * The returned stream will start writing data at the requested offset
+ * in the underlying file, which can be used to resume a partially
+ * written file. If a valid file length is specified, the system will
+ * preallocate the underlying disk space to optimize placement on disk.
+ * It's strongly recommended to provide a valid file length when known.
+ * <p>
+ * You can write data into the returned stream, optionally call
+ * {@link #fsync(OutputStream)} as needed to ensure bytes have been
+ * persisted to disk, and then close when finished. All streams must be
+ * closed before calling {@link #commit(IntentSender)}.
+ *
+ * @param name arbitrary, unique name of your choosing to identify the
+ * APK being written. You can open a file again for
+ * additional writes (such as after a reboot) by using the
+ * same name. This name is only meaningful within the context
+ * of a single install session.
+ * @param offsetBytes offset into the file to begin writing at, or 0 to
+ * start at the beginning of the file.
+ * @param lengthBytes total size of the file being written, used to
+ * preallocate the underlying disk space, or -1 if unknown.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @throws IOException if trouble opening the file for writing, such as
+ * lack of disk space or unavailable media.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ */
+ public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
+ long lengthBytes) throws IOException {
+ try {
+ if (ENABLE_REVOCABLE_FD) {
+ return new ParcelFileDescriptor.AutoCloseOutputStream(
+ mSession.openWrite(name, offsetBytes, lengthBytes));
+ } else {
+ final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
+ offsetBytes, lengthBytes);
+ return new FileBridge.FileBridgeOutputStream(clientSocket);
+ }
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } 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();
+ }
+ }
+
+ /**
+ * Populate an APK file by creating a hard link to avoid the need to copy.
+ * <p>
+ * Note this API is used by RollbackManager only and can only be called from system_server.
+ * {@code target} will be relabeled if link is created successfully. RollbackManager has
+ * to delete {@code target} when the session is committed successfully to avoid SELinux
+ * label conflicts.
+ * <p>
+ * Note No more bytes should be written to the file once the link is created successfully.
+ *
+ * @param target the path of the link target
+ *
+ * @hide
+ */
+ public void stageViaHardLink(String target) throws IOException {
+ try {
+ mSession.stageViaHardLink(target);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Ensure that any outstanding data for given stream has been committed
+ * to disk. This is only valid for streams returned from
+ * {@link #openWrite(String, long, long)}.
+ */
+ public void fsync(@NonNull OutputStream out) throws IOException {
+ if (ENABLE_REVOCABLE_FD) {
+ if (out instanceof ParcelFileDescriptor.AutoCloseOutputStream) {
+ try {
+ Os.fsync(((ParcelFileDescriptor.AutoCloseOutputStream) out).getFD());
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ throw new IllegalArgumentException("Unrecognized stream");
+ }
+ } else {
+ if (out instanceof FileBridge.FileBridgeOutputStream) {
+ ((FileBridge.FileBridgeOutputStream) out).fsync();
+ } else {
+ throw new IllegalArgumentException("Unrecognized stream");
+ }
+ }
+ }
+
+ /**
+ * Return all APK names contained in this session.
+ * <p>
+ * This returns all names which have been previously written through
+ * {@link #openWrite(String, long, long)} as part of this session.
+ *
+ * @throws SecurityException if called after the session has been
+ * committed or abandoned.
+ */
+ public @NonNull String[] getNames() throws IOException {
+ try {
+ return mSession.getNames();
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Open a stream to read an APK file from the session.
+ * <p>
+ * This is only valid for names which have been previously written
+ * through {@link #openWrite(String, long, long)} as part of this
+ * session. For example, this stream may be used to calculate a
+ * {@link MessageDigest} of a written APK before committing.
+ *
+ * @throws SecurityException if called after the session has been
+ * committed or abandoned.
+ */
+ public @NonNull InputStream openRead(@NonNull String name) throws IOException {
+ try {
+ final ParcelFileDescriptor pfd = mSession.openRead(name);
+ return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a split.
+ * <p>
+ * Split removals occur prior to adding new APKs. If upgrading a feature
+ * split, it is not expected nor desirable to remove the split prior to
+ * upgrading.
+ * <p>
+ * When split removal is bundled with new APKs, the packageName must be
+ * identical.
+ */
+ public void removeSplit(@NonNull String splitName) throws IOException {
+ try {
+ mSession.removeSplit(splitName);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return data loader params or null if the session is not using one.
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
+ public @Nullable DataLoaderParams getDataLoaderParams() {
+ try {
+ DataLoaderParamsParcel data = mSession.getDataLoaderParams();
+ if (data == null) {
+ return null;
+ }
+ return new DataLoaderParams(data);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a file to session. On commit this file will be pulled from DataLoader {@code
+ * android.service.dataloader.DataLoaderService.DataLoader}.
+ *
+ * @param location target location for the file. Possible values:
+ * {@link #LOCATION_DATA_APP},
+ * {@link #LOCATION_MEDIA_OBB},
+ * {@link #LOCATION_MEDIA_DATA}.
+ * @param name arbitrary, unique name of your choosing to identify the
+ * APK being written. You can open a file again for
+ * additional writes (such as after a reboot) by using the
+ * same name. This name is only meaningful within the context
+ * of a single install session.
+ * @param lengthBytes total size of the file being written.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @param metadata additional info use by DataLoader to pull data for the file.
+ * @param signature additional file signature, e.g.
+ * <a href="https://source.android.com/security/apksigning/v4.html">APK Signature Scheme v4</a>
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-streaming session
+ *
+ * @see android.content.pm.InstallationFile
+ *
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
+ public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes,
+ @NonNull byte[] metadata, @Nullable byte[] signature) {
+ try {
+ mSession.addFile(location, name, lengthBytes, metadata, signature);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a file.
+ *
+ * @param location target location for the file. Possible values:
+ * {@link #LOCATION_DATA_APP},
+ * {@link #LOCATION_MEDIA_OBB},
+ * {@link #LOCATION_MEDIA_DATA}.
+ * @param name name of a file, e.g. split.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-DataLoader session
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
+ public void removeFile(@FileLocation int location, @NonNull String name) {
+ try {
+ mSession.removeFile(location, name);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets installer-provided checksums for the APK file in session.
+ *
+ * @param name previously written as part of this session.
+ * {@link #openWrite}
+ * @param checksums installer intends to make available via
+ * {@link PackageManager#requestChecksums}.
+ * @param signature DER PKCS#7 detached signature bytes over binary serialized checksums
+ * to enable integrity checking for the checksums or null for no integrity
+ * checking. {@link PackageManager#requestChecksums} will return
+ * the certificate used to create signature.
+ * Binary format for checksums:
+ * <pre>{@code DataOutputStream dos;
+ * dos.writeInt(checksum.getType());
+ * dos.writeInt(checksum.getValue().length);
+ * dos.write(checksum.getValue());}</pre>
+ * If using <b>openssl cms</b>, make sure to specify -binary -nosmimecap.
+ * @see <a href="https://www.openssl.org/docs/man1.0.2/man1/cms.html">openssl cms</a>
+ * @throws SecurityException if called after the session has been
+ * committed or abandoned.
+ * @throws IllegalStateException if checksums for this file have already been added.
+ * @deprecated do not use installer-provided checksums,
+ * use platform-enforced checksums
+ * e.g. {@link Checksum#TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}
+ * in {@link PackageManager#requestChecksums}.
+ */
+ @Deprecated
+ public void setChecksums(@NonNull String name, @NonNull List<Checksum> checksums,
+ @Nullable byte[] signature) throws IOException {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(checksums);
+
+ try {
+ mSession.setChecksums(name, checksums.toArray(new Checksum[checksums.size()]),
+ signature);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to commit everything staged in this session. This may require
+ * user intervention, and so it may not happen immediately. The final
+ * result of the commit will be reported through the given callback.
+ * <p>
+ * Once this method is called, the session is sealed and no additional mutations may be
+ * performed on the session. In case of device reboot or data loader transient failure
+ * 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.
+ *
+ * @param statusReceiver Called when the state of the session changes. Intents
+ * sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
+ * individual status codes on how to handle them.
+ *
+ * @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 {
+ mSession.commit(statusReceiver, false);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to commit a session that has been {@link #transfer(String) transferred}.
+ *
+ * <p>If the device reboots before the session has been finalized, you may commit the
+ * session again.
+ *
+ * <p>The caller of this method is responsible to ensure the safety of the session. As the
+ * session was created by another - usually less trusted - app, it is paramount that before
+ * committing <u>all</u> public and system {@link SessionInfo properties of the session}
+ * and <u>all</u> {@link #openRead(String) APKs} are verified by the caller. It might happen
+ * that new properties are added to the session with a new API revision. In this case the
+ * callers need to be updated.
+ *
+ * @param statusReceiver Called when the state of the session changes. Intents
+ * sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
+ * individual status codes on how to handle them.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
+ public void commitTransferred(@NonNull IntentSender statusReceiver) {
+ try {
+ mSession.commit(statusReceiver, true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Transfer the session to a new owner.
+ * <p>
+ * Only sessions that update the installing app can be transferred.
+ * <p>
+ * After the transfer to a package with a different uid all method calls on the session
+ * will cause {@link SecurityException}s.
+ * <p>
+ * Once this method is called, the session is sealed and no additional mutations beside
+ * committing it may be performed on the session.
+ *
+ * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES
+ * permission.
+ *
+ * @throws PackageManager.NameNotFoundException if the new owner could not be found.
+ * @throws SecurityException if called after the session has been committed or abandoned.
+ * @throws IllegalStateException if streams opened through
+ * {@link #openWrite(String, long, long) are still open.
+ * @throws IllegalArgumentException if {@code packageName} is invalid.
+ */
+ public void transfer(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
+
+ try {
+ mSession.transfer(packageName);
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Release this session object. You can open the session again if it
+ * hasn't been finalized.
+ */
+ @Override
+ public void close() {
+ try {
+ mSession.close();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Completely abandon this session, destroying all staged data and
+ * rendering it invalid. Abandoned sessions will be reported to
+ * {@link SessionCallback} listeners as failures. This is equivalent to
+ * {@link #abandonSession(int)}.
+ * <p>If the parent is abandoned, all children will also be abandoned. Any written data
+ * would be destroyed and the created {@link Session} information will be discarded.</p>
+ */
+ public void abandon() {
+ try {
+ mSession.abandon();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return {@code true} if this session will commit more than one package when it is
+ * committed.
+ */
+ public boolean isMultiPackage() {
+ try {
+ return mSession.isMultiPackage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return {@code true} if this session will be staged and applied at next reboot.
+ */
+ public boolean isStaged() {
+ try {
+ return mSession.isStaged();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return the session ID of the multi-package session that this belongs to or
+ * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session.
+ */
+ public int getParentSessionId() {
+ try {
+ return mSession.getParentSessionId();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return the set of session IDs that will be committed atomically when this session is
+ * committed if this is a multi-package session or null if none exist.
+ */
+ @NonNull
+ public int[] getChildSessionIds() {
+ try {
+ return mSession.getChildSessionIds();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a session ID to the set of sessions that will be committed atomically
+ * when this session is committed.
+ *
+ * <p>If the parent is staged or has rollback enabled, all children must have
+ * the same properties.</p>
+ * <p>If the parent is abandoned, all children will also be abandoned.</p>
+ *
+ * @param sessionId the session ID to add to this multi-package session.
+ */
+ public void addChildSessionId(int sessionId) {
+ try {
+ mSession.addChildSessionId(sessionId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a session ID from the set of sessions that will be committed
+ * atomically when this session is committed.
+ *
+ * @param sessionId the session ID to remove from this multi-package session.
+ */
+ public void removeChildSessionId(int sessionId) {
+ try {
+ mSession.removeChildSessionId(sessionId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Parameters for creating a new {@link PackageInstaller.Session}.
+ */
+ public static class SessionParams implements Parcelable {
+
+ /** {@hide} */
+ public static final int MODE_INVALID = -1;
+
+ /**
+ * Mode for an install session whose staged APKs should fully replace any
+ * existing APKs for the target app.
+ */
+ public static final int MODE_FULL_INSTALL = 1;
+
+ /**
+ * Mode for an install session that should inherit any existing APKs for the
+ * target app, unless they have been explicitly overridden (based on split
+ * name) by the session. For example, this can be used to add one or more
+ * split APKs to an existing installation.
+ * <p>
+ * If there are no existing APKs for the target app, this behaves like
+ * {@link #MODE_FULL_INSTALL}.
+ */
+ public static final int MODE_INHERIT_EXISTING = 2;
+
+ /**
+ * Special constant to refer to all restricted permissions.
+ */
+ public static final @NonNull Set<String> RESTRICTED_PERMISSIONS_ALL = new ArraySet<>();
+
+ /** {@hide} */
+ public static final int UID_UNKNOWN = -1;
+
+ /**
+ * This value is derived from the maximum file name length. No package above this limit
+ * can ever be successfully installed on the device.
+ * @hide
+ */
+ public static final int MAX_PACKAGE_NAME_LENGTH = 255;
+
+ /** @hide */
+ @IntDef(prefix = {"USER_ACTION_"}, value = {
+ USER_ACTION_UNSPECIFIED,
+ USER_ACTION_REQUIRED,
+ USER_ACTION_NOT_REQUIRED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActionRequirement {}
+
+ /**
+ * The installer did not call {@link SessionParams#setRequireUserAction(int)} to
+ * specify whether user action should be required for the install.
+ */
+ public static final int USER_ACTION_UNSPECIFIED = 0;
+
+ /**
+ * The installer called {@link SessionParams#setRequireUserAction(int)} with
+ * {@code true} to require user action for the install to complete.
+ */
+ public static final int USER_ACTION_REQUIRED = 1;
+
+ /**
+ * The installer called {@link SessionParams#setRequireUserAction(int)} with
+ * {@code false} to request that user action not be required for this install.
+ */
+ public static final int USER_ACTION_NOT_REQUIRED = 2;
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public int mode = MODE_INVALID;
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public int installFlags = PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
+ /** {@hide} */
+ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ /** {@hide} */
+ public @InstallReason int installReason = PackageManager.INSTALL_REASON_UNKNOWN;
+ /**
+ * {@hide}
+ *
+ * This flag indicates which installation scenario best describes this session. The system
+ * may use this value when making decisions about how to handle the installation, such as
+ * prioritizing system health or user experience.
+ */
+ public @InstallScenario int installScenario = PackageManager.INSTALL_SCENARIO_DEFAULT;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long sizeBytes = -1;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public String appPackageName;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Bitmap appIcon;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public String appLabel;
+ /** {@hide} */
+ public long appIconLastModified = -1;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int originatingUid = UID_UNKNOWN;
+ /** {@hide} */
+ public Uri referrerUri;
+ /** {@hide} */
+ public String abiOverride;
+ /** {@hide} */
+ public String volumeUuid;
+ /** {@hide} */
+ public String[] grantedRuntimePermissions;
+ /** {@hide} */
+ public List<String> whitelistedRestrictedPermissions;
+ /** {@hide} */
+ public int autoRevokePermissionsMode = MODE_DEFAULT;
+ /** {@hide} */
+ public String installerPackageName;
+ /** {@hide} */
+ public boolean isMultiPackage;
+ /** {@hide} */
+ public boolean isStaged;
+ /** {@hide} */
+ public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
+ /** {@hide} */
+ public DataLoaderParams dataLoaderParams;
+ /** {@hide} */
+ public int rollbackDataPolicy = PackageManager.RollbackDataPolicy.RESTORE;
+ /** {@hide} */
+ public boolean forceQueryableOverride;
+ /** {@hide} */
+ public int requireUserAction = USER_ACTION_UNSPECIFIED;
+
+ /**
+ * Construct parameters for a new package install session.
+ *
+ * @param mode one of {@link #MODE_FULL_INSTALL} or
+ * {@link #MODE_INHERIT_EXISTING} describing how the session
+ * should interact with an existing app.
+ */
+ public SessionParams(int mode) {
+ this.mode = mode;
+ }
+
+ /** {@hide} */
+ public SessionParams(Parcel source) {
+ mode = source.readInt();
+ installFlags = source.readInt();
+ installLocation = source.readInt();
+ installReason = source.readInt();
+ installScenario = source.readInt();
+ sizeBytes = source.readLong();
+ appPackageName = source.readString();
+ appIcon = source.readParcelable(null);
+ appLabel = source.readString();
+ originatingUri = source.readParcelable(null);
+ originatingUid = source.readInt();
+ referrerUri = source.readParcelable(null);
+ abiOverride = source.readString();
+ volumeUuid = source.readString();
+ grantedRuntimePermissions = source.readStringArray();
+ whitelistedRestrictedPermissions = source.createStringArrayList();
+ autoRevokePermissionsMode = source.readInt();
+ installerPackageName = source.readString();
+ isMultiPackage = source.readBoolean();
+ isStaged = source.readBoolean();
+ forceQueryableOverride = source.readBoolean();
+ requiredInstalledVersionCode = source.readLong();
+ DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
+ DataLoaderParamsParcel.class.getClassLoader());
+ if (dataLoaderParamsParcel != null) {
+ dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
+ }
+ rollbackDataPolicy = source.readInt();
+ requireUserAction = source.readInt();
+ }
+
+ /** {@hide} */
+ public SessionParams copy() {
+ SessionParams ret = new SessionParams(mode);
+ ret.installFlags = installFlags;
+ ret.installLocation = installLocation;
+ ret.installReason = installReason;
+ ret.installScenario = installScenario;
+ ret.sizeBytes = sizeBytes;
+ ret.appPackageName = appPackageName;
+ ret.appIcon = appIcon; // not a copy.
+ ret.appLabel = appLabel;
+ ret.originatingUri = originatingUri; // not a copy, but immutable.
+ ret.originatingUid = originatingUid;
+ ret.referrerUri = referrerUri; // not a copy, but immutable.
+ ret.abiOverride = abiOverride;
+ ret.volumeUuid = volumeUuid;
+ ret.grantedRuntimePermissions = grantedRuntimePermissions;
+ ret.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
+ ret.autoRevokePermissionsMode = autoRevokePermissionsMode;
+ ret.installerPackageName = installerPackageName;
+ ret.isMultiPackage = isMultiPackage;
+ ret.isStaged = isStaged;
+ ret.forceQueryableOverride = forceQueryableOverride;
+ ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
+ ret.dataLoaderParams = dataLoaderParams;
+ ret.rollbackDataPolicy = rollbackDataPolicy;
+ ret.requireUserAction = requireUserAction;
+ return ret;
+ }
+
+ /**
+ * Check if there are hidden options set.
+ *
+ * <p>Hidden options are those options that cannot be verified via public or system-api
+ * methods on {@link SessionInfo}.
+ *
+ * @return {@code true} if any hidden option is set.
+ *
+ * @hide
+ */
+ public boolean areHiddenOptionsSet() {
+ return (installFlags & (PackageManager.INSTALL_REQUEST_DOWNGRADE
+ | PackageManager.INSTALL_ALLOW_DOWNGRADE
+ | PackageManager.INSTALL_DONT_KILL_APP
+ | PackageManager.INSTALL_INSTANT_APP
+ | PackageManager.INSTALL_FULL_APP
+ | PackageManager.INSTALL_VIRTUAL_PRELOAD
+ | PackageManager.INSTALL_ALLOCATE_AGGRESSIVE)) != installFlags
+ || abiOverride != null || volumeUuid != null;
+ }
+
+ /**
+ * Provide value of {@link PackageInfo#installLocation}, which may be used
+ * to determine where the app will be staged. Defaults to
+ * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
+ */
+ public void setInstallLocation(int installLocation) {
+ this.installLocation = installLocation;
+ }
+
+ /**
+ * Optionally indicate the total size (in bytes) of all APKs that will be
+ * delivered in this session. The system may use this to ensure enough disk
+ * space exists before proceeding, or to estimate container size for
+ * installations living on external storage.
+ *
+ * @see PackageInfo#INSTALL_LOCATION_AUTO
+ * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL
+ */
+ public void setSize(long sizeBytes) {
+ this.sizeBytes = sizeBytes;
+ }
+
+ /**
+ * Optionally set the package name of the app being installed. It's strongly
+ * recommended that you provide this value when known, so that observers can
+ * communicate installing apps to users.
+ * <p>
+ * If the APKs staged in the session aren't consistent with this package
+ * name, the install will fail. Regardless of this value, all APKs in the
+ * app must have the same package name.
+ */
+ public void setAppPackageName(@Nullable String appPackageName) {
+ this.appPackageName = appPackageName;
+ }
+
+ /**
+ * Optionally set an icon representing the app being installed. This should
+ * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both
+ * dimensions.
+ */
+ public void setAppIcon(@Nullable Bitmap appIcon) {
+ this.appIcon = appIcon;
+ }
+
+ /**
+ * Optionally set a label representing the app being installed.
+ *
+ * This value will be trimmed to the first 1000 characters.
+ */
+ public void setAppLabel(@Nullable CharSequence appLabel) {
+ this.appLabel = (appLabel != null) ? appLabel.toString() : null;
+ }
+
+ /**
+ * Optionally set the URI where this package was downloaded from. This is
+ * informational and may be used as a signal for anti-malware purposes.
+ *
+ * @see Intent#EXTRA_ORIGINATING_URI
+ */
+ public void setOriginatingUri(@Nullable Uri originatingUri) {
+ this.originatingUri = originatingUri;
+ }
+
+ /**
+ * Sets the UID that initiated the package installation. This is informational
+ * and may be used as a signal for anti-malware purposes.
+ */
+ public void setOriginatingUid(int originatingUid) {
+ this.originatingUid = originatingUid;
+ }
+
+ /**
+ * Optionally set the URI that referred you to install this package. This is
+ * informational and may be used as a signal for anti-malware purposes.
+ *
+ * @see Intent#EXTRA_REFERRER
+ */
+ public void setReferrerUri(@Nullable Uri referrerUri) {
+ this.referrerUri = referrerUri;
+ }
+
+ /**
+ * Sets which runtime permissions to be granted to the package at installation.
+ *
+ * @param permissions The permissions to grant or null to grant all runtime
+ * permissions.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS)
+ public void setGrantedRuntimePermissions(String[] permissions) {
+ installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
+ this.grantedRuntimePermissions = permissions;
+ }
+
+ /**
+ * Sets which restricted permissions to be allowlisted for the app. Allowlisting
+ * is not granting the permissions, rather it allows the app to hold permissions
+ * which are otherwise restricted. Allowlisting a non restricted permission has
+ * no effect.
+ *
+ * <p> Permissions can be hard restricted which means that the app cannot hold
+ * them or soft restricted where the app can hold the permission but in a weaker
+ * form. Whether a permission is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard
+ * restricted} or {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted}
+ * depends on the permission declaration. Allowlisting a hard restricted permission
+ * allows the app to hold that permission and allowlisting a soft restricted
+ * permission allows the app to hold the permission in its full, unrestricted form.
+ *
+ * <p> Permissions can also be immutably restricted which means that the allowlist
+ * state of the permission can be determined only at install time and cannot be
+ * changed on updated or at a later point via the package manager APIs.
+ *
+ * <p>Initially, all restricted permissions are allowlisted but you can change
+ * which ones are allowlisted by calling this method or the corresponding ones
+ * on the {@link PackageManager}. Only soft or hard restricted permissions on the current
+ * Android version are supported and any invalid entries will be removed.
+ *
+ * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int)
+ * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int)
+ */
+ public void setWhitelistedRestrictedPermissions(@Nullable Set<String> permissions) {
+ if (permissions == RESTRICTED_PERMISSIONS_ALL) {
+ installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
+ whitelistedRestrictedPermissions = null;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
+ whitelistedRestrictedPermissions = (permissions != null)
+ ? new ArrayList<>(permissions) : null;
+ }
+ }
+
+ /**
+ * Sets whether permissions should be auto-revoked if this package is unused for an
+ * extended periodd of time.
+ *
+ * It's disabled by default but generally the installer should enable it for most packages,
+ * excluding only those where doing so might cause breakage that cannot be easily addressed
+ * by simply re-requesting the permission(s).
+ *
+ * If user explicitly enabled or disabled it via settings, this call is ignored.
+ *
+ * @param shouldAutoRevoke whether permissions should be auto-revoked.
+ *
+ * @deprecated No longer used
+ */
+ @Deprecated
+ public void setAutoRevokePermissionsMode(boolean shouldAutoRevoke) {
+ autoRevokePermissionsMode = shouldAutoRevoke ? MODE_ALLOWED : MODE_IGNORED;
+ }
+
+ /**
+ * Request that rollbacks be enabled or disabled for the given upgrade with rollback data
+ * policy set to RESTORE.
+ *
+ * <p>If the parent session is staged or has rollback enabled, all children sessions
+ * must have the same properties.
+ *
+ * @param enable set to {@code true} to enable, {@code false} to disable
+ * @see SessionParams#setEnableRollback(boolean, int)
+ * @hide
+ */
+ @SystemApi
+ public void setEnableRollback(boolean enable) {
+ if (enable) {
+ installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+ }
+ rollbackDataPolicy = PackageManager.RollbackDataPolicy.RESTORE;
+ }
+
+ /**
+ * Request that rollbacks be enabled or disabled for the given upgrade.
+ *
+ * <p>If the parent session is staged or has rollback enabled, all children sessions
+ * must have the same properties.
+ *
+ * <p> For a multi-package install, this method must be called on each child session to
+ * specify rollback data policies explicitly. Note each child session is allowed to have
+ * different policies.
+ *
+ * @param enable set to {@code true} to enable, {@code false} to disable
+ * @param dataPolicy the rollback data policy for this session
+ * @hide
+ */
+ @SystemApi
+ public void setEnableRollback(boolean enable,
+ @PackageManager.RollbackDataPolicy int dataPolicy) {
+ if (enable) {
+ installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+ }
+ rollbackDataPolicy = dataPolicy;
+ }
+
+
+ /**
+ * @deprecated use {@link #setRequestDowngrade(boolean)}.
+ * {@hide}
+ */
+ @SystemApi
+ @Deprecated
+ public void setAllowDowngrade(boolean allowDowngrade) {
+ setRequestDowngrade(allowDowngrade);
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public void setRequestDowngrade(boolean requestDowngrade) {
+ if (requestDowngrade) {
+ installFlags |= PackageManager.INSTALL_REQUEST_DOWNGRADE;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
+ }
+ }
+
+ /**
+ * Require the given version of the package be installed.
+ * The install will only be allowed if the existing version code of
+ * the package installed on the device matches the given version code.
+ * Use {@link * PackageManager#VERSION_CODE_HIGHEST} to allow
+ * installation regardless of the currently installed package version.
+ *
+ * @hide
+ */
+ public void setRequiredInstalledVersionCode(long versionCode) {
+ requiredInstalledVersionCode = versionCode;
+ }
+
+ /** {@hide} */
+ public void setInstallFlagsForcePermissionPrompt() {
+ installFlags |= PackageManager.INSTALL_FORCE_PERMISSION_PROMPT;
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public void setDontKillApp(boolean dontKillApp) {
+ if (dontKillApp) {
+ installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_DONT_KILL_APP;
+ }
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public void setInstallAsInstantApp(boolean isInstantApp) {
+ if (isInstantApp) {
+ installFlags |= PackageManager.INSTALL_INSTANT_APP;
+ installFlags &= ~PackageManager.INSTALL_FULL_APP;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+ installFlags |= PackageManager.INSTALL_FULL_APP;
+ }
+ }
+
+ /**
+ * Sets the install as a virtual preload. Will only have effect when called
+ * by the verifier.
+ * {@hide}
+ */
+ @SystemApi
+ public void setInstallAsVirtualPreload() {
+ installFlags |= PackageManager.INSTALL_VIRTUAL_PRELOAD;
+ }
+
+ /**
+ * Set the reason for installing this package.
+ * <p>
+ * The install reason should be a pre-defined integer. The behavior is
+ * undefined if other values are used.
+ *
+ * @see PackageManager#INSTALL_REASON_UNKNOWN
+ * @see PackageManager#INSTALL_REASON_POLICY
+ * @see PackageManager#INSTALL_REASON_DEVICE_RESTORE
+ * @see PackageManager#INSTALL_REASON_DEVICE_SETUP
+ * @see PackageManager#INSTALL_REASON_USER
+ */
+ public void setInstallReason(@InstallReason int installReason) {
+ this.installReason = installReason;
+ }
+
+ /** {@hide} */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE)
+ public void setAllocateAggressive(boolean allocateAggressive) {
+ if (allocateAggressive) {
+ installFlags |= PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public void setInstallFlagAllowTest() {
+ installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+ }
+
+ /**
+ * Set the installer package for the app.
+ *
+ * By default this is the app that created the {@link PackageInstaller} object.
+ *
+ * @param installerPackageName name of the installer package
+ * {@hide}
+ */
+ @TestApi
+ public void setInstallerPackageName(@Nullable String installerPackageName) {
+ this.installerPackageName = installerPackageName;
+ }
+
+ /**
+ * Set this session to be the parent of a multi-package install.
+ *
+ * A multi-package install session contains no APKs and only references other install
+ * sessions via ID. When a multi-package session is committed, all of its children
+ * are committed to the system in an atomic manner. If any children fail to install,
+ * all of them do, including the multi-package session.
+ */
+ public void setMultiPackage() {
+ this.isMultiPackage = true;
+ }
+
+ /**
+ * Set this session to be staged to be installed at reboot.
+ *
+ * Staged sessions are scheduled to be installed at next reboot. Staged sessions can also be
+ * multi-package. In that case, if any of the children sessions fail to install at reboot,
+ * all the other children sessions are aborted as well.
+ *
+ * <p>If the parent session is staged or has rollback enabled, all children sessions
+ * must have the same properties.
+ *
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public void setStaged() {
+ this.isStaged = true;
+ }
+
+ /**
+ * Set this session to be installing an APEX package.
+ *
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public void setInstallAsApex() {
+ installFlags |= PackageManager.INSTALL_APEX;
+ }
+
+ /** @hide */
+ public boolean getEnableRollback() {
+ return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0;
+ }
+
+ /**
+ * Set the data loader params for the session.
+ * This also switches installation into data loading mode and disallow direct writes into
+ * staging folder.
+ *
+ * @see android.service.dataloader.DataLoaderService.DataLoader
+ *
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.USE_INSTALLER_V2})
+ public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) {
+ this.dataLoaderParams = dataLoaderParams;
+ }
+
+ /**
+ *
+ * {@hide}
+ */
+ public void setForceQueryable() {
+ this.forceQueryableOverride = true;
+ }
+
+ /**
+ * Optionally indicate whether user action should be required when the session is
+ * committed.
+ * <p>
+ * Defaults to {@link #USER_ACTION_UNSPECIFIED} unless otherwise set. When unspecified for
+ * installers using the
+ * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
+ * permission will behave as if set to {@link #USER_ACTION_REQUIRED}, and
+ * {@link #USER_ACTION_NOT_REQUIRED} otherwise. When {@code requireUserAction} is set to
+ * {@link #USER_ACTION_REQUIRED}, installers will receive a
+ * {@link #STATUS_PENDING_USER_ACTION} callback once the session is committed, indicating
+ * that user action is required for the install to proceed.
+ * <p>
+ * For installers that have been granted the
+ * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
+ * permission, user action will not be required when all of the following conditions are
+ * met:
+ *
+ * <ul>
+ * <li>{@code requireUserAction} is set to {@link #USER_ACTION_NOT_REQUIRED}.</li>
+ * <li>The app being installed targets {@link android.os.Build.VERSION_CODES#Q API 29}
+ * or higher.</li>
+ * <li>The installer is the {@link InstallSourceInfo#getInstallingPackageName()
+ * installer of record} of an existing version of the app (in other words, this install
+ * session is an app update) or the installer is updating itself.</li>
+ * <li>The installer declares the
+ * {@link android.Manifest.permission#UPDATE_PACKAGES_WITHOUT_USER_ACTION
+ * UPDATE_PACKAGES_WITHOUT_USER_ACTION} permission.</li>
+ * </ul>
+ * <p>
+ * Note: The target API level requirement will advance in future Android versions.
+ * Session owners should always be prepared to handle {@link #STATUS_PENDING_USER_ACTION}.
+ *
+ * @param requireUserAction whether user action should be required.
+ */
+ public void setRequireUserAction(
+ @SessionParams.UserActionRequirement int requireUserAction) {
+ if (requireUserAction != USER_ACTION_UNSPECIFIED
+ && requireUserAction != USER_ACTION_REQUIRED
+ && requireUserAction != USER_ACTION_NOT_REQUIRED) {
+ throw new IllegalArgumentException("requireUserAction set as invalid value of "
+ + requireUserAction + ", but must be one of ["
+ + "USER_ACTION_UNSPECIFIED, USER_ACTION_REQUIRED, USER_ACTION_NOT_REQUIRED"
+ + "]");
+ }
+ this.requireUserAction = requireUserAction;
+ }
+
+ /**
+ * Sets the install scenario for this session, which describes the expected user journey.
+ */
+ public void setInstallScenario(@InstallScenario int installScenario) {
+ this.installScenario = installScenario;
+ }
+
+ /** {@hide} */
+ public void dump(IndentingPrintWriter pw) {
+ pw.printPair("mode", mode);
+ pw.printHexPair("installFlags", installFlags);
+ pw.printPair("installLocation", installLocation);
+ pw.printPair("installReason", installReason);
+ pw.printPair("installScenario", installScenario);
+ pw.printPair("sizeBytes", sizeBytes);
+ pw.printPair("appPackageName", appPackageName);
+ pw.printPair("appIcon", (appIcon != null));
+ pw.printPair("appLabel", appLabel);
+ pw.printPair("originatingUri", originatingUri);
+ pw.printPair("originatingUid", originatingUid);
+ pw.printPair("referrerUri", referrerUri);
+ pw.printPair("abiOverride", abiOverride);
+ pw.printPair("volumeUuid", volumeUuid);
+ pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions);
+ pw.printPair("whitelistedRestrictedPermissions", whitelistedRestrictedPermissions);
+ pw.printPair("autoRevokePermissions", autoRevokePermissionsMode);
+ pw.printPair("installerPackageName", installerPackageName);
+ pw.printPair("isMultiPackage", isMultiPackage);
+ pw.printPair("isStaged", isStaged);
+ pw.printPair("forceQueryable", forceQueryableOverride);
+ pw.printPair("requireUserAction", SessionInfo.userActionToString(requireUserAction));
+ pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
+ pw.printPair("dataLoaderParams", dataLoaderParams);
+ pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+ pw.println();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mode);
+ dest.writeInt(installFlags);
+ dest.writeInt(installLocation);
+ dest.writeInt(installReason);
+ dest.writeInt(installScenario);
+ dest.writeLong(sizeBytes);
+ dest.writeString(appPackageName);
+ dest.writeParcelable(appIcon, flags);
+ dest.writeString(appLabel);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeInt(originatingUid);
+ dest.writeParcelable(referrerUri, flags);
+ dest.writeString(abiOverride);
+ dest.writeString(volumeUuid);
+ dest.writeStringArray(grantedRuntimePermissions);
+ dest.writeStringList(whitelistedRestrictedPermissions);
+ dest.writeInt(autoRevokePermissionsMode);
+ dest.writeString(installerPackageName);
+ dest.writeBoolean(isMultiPackage);
+ dest.writeBoolean(isStaged);
+ dest.writeBoolean(forceQueryableOverride);
+ dest.writeLong(requiredInstalledVersionCode);
+ if (dataLoaderParams != null) {
+ dest.writeParcelable(dataLoaderParams.getData(), flags);
+ } else {
+ dest.writeParcelable(null, flags);
+ }
+ dest.writeInt(rollbackDataPolicy);
+ dest.writeInt(requireUserAction);
+ }
+
+ public static final Parcelable.Creator<SessionParams>
+ CREATOR = new Parcelable.Creator<SessionParams>() {
+ @Override
+ public SessionParams createFromParcel(Parcel p) {
+ return new SessionParams(p);
+ }
+
+ @Override
+ public SessionParams[] newArray(int size) {
+ return new SessionParams[size];
+ }
+ };
+ }
+
+ /**
+ * Details for an active install session.
+ */
+ public static class SessionInfo implements Parcelable {
+
+ /**
+ * A session ID that does not exist or is invalid.
+ */
+ public static final int INVALID_ID = -1;
+ /** {@hide} */
+ private static final int[] NO_SESSIONS = {};
+
+ /** @hide */
+ @IntDef(prefix = { "STAGED_SESSION_" }, value = {
+ STAGED_SESSION_NO_ERROR,
+ STAGED_SESSION_VERIFICATION_FAILED,
+ STAGED_SESSION_ACTIVATION_FAILED,
+ STAGED_SESSION_UNKNOWN,
+ STAGED_SESSION_CONFLICT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StagedSessionErrorCode{}
+ /**
+ * Constant indicating that no error occurred during the preparation or the activation of
+ * this staged session.
+ */
+ public static final int STAGED_SESSION_NO_ERROR = 0;
+
+ /**
+ * Constant indicating that an error occurred during the verification phase (pre-reboot) of
+ * this staged session.
+ */
+ public static final int STAGED_SESSION_VERIFICATION_FAILED = 1;
+
+ /**
+ * Constant indicating that an error occurred during the activation phase (post-reboot) of
+ * this staged session.
+ */
+ public static final int STAGED_SESSION_ACTIVATION_FAILED = 2;
+
+ /**
+ * Constant indicating that an unknown error occurred while processing this staged session.
+ */
+ public static final int STAGED_SESSION_UNKNOWN = 3;
+
+ /**
+ * Constant indicating that the session was in conflict with another staged session and had
+ * to be sacrificed for resolution.
+ */
+ public static final int STAGED_SESSION_CONFLICT = 4;
+
+ private static String userActionToString(int requireUserAction) {
+ switch(requireUserAction) {
+ case SessionParams.USER_ACTION_REQUIRED:
+ return "REQUIRED";
+ case SessionParams.USER_ACTION_NOT_REQUIRED:
+ return "NOT_REQUIRED";
+ default:
+ return "UNSPECIFIED";
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public int sessionId;
+ /** {@hide} */
+ public int userId;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public String installerPackageName;
+ /** {@hide} */
+ public String installerAttributionTag;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String resolvedBaseCodePath;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public float progress;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean sealed;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public boolean active;
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public int mode;
+ /** {@hide} */
+ public @InstallReason int installReason;
+ /** {@hide} */
+ public @InstallScenario int installScenario;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public long sizeBytes;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public String appPackageName;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public Bitmap appIcon;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public CharSequence appLabel;
+
+ /** {@hide} */
+ public int installLocation;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public int originatingUid;
+ /** {@hide} */
+ public Uri referrerUri;
+ /** {@hide} */
+ public String[] grantedRuntimePermissions;
+ /** {@hide}*/
+ public List<String> whitelistedRestrictedPermissions;
+ /** {@hide}*/
+ public int autoRevokePermissionsMode = MODE_DEFAULT;
+ /** {@hide} */
+ public int installFlags;
+ /** {@hide} */
+ public boolean isMultiPackage;
+ /** {@hide} */
+ public boolean isStaged;
+ /** {@hide} */
+ public boolean forceQueryable;
+ /** {@hide} */
+ public int parentSessionId = INVALID_ID;
+ /** {@hide} */
+ public int[] childSessionIds = NO_SESSIONS;
+
+ /** {@hide} */
+ public boolean isStagedSessionApplied;
+ /** {@hide} */
+ public boolean isStagedSessionReady;
+ /** {@hide} */
+ public boolean isStagedSessionFailed;
+ private int mStagedSessionErrorCode;
+ private String mStagedSessionErrorMessage;
+
+ /** {@hide} */
+ public boolean isCommitted;
+
+ /** {@hide} */
+ public long createdMillis;
+
+ /** {@hide} */
+ public long updatedMillis;
+
+ /** {@hide} */
+ public int rollbackDataPolicy;
+
+ /** {@hide} */
+ public int requireUserAction;
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public SessionInfo() {
+ }
+
+ /** {@hide} */
+ public SessionInfo(Parcel source) {
+ sessionId = source.readInt();
+ userId = source.readInt();
+ installerPackageName = source.readString();
+ installerAttributionTag = source.readString();
+ resolvedBaseCodePath = source.readString();
+ progress = source.readFloat();
+ sealed = source.readInt() != 0;
+ active = source.readInt() != 0;
+
+ mode = source.readInt();
+ installReason = source.readInt();
+ installScenario = source.readInt();
+ sizeBytes = source.readLong();
+ appPackageName = source.readString();
+ appIcon = source.readParcelable(null);
+ appLabel = source.readString();
+
+ installLocation = source.readInt();
+ originatingUri = source.readParcelable(null);
+ originatingUid = source.readInt();
+ referrerUri = source.readParcelable(null);
+ grantedRuntimePermissions = source.readStringArray();
+ whitelistedRestrictedPermissions = source.createStringArrayList();
+ autoRevokePermissionsMode = source.readInt();
+
+ installFlags = source.readInt();
+ isMultiPackage = source.readBoolean();
+ isStaged = source.readBoolean();
+ forceQueryable = source.readBoolean();
+ parentSessionId = source.readInt();
+ childSessionIds = source.createIntArray();
+ if (childSessionIds == null) {
+ childSessionIds = NO_SESSIONS;
+ }
+ isStagedSessionApplied = source.readBoolean();
+ isStagedSessionReady = source.readBoolean();
+ isStagedSessionFailed = source.readBoolean();
+ mStagedSessionErrorCode = source.readInt();
+ mStagedSessionErrorMessage = source.readString();
+ isCommitted = source.readBoolean();
+ rollbackDataPolicy = source.readInt();
+ createdMillis = source.readLong();
+ requireUserAction = source.readInt();
+ }
+
+ /**
+ * Return the ID for this session.
+ */
+ public int getSessionId() {
+ return sessionId;
+ }
+
+ /**
+ * Return the user associated with this session.
+ */
+ public @NonNull UserHandle getUser() {
+ return new UserHandle(userId);
+ }
+
+ /**
+ * Return the package name of the app that owns this session.
+ */
+ public @Nullable String getInstallerPackageName() {
+ return installerPackageName;
+ }
+
+ /**
+ * @return {@link android.content.Context#getAttributionTag attribution tag} of the context
+ * that created this session
+ */
+ public @Nullable String getInstallerAttributionTag() {
+ return installerAttributionTag;
+ }
+
+ /**
+ * Return current overall progress of this session, between 0 and 1.
+ * <p>
+ * Note that this progress may not directly correspond to the value
+ * reported by
+ * {@link PackageInstaller.Session#setStagingProgress(float)}, as the
+ * system may carve out a portion of the overall progress to represent
+ * its own internal installation work.
+ */
+ public float getProgress() {
+ return progress;
+ }
+
+ /**
+ * Return if this session is currently active.
+ * <p>
+ * A session is considered active whenever there is ongoing forward
+ * progress being made, such as the installer holding an open
+ * {@link Session} instance while streaming data into place, or the
+ * system optimizing code as the result of
+ * {@link Session#commit(IntentSender)}.
+ * <p>
+ * If the installer closes the {@link Session} without committing, the
+ * session is considered inactive until the installer opens the session
+ * again.
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Return if this session is sealed.
+ * <p>
+ * Once sealed, no further changes may be made to the session. A session
+ * is sealed the moment {@link Session#commit(IntentSender)} is called.
+ */
+ public boolean isSealed() {
+ return sealed;
+ }
+
+ /**
+ * Return the reason for installing this package.
+ *
+ * @return The install reason.
+ */
+ public @InstallReason int getInstallReason() {
+ return installReason;
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public boolean isOpen() {
+ return isActive();
+ }
+
+ /**
+ * Return the package name this session is working with. May be {@code null}
+ * if unknown.
+ */
+ public @Nullable String getAppPackageName() {
+ return appPackageName;
+ }
+
+ /**
+ * Return an icon representing the app being installed. May be {@code null}
+ * if unavailable.
+ */
+ public @Nullable Bitmap getAppIcon() {
+ if (appIcon == null) {
+ // Icon may have been omitted for calls that return bulk session
+ // lists, so try fetching the specific icon.
+ try {
+ final SessionInfo info = AppGlobals.getPackageManager().getPackageInstaller()
+ .getSessionInfo(sessionId);
+ appIcon = (info != null) ? info.appIcon : null;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return appIcon;
+ }
+
+ /**
+ * Return a label representing the app being installed. May be {@code null}
+ * if unavailable.
+ */
+ public @Nullable CharSequence getAppLabel() {
+ return appLabel;
+ }
+
+ /**
+ * Return an Intent that can be started to view details about this install
+ * session. This may surface actions such as pause, resume, or cancel.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
+ *
+ * @see PackageInstaller#ACTION_SESSION_DETAILS
+ */
+ public @Nullable Intent createDetailsIntent() {
+ final Intent intent = new Intent(PackageInstaller.ACTION_SESSION_DETAILS);
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ intent.setPackage(installerPackageName);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ /**
+ * Get the mode of the session as set in the constructor of the {@link SessionParams}.
+ *
+ * @return One of {@link SessionParams#MODE_FULL_INSTALL}
+ * or {@link SessionParams#MODE_INHERIT_EXISTING}
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setInstallLocation(int)}.
+ */
+ public int getInstallLocation() {
+ return installLocation;
+ }
+
+ /**
+ * Get the value as set in {@link SessionParams#setSize(long)}.
+ *
+ * <p>The value is a hint and does not have to match the actual size.
+ */
+ public long getSize() {
+ return sizeBytes;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUri(Uri)}.
+ * Note: This value will only be non-null for the owner of the session.
+ */
+ public @Nullable Uri getOriginatingUri() {
+ return originatingUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUid(int)}.
+ */
+ public int getOriginatingUid() {
+ return originatingUid;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setReferrerUri(Uri)}
+ * Note: This value will only be non-null for the owner of the session.
+ */
+ public @Nullable Uri getReferrerUri() {
+ return referrerUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setGrantedRuntimePermissions(String[])}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String[] getGrantedRuntimePermissions() {
+ return grantedRuntimePermissions;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ * Note that if all permissions are allowlisted this method returns {@link
+ * SessionParams#RESTRICTED_PERMISSIONS_ALL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Set<String> getWhitelistedRestrictedPermissions() {
+ if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0) {
+ return SessionParams.RESTRICTED_PERMISSIONS_ALL;
+ }
+ if (whitelistedRestrictedPermissions != null) {
+ return new ArraySet<>(whitelistedRestrictedPermissions);
+ }
+ return Collections.emptySet();
+ }
+
+ /**
+ * Get the status of whether permission auto-revocation should be allowed, ignored, or
+ * deferred to manifest data.
+ *
+ * @see android.app.AppOpsManager#MODE_ALLOWED
+ * @see android.app.AppOpsManager#MODE_IGNORED
+ * @see android.app.AppOpsManager#MODE_DEFAULT
+ *
+ * @return the status of auto-revoke for this package
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getAutoRevokePermissionsMode() {
+ return autoRevokePermissionsMode;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}.
+ *
+ * @deprecated use {@link #getRequestDowngrade()}.
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ public boolean getAllowDowngrade() {
+ return getRequestDowngrade();
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setRequestDowngrade(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getRequestDowngrade() {
+ return (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setDontKillApp(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getDontKillApp() {
+ return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0;
+ }
+
+ /**
+ * Get if this session is to be installed as Instant Apps.
+ *
+ * @param isInstantApp an unused parameter and is ignored.
+ * @return {@code true} if {@link SessionParams#setInstallAsInstantApp(boolean)} was called
+ * with {@code true}; {@code false} if it was called with {@code false} or if it was not
+ * called.
+ *
+ * @see #getInstallAsFullApp
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getInstallAsInstantApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ }
+
+ /**
+ * Get if this session is to be installed as full apps.
+ *
+ * @param isInstantApp an unused parameter and is ignored.
+ * @return {@code true} if {@link SessionParams#setInstallAsInstantApp(boolean)} was called
+ * with {@code false}; {code false} if it was called with {@code true} or if it was not
+ * called.
+ *
+ * @see #getInstallAsInstantApp
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getInstallAsFullApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+ }
+
+ /**
+ * Get if {@link SessionParams#setInstallAsVirtualPreload()} was called.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getInstallAsVirtualPreload() {
+ return (installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0;
+ }
+
+ /**
+ * Return whether rollback is enabled or disabled for the given upgrade.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getEnableRollback() {
+ return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllocateAggressive(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllocateAggressive() {
+ return (installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0;
+ }
+
+
+ /** {@hide} */
+ @Deprecated
+ public @Nullable Intent getDetailsIntent() {
+ return createDetailsIntent();
+ }
+
+ /**
+ * Returns true if this session is a multi-package session containing references to other
+ * sessions.
+ */
+ public boolean isMultiPackage() {
+ return isMultiPackage;
+ }
+
+ /**
+ * Returns true if this session is a staged session.
+ */
+ public boolean isStaged() {
+ return isStaged;
+ }
+
+ /**
+ * Return the data policy associated with the rollback for the given upgrade.
+ *
+ * @hide
+ */
+ @SystemApi
+ @PackageManager.RollbackDataPolicy
+ public int getRollbackDataPolicy() {
+ return rollbackDataPolicy;
+ }
+
+ /**
+ * Returns true if this session is marked as forceQueryable
+ * {@hide}
+ */
+ public boolean isForceQueryable() {
+ return forceQueryable;
+ }
+
+ /**
+ * Returns {@code true} if this session is an active staged session.
+ *
+ * We consider a session active if it has been committed and it is either pending
+ * verification, or will be applied at next reboot.
+ *
+ * <p>Staged session is active iff:
+ * <ul>
+ * <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and
+ * <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code
+ * false}, and
+ * <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is
+ * {@code false}.
+ * </ul>
+ *
+ * <p>In case of a multi-package session, reasoning above is applied to the parent session,
+ * since that is the one that should have been {@link Session#commit committed}.
+ */
+ public boolean isStagedSessionActive() {
+ return isStaged && isCommitted && !isStagedSessionApplied && !isStagedSessionFailed
+ && !hasParentSessionId();
+ }
+
+ /**
+ * Returns the parent multi-package session ID if this session belongs to one,
+ * {@link #INVALID_ID} otherwise.
+ */
+ public int getParentSessionId() {
+ return parentSessionId;
+ }
+
+ /**
+ * Returns true if session has a valid parent session, otherwise false.
+ */
+ public boolean hasParentSessionId() {
+ return parentSessionId != INVALID_ID;
+ }
+
+ /**
+ * Returns the set of session IDs that will be committed when this session is commited if
+ * this session is a multi-package session.
+ */
+ @NonNull
+ public int[] getChildSessionIds() {
+ return childSessionIds;
+ }
+
+ private void checkSessionIsStaged() {
+ if (!isStaged) {
+ throw new IllegalStateException("Session is not marked as staged.");
+ }
+ }
+
+ /**
+ * Whether the staged session has been applied successfully, meaning that all of its
+ * packages have been activated and no further action is required.
+ * Only meaningful if {@code isStaged} is true.
+ */
+ public boolean isStagedSessionApplied() {
+ checkSessionIsStaged();
+ return isStagedSessionApplied;
+ }
+
+ /**
+ * Whether the staged session is ready to be applied at next reboot. Only meaningful if
+ * {@code isStaged} is true.
+ */
+ public boolean isStagedSessionReady() {
+ checkSessionIsStaged();
+ return isStagedSessionReady;
+ }
+
+ /**
+ * Whether something went wrong and the staged session is declared as failed, meaning that
+ * it will be ignored at next reboot. Only meaningful if {@code isStaged} is true.
+ */
+ public boolean isStagedSessionFailed() {
+ checkSessionIsStaged();
+ return isStagedSessionFailed;
+ }
+
+ /**
+ * If something went wrong with a staged session, clients can check this error code to
+ * understand which kind of failure happened. Only meaningful if {@code isStaged} is true.
+ */
+ public @StagedSessionErrorCode int getStagedSessionErrorCode() {
+ checkSessionIsStaged();
+ return mStagedSessionErrorCode;
+ }
+
+ /**
+ * Text description of the error code returned by {@code getStagedSessionErrorCode}, or
+ * empty string if no error was encountered.
+ */
+ public @NonNull String getStagedSessionErrorMessage() {
+ checkSessionIsStaged();
+ return mStagedSessionErrorMessage;
+ }
+
+ /** {@hide} */
+ public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode,
+ String errorMessage) {
+ mStagedSessionErrorCode = errorCode;
+ mStagedSessionErrorMessage = errorMessage;
+ }
+
+ /**
+ * Returns {@code true} if {@link Session#commit(IntentSender)}} was called for this
+ * session.
+ */
+ public boolean isCommitted() {
+ return isCommitted;
+ }
+
+ /**
+ * The timestamp of the initial creation of the session.
+ */
+ public long getCreatedMillis() {
+ return createdMillis;
+ }
+
+ /**
+ * The timestamp of the last update that occurred to the session, including changing of
+ * states in case of staged sessions.
+ */
+ @CurrentTimeMillisLong
+ public long getUpdatedMillis() {
+ return updatedMillis;
+ }
+
+ /**
+ * Whether user action was required by the installer.
+ *
+ * <p>
+ * Note: a return value of {@code USER_ACTION_NOT_REQUIRED} does not guarantee that the
+ * install will not result in user action.
+ *
+ * @return {@link SessionParams#USER_ACTION_NOT_REQUIRED},
+ * {@link SessionParams#USER_ACTION_REQUIRED} or
+ * {@link SessionParams#USER_ACTION_UNSPECIFIED}
+ */
+ @SessionParams.UserActionRequirement
+ public int getRequireUserAction() {
+ return requireUserAction;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(sessionId);
+ dest.writeInt(userId);
+ dest.writeString(installerPackageName);
+ dest.writeString(installerAttributionTag);
+ dest.writeString(resolvedBaseCodePath);
+ dest.writeFloat(progress);
+ dest.writeInt(sealed ? 1 : 0);
+ dest.writeInt(active ? 1 : 0);
+
+ dest.writeInt(mode);
+ dest.writeInt(installReason);
+ dest.writeInt(installScenario);
+ dest.writeLong(sizeBytes);
+ dest.writeString(appPackageName);
+ dest.writeParcelable(appIcon, flags);
+ dest.writeString(appLabel != null ? appLabel.toString() : null);
+
+ dest.writeInt(installLocation);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeInt(originatingUid);
+ dest.writeParcelable(referrerUri, flags);
+ dest.writeStringArray(grantedRuntimePermissions);
+ dest.writeStringList(whitelistedRestrictedPermissions);
+ dest.writeInt(autoRevokePermissionsMode);
+ dest.writeInt(installFlags);
+ dest.writeBoolean(isMultiPackage);
+ dest.writeBoolean(isStaged);
+ dest.writeBoolean(forceQueryable);
+ dest.writeInt(parentSessionId);
+ dest.writeIntArray(childSessionIds);
+ dest.writeBoolean(isStagedSessionApplied);
+ dest.writeBoolean(isStagedSessionReady);
+ dest.writeBoolean(isStagedSessionFailed);
+ dest.writeInt(mStagedSessionErrorCode);
+ dest.writeString(mStagedSessionErrorMessage);
+ dest.writeBoolean(isCommitted);
+ dest.writeInt(rollbackDataPolicy);
+ dest.writeLong(createdMillis);
+ dest.writeInt(requireUserAction);
+ }
+
+ public static final Parcelable.Creator<SessionInfo>
+ CREATOR = new Parcelable.Creator<SessionInfo>() {
+ @Override
+ public SessionInfo createFromParcel(Parcel p) {
+ return new SessionInfo(p);
+ }
+
+ @Override
+ public SessionInfo[] newArray(int size) {
+ return new SessionInfo[size];
+ }
+ };
+ }
+}
diff --git a/android/content/pm/PackageItemInfo.java b/android/content/pm/PackageItemInfo.java
new file mode 100644
index 0000000..2bac066
--- /dev/null
+++ b/android/content/pm/PackageItemInfo.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2007 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.text.TextUtils.SAFE_STRING_FLAG_FIRST_LINE;
+import static android.text.TextUtils.SAFE_STRING_FLAG_SINGLE_LINE;
+import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM;
+import static android.text.TextUtils.makeSafeForPresentation;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+
+
+import java.text.Collator;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * Base class containing information common to all package items held by
+ * the package manager. This provides a very common basic set of attributes:
+ * a label, icon, and meta-data. This class is not intended
+ * to be used by itself; it is simply here to share common definitions
+ * between all items returned by the package manager. As such, it does not
+ * itself implement Parcelable, but does provide convenience methods to assist
+ * in the implementation of Parcelable in subclasses.
+ */
+public class PackageItemInfo {
+
+ /**
+ * The maximum length of a safe label, in characters
+ *
+ * TODO(b/157997155): It may make sense to expose this publicly so that apps can check for the
+ * value and truncate the strings/use a different label, without having to hardcode and make
+ * assumptions about the value.
+ * @hide
+ */
+ public static final int MAX_SAFE_LABEL_LENGTH = 1000;
+
+ /** @hide */
+ public static final float DEFAULT_MAX_LABEL_SIZE_PX = 1000f;
+
+ /**
+ * Remove {@link Character#isWhitespace(int) whitespace} and non-breaking spaces from the edges
+ * of the label.
+ *
+ * @see #loadSafeLabel(PackageManager, float, int)
+ *
+ * @deprecated Use {@link TextUtils#SAFE_STRING_FLAG_TRIM} instead
+ * @hide
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ public static final int SAFE_LABEL_FLAG_TRIM = SAFE_STRING_FLAG_TRIM;
+
+ /**
+ * Force entire string into single line of text (no newlines). Cannot be set at the same time as
+ * {@link #SAFE_LABEL_FLAG_FIRST_LINE}.
+ *
+ * @see #loadSafeLabel(PackageManager, float, int)
+ *
+ * @deprecated Use {@link TextUtils#SAFE_STRING_FLAG_SINGLE_LINE} instead
+ * @hide
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ public static final int SAFE_LABEL_FLAG_SINGLE_LINE = SAFE_STRING_FLAG_SINGLE_LINE;
+
+ /**
+ * Return only first line of text (truncate at first newline). Cannot be set at the same time as
+ * {@link #SAFE_LABEL_FLAG_SINGLE_LINE}.
+ *
+ * @see #loadSafeLabel(PackageManager, float, int)
+ *
+ * @deprecated Use {@link TextUtils#SAFE_STRING_FLAG_FIRST_LINE} instead
+ * @hide
+ * @removed
+ */
+ @Deprecated
+ @SystemApi
+ public static final int SAFE_LABEL_FLAG_FIRST_LINE = SAFE_STRING_FLAG_FIRST_LINE;
+
+ private static volatile boolean sForceSafeLabels = false;
+
+ /**
+ * Always use {@link #loadSafeLabel safe labels} when calling {@link #loadLabel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void forceSafeLabels() {
+ sForceSafeLabels = true;
+ }
+
+ /**
+ * Public name of this item. From the "android:name" attribute.
+ */
+ public String name;
+
+ /**
+ * Name of the package that this item is in.
+ */
+ public String packageName;
+
+ /**
+ * A string resource identifier (in the package's resources) of this
+ * component's label. From the "label" attribute or, if not set, 0.
+ */
+ public int labelRes;
+
+ /**
+ * The string provided in the AndroidManifest file, if any. You
+ * probably don't want to use this. You probably want
+ * {@link PackageManager#getApplicationLabel}
+ */
+ public CharSequence nonLocalizedLabel;
+
+ /**
+ * A drawable resource identifier (in the package's resources) of this
+ * component's icon. From the "icon" attribute or, if not set, 0.
+ */
+ public int icon;
+
+ /**
+ * A drawable resource identifier (in the package's resources) of this
+ * component's banner. From the "banner" attribute or, if not set, 0.
+ */
+ public int banner;
+
+ /**
+ * A drawable resource identifier (in the package's resources) of this
+ * component's logo. Logos may be larger/wider than icons and are
+ * displayed by certain UI elements in place of a name or name/icon
+ * combination. From the "logo" attribute or, if not set, 0.
+ */
+ public int logo;
+
+ /**
+ * Additional meta-data associated with this component. This field
+ * will only be filled in if you set the
+ * {@link PackageManager#GET_META_DATA} flag when requesting the info.
+ */
+ public Bundle metaData;
+
+ /**
+ * If different of UserHandle.USER_NULL, The icon of this item will represent that user.
+ * @hide
+ */
+ public int showUserIcon;
+
+ public PackageItemInfo() {
+ showUserIcon = UserHandle.USER_NULL;
+ }
+
+ public PackageItemInfo(PackageItemInfo orig) {
+ name = orig.name;
+ if (name != null) name = name.trim();
+ packageName = orig.packageName;
+ labelRes = orig.labelRes;
+ nonLocalizedLabel = orig.nonLocalizedLabel;
+ if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim();
+ icon = orig.icon;
+ banner = orig.banner;
+ logo = orig.logo;
+ metaData = orig.metaData;
+ showUserIcon = orig.showUserIcon;
+ }
+
+ /**
+ * Retrieve the current textual label associated with this item. This
+ * will call back on the given PackageManager to load the label from
+ * the application.
+ *
+ * @param pm A PackageManager from which the label can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a CharSequence containing the item's label. If the
+ * item does not have a label, its name is returned.
+ */
+ public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) {
+ if (sForceSafeLabels && !Objects.equals(packageName, ActivityThread.currentPackageName())) {
+ return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
+ | SAFE_STRING_FLAG_FIRST_LINE);
+ } else {
+ // Trims the label string to the MAX_SAFE_LABEL_LENGTH. This is to prevent that the
+ // system is overwhelmed by an enormous string returned by the application.
+ return TextUtils.trimToSize(loadUnsafeLabel(pm), MAX_SAFE_LABEL_LENGTH);
+ }
+ }
+
+ /** {@hide} */
+ public CharSequence loadUnsafeLabel(PackageManager pm) {
+ if (nonLocalizedLabel != null) {
+ return nonLocalizedLabel;
+ }
+ if (labelRes != 0) {
+ CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
+ if (label != null) {
+ return label.toString().trim();
+ }
+ }
+ if (name != null) {
+ return name;
+ }
+ return packageName;
+ }
+
+ /**
+ * @hide
+ * @deprecated use loadSafeLabel(PackageManager, float, int) instead
+ */
+ @SystemApi
+ @Deprecated
+ public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
+ return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
+ | SAFE_STRING_FLAG_FIRST_LINE);
+ }
+
+ /**
+ * Calls {@link TextUtils#makeSafeForPresentation} for the label of this item.
+ *
+ * <p>For parameters see {@link TextUtils#makeSafeForPresentation}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm,
+ @FloatRange(from = 0) float ellipsizeDip, @TextUtils.SafeStringFlags int flags) {
+ Objects.requireNonNull(pm);
+
+ return makeSafeForPresentation(loadUnsafeLabel(pm).toString(), MAX_SAFE_LABEL_LENGTH,
+ ellipsizeDip, flags);
+ }
+
+ /**
+ * Retrieve the current graphical icon associated with this item. This
+ * will call back on the given PackageManager to load the icon from
+ * the application.
+ *
+ * @param pm A PackageManager from which the icon can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's icon. If the
+ * item does not have an icon, the item's default icon is returned
+ * such as the default activity icon.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ return pm.loadItemIcon(this, getApplicationInfo());
+ }
+
+ /**
+ * Retrieve the current graphical icon associated with this item without
+ * the addition of a work badge if applicable.
+ * This will call back on the given PackageManager to load the icon from
+ * the application.
+ *
+ * @param pm A PackageManager from which the icon can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's icon. If the
+ * item does not have an icon, the item's default icon is returned
+ * such as the default activity icon.
+ */
+ public Drawable loadUnbadgedIcon(PackageManager pm) {
+ return pm.loadUnbadgedItemIcon(this, getApplicationInfo());
+ }
+
+ /**
+ * Retrieve the current graphical banner associated with this item. This
+ * will call back on the given PackageManager to load the banner from
+ * the application.
+ *
+ * @param pm A PackageManager from which the banner can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's banner. If the item
+ * does not have a banner, this method will return null.
+ */
+ public Drawable loadBanner(PackageManager pm) {
+ if (banner != 0) {
+ Drawable dr = pm.getDrawable(packageName, banner, getApplicationInfo());
+ if (dr != null) {
+ return dr;
+ }
+ }
+ return loadDefaultBanner(pm);
+ }
+
+ /**
+ * Retrieve the default graphical icon associated with this item.
+ *
+ * @param pm A PackageManager from which the icon can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's default icon
+ * such as the default activity icon.
+ *
+ * @hide
+ */
+ public Drawable loadDefaultIcon(PackageManager pm) {
+ return pm.getDefaultActivityIcon();
+ }
+
+ /**
+ * Retrieve the default graphical banner associated with this item.
+ *
+ * @param pm A PackageManager from which the banner can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's default banner
+ * or null if no default logo is available.
+ *
+ * @hide
+ */
+ protected Drawable loadDefaultBanner(PackageManager pm) {
+ return null;
+ }
+
+ /**
+ * Retrieve the current graphical logo associated with this item. This
+ * will call back on the given PackageManager to load the logo from
+ * the application.
+ *
+ * @param pm A PackageManager from which the logo can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's logo. If the item
+ * does not have a logo, this method will return null.
+ */
+ public Drawable loadLogo(PackageManager pm) {
+ if (logo != 0) {
+ Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo());
+ if (d != null) {
+ return d;
+ }
+ }
+ return loadDefaultLogo(pm);
+ }
+
+ /**
+ * Retrieve the default graphical logo associated with this item.
+ *
+ * @param pm A PackageManager from which the logo can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's default logo
+ * or null if no default logo is available.
+ *
+ * @hide
+ */
+ protected Drawable loadDefaultLogo(PackageManager pm) {
+ return null;
+ }
+
+ /**
+ * Load an XML resource attached to the meta-data of this item. This will
+ * retrieved the name meta-data entry, and if defined call back on the
+ * given PackageManager to load its XML file from the application.
+ *
+ * @param pm A PackageManager from which the XML can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ * @param name Name of the meta-date you would like to load.
+ *
+ * @return Returns an XmlPullParser you can use to parse the XML file
+ * assigned as the given meta-data. If the meta-data name is not defined
+ * or the XML resource could not be found, null is returned.
+ */
+ public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) {
+ if (metaData != null) {
+ int resid = metaData.getInt(name);
+ if (resid != 0) {
+ return pm.getXml(packageName, resid, getApplicationInfo());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide Flag for dumping: include all details.
+ */
+ public static final int DUMP_FLAG_DETAILS = 1<<0;
+
+ /**
+ * @hide Flag for dumping: include nested ApplicationInfo.
+ */
+ public static final int DUMP_FLAG_APPLICATION = 1<<1;
+
+ /**
+ * @hide Flag for dumping: all flags to dump everything.
+ */
+ public static final int DUMP_FLAG_ALL = DUMP_FLAG_DETAILS | DUMP_FLAG_APPLICATION;
+
+ protected void dumpFront(Printer pw, String prefix) {
+ if (name != null) {
+ pw.println(prefix + "name=" + name);
+ }
+ pw.println(prefix + "packageName=" + packageName);
+ if (labelRes != 0 || nonLocalizedLabel != null || icon != 0 || banner != 0) {
+ pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
+ + " nonLocalizedLabel=" + nonLocalizedLabel
+ + " icon=0x" + Integer.toHexString(icon)
+ + " banner=0x" + Integer.toHexString(banner));
+ }
+ }
+
+ protected void dumpBack(Printer pw, String prefix) {
+ // no back here
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString8(name);
+ dest.writeString8(packageName);
+ dest.writeInt(labelRes);
+ TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
+ dest.writeInt(icon);
+ dest.writeInt(logo);
+ dest.writeBundle(metaData);
+ dest.writeInt(banner);
+ dest.writeInt(showUserIcon);
+ }
+
+ /**
+ * @hide
+ */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId, int dumpFlags) {
+ long token = proto.start(fieldId);
+ if (name != null) {
+ proto.write(PackageItemInfoProto.NAME, name);
+ }
+ proto.write(PackageItemInfoProto.PACKAGE_NAME, packageName);
+ proto.write(PackageItemInfoProto.LABEL_RES, labelRes);
+ if (nonLocalizedLabel != null) {
+ proto.write(PackageItemInfoProto.NON_LOCALIZED_LABEL, nonLocalizedLabel.toString());
+ }
+ proto.write(PackageItemInfoProto.ICON, icon);
+ proto.write(PackageItemInfoProto.BANNER, banner);
+ proto.end(token);
+ }
+
+ protected PackageItemInfo(Parcel source) {
+ name = source.readString8();
+ packageName = source.readString8();
+ labelRes = source.readInt();
+ nonLocalizedLabel
+ = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ icon = source.readInt();
+ logo = source.readInt();
+ metaData = source.readBundle();
+ banner = source.readInt();
+ showUserIcon = source.readInt();
+ }
+
+ /**
+ * Get the ApplicationInfo for the application to which this item belongs,
+ * if available, otherwise returns null.
+ *
+ * @return Returns the ApplicationInfo of this item, or null if not known.
+ *
+ * @hide
+ */
+ protected ApplicationInfo getApplicationInfo() {
+ return null;
+ }
+
+ public static class DisplayNameComparator
+ implements Comparator<PackageItemInfo> {
+ public DisplayNameComparator(PackageManager pm) {
+ mPM = pm;
+ }
+
+ public final int compare(PackageItemInfo aa, PackageItemInfo ab) {
+ CharSequence sa = aa.loadLabel(mPM);
+ if (sa == null) sa = aa.name;
+ CharSequence sb = ab.loadLabel(mPM);
+ if (sb == null) sb = ab.name;
+ return sCollator.compare(sa.toString(), sb.toString());
+ }
+
+ private final Collator sCollator = Collator.getInstance();
+ private PackageManager mPM;
+ }
+}
diff --git a/android/content/pm/PackageManager.java b/android/content/pm/PackageManager.java
new file mode 100644
index 0000000..2ed00b5
--- /dev/null
+++ b/android/content/pm/PackageManager.java
@@ -0,0 +1,9307 @@
+/*
+ * Copyright (C) 2006 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.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.CheckResult;
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringRes;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.annotation.XmlRes;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.AppDetailsActivity;
+import android.app.PackageDeleteObserver;
+import android.app.PackageInstallObserver;
+import android.app.PropertyInvalidatedCache;
+import android.app.admin.DevicePolicyManager;
+import android.app.usage.StorageStatsManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.dex.ArtManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.incremental.IncrementalManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.permission.PermissionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.GbaService;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDelegateManager;
+import android.util.AndroidException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import dalvik.system.VMRuntime;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Class for retrieving various kinds of information related to the application
+ * packages that are currently installed on the device.
+ *
+ * You can find this class through {@link Context#getPackageManager}.
+ *
+ * <p class="note"><strong>Note: </strong>If your app targets Android 11 (API level 30) or
+ * higher, the methods in this class each return a filtered list of apps. Learn more about how to
+ * <a href="/training/basics/intents/package-visibility">manage package visibility</a>.
+ * </p>
+ */
+public abstract class PackageManager {
+ private static final String TAG = "PackageManager";
+
+ /** {@hide} */
+ public static final boolean APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE = true;
+
+ /**
+ * This exception is thrown when a given package, application, or component
+ * name cannot be found.
+ */
+ public static class NameNotFoundException extends AndroidException {
+ public NameNotFoundException() {
+ }
+
+ public NameNotFoundException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * <application> level {@link android.content.pm.PackageManager.Property} tag specifying
+ * the XML resource ID containing an application's media capabilities XML file
+ *
+ * For example:
+ * <application>
+ * <property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+ * android:resource="@xml/media_capabilities">
+ * <application>
+ */
+ public static final String PROPERTY_MEDIA_CAPABILITIES =
+ "android.media.PROPERTY_MEDIA_CAPABILITIES";
+
+ /**
+ * A property value set within the manifest.
+ * <p>
+ * The value of a property will only have a single type, as defined by
+ * the property itself.
+ */
+ public static final class Property implements Parcelable {
+ private static final int TYPE_BOOLEAN = 1;
+ private static final int TYPE_FLOAT = 2;
+ private static final int TYPE_INTEGER = 3;
+ private static final int TYPE_RESOURCE = 4;
+ private static final int TYPE_STRING = 5;
+ private final String mName;
+ private final int mType;
+ private final String mClassName;
+ private final String mPackageName;
+ private boolean mBooleanValue;
+ private float mFloatValue;
+ private int mIntegerValue;
+ private String mStringValue;
+
+ /** @hide */
+ @VisibleForTesting
+ public Property(@NonNull String name, int type,
+ @NonNull String packageName, @Nullable String className) {
+ assert name != null;
+ assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
+ assert packageName != null;
+ this.mName = name;
+ this.mType = type;
+ this.mPackageName = packageName;
+ this.mClassName = className;
+ }
+ /** @hide */
+ public Property(@NonNull String name, boolean value,
+ String packageName, String className) {
+ this(name, TYPE_BOOLEAN, packageName, className);
+ mBooleanValue = value;
+ }
+ /** @hide */
+ public Property(@NonNull String name, float value,
+ String packageName, String className) {
+ this(name, TYPE_FLOAT, packageName, className);
+ mFloatValue = value;
+ }
+ /** @hide */
+ public Property(@NonNull String name, int value, boolean isResource,
+ String packageName, String className) {
+ this(name, isResource ? TYPE_RESOURCE : TYPE_INTEGER, packageName, className);
+ mIntegerValue = value;
+ }
+ /** @hide */
+ public Property(@NonNull String name, String value,
+ String packageName, String className) {
+ this(name, TYPE_STRING, packageName, className);
+ mStringValue = value;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the property.
+ */
+ @NonNull public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the name of the package where this this property was defined.
+ */
+ @NonNull public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the classname of the component where this property was defined.
+ * <p>If the property was defined within and <application> tag, retutrns
+ * {@code null}
+ */
+ @Nullable public String getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Returns the boolean value set for the property.
+ * <p>If the property is not of a boolean type, returns {@code false}.
+ */
+ public boolean getBoolean() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@code true} if the property is a boolean type. Otherwise {@code false}.
+ */
+ public boolean isBoolean() {
+ return mType == TYPE_BOOLEAN;
+ }
+
+ /**
+ * Returns the float value set for the property.
+ * <p>If the property is not of a float type, returns {@code 0.0}.
+ */
+ public float getFloat() {
+ return mFloatValue;
+ }
+
+ /**
+ * Returns {@code true} if the property is a float type. Otherwise {@code false}.
+ */
+ public boolean isFloat() {
+ return mType == TYPE_FLOAT;
+ }
+
+ /**
+ * Returns the integer value set for the property.
+ * <p>If the property is not of an integer type, returns {@code 0}.
+ */
+ public int getInteger() {
+ return mType == TYPE_INTEGER ? mIntegerValue : 0;
+ }
+
+ /**
+ * Returns {@code true} if the property is an integer type. Otherwise {@code false}.
+ */
+ public boolean isInteger() {
+ return mType == TYPE_INTEGER;
+ }
+
+ /**
+ * Returns the a resource id set for the property.
+ * <p>If the property is not of a resource id type, returns {@code 0}.
+ */
+ public int getResourceId() {
+ return mType == TYPE_RESOURCE ? mIntegerValue : 0;
+ }
+
+ /**
+ * Returns {@code true} if the property is a resource id type. Otherwise {@code false}.
+ */
+ public boolean isResourceId() {
+ return mType == TYPE_RESOURCE;
+ }
+
+ /**
+ * Returns the a String value set for the property.
+ * <p>If the property is not a String type, returns {@code null}.
+ */
+ @Nullable public String getString() {
+ return mStringValue;
+ }
+
+ /**
+ * Returns {@code true} if the property is a String type. Otherwise {@code false}.
+ */
+ public boolean isString() {
+ return mType == TYPE_STRING;
+ }
+
+ /**
+ * Adds a mapping from the given key to this property's value in the provided
+ * {@link android.os.Bundle}. If the provided {@link android.os.Bundle} is
+ * {@code null}, creates a new {@link android.os.Bundle}.
+ * @hide
+ */
+ public Bundle toBundle(Bundle outBundle) {
+ final Bundle b = outBundle == null ? new Bundle() : outBundle;
+ if (mType == TYPE_BOOLEAN) {
+ b.putBoolean(mName, mBooleanValue);
+ } else if (mType == TYPE_FLOAT) {
+ b.putFloat(mName, mFloatValue);
+ } else if (mType == TYPE_INTEGER) {
+ b.putInt(mName, mIntegerValue);
+ } else if (mType == TYPE_RESOURCE) {
+ b.putInt(mName, mIntegerValue);
+ } else if (mType == TYPE_STRING) {
+ b.putString(mName, mStringValue);
+ }
+ return b;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeInt(mType);
+ dest.writeString(mPackageName);
+ dest.writeString(mClassName);
+ if (mType == TYPE_BOOLEAN) {
+ dest.writeBoolean(mBooleanValue);
+ } else if (mType == TYPE_FLOAT) {
+ dest.writeFloat(mFloatValue);
+ } else if (mType == TYPE_INTEGER) {
+ dest.writeInt(mIntegerValue);
+ } else if (mType == TYPE_RESOURCE) {
+ dest.writeInt(mIntegerValue);
+ } else if (mType == TYPE_STRING) {
+ dest.writeString(mStringValue);
+ }
+ }
+
+ @NonNull
+ public static final Creator<Property> CREATOR = new Creator<Property>() {
+ @Override
+ public Property createFromParcel(@NonNull Parcel source) {
+ final String name = source.readString();
+ final int type = source.readInt();
+ final String packageName = source.readString();
+ final String className = source.readString();
+ if (type == TYPE_BOOLEAN) {
+ return new Property(name, source.readBoolean(), packageName, className);
+ } else if (type == TYPE_FLOAT) {
+ return new Property(name, source.readFloat(), packageName, className);
+ } else if (type == TYPE_INTEGER) {
+ return new Property(name, source.readInt(), false, packageName, className);
+ } else if (type == TYPE_RESOURCE) {
+ return new Property(name, source.readInt(), true, packageName, className);
+ } else if (type == TYPE_STRING) {
+ return new Property(name, source.readString(), packageName, className);
+ }
+ return null;
+ }
+
+ @Override
+ public Property[] newArray(int size) {
+ return new Property[size];
+ }
+ };
+ }
+
+ /**
+ * Listener for changes in permissions granted to a UID.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface OnPermissionsChangedListener {
+
+ /**
+ * Called when the permissions for a UID change.
+ * @param uid The UID with a change.
+ */
+ public void onPermissionsChanged(int uid);
+ }
+
+ /** @hide */
+ public static final int TYPE_UNKNOWN = 0;
+ /** @hide */
+ public static final int TYPE_ACTIVITY = 1;
+ /** @hide */
+ public static final int TYPE_RECEIVER = 2;
+ /** @hide */
+ public static final int TYPE_SERVICE = 3;
+ /** @hide */
+ public static final int TYPE_PROVIDER = 4;
+ /** @hide */
+ public static final int TYPE_APPLICATION = 5;
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_UNKNOWN,
+ TYPE_ACTIVITY,
+ TYPE_RECEIVER,
+ TYPE_SERVICE,
+ TYPE_PROVIDER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ComponentType {}
+
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_UNKNOWN,
+ TYPE_ACTIVITY,
+ TYPE_RECEIVER,
+ TYPE_SERVICE,
+ TYPE_PROVIDER,
+ TYPE_APPLICATION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PropertyLocation {}
+
+ /**
+ * As a guiding principle:
+ * <p>
+ * {@code GET_} flags are used to request additional data that may have been
+ * elided to save wire space.
+ * <p>
+ * {@code MATCH_} flags are used to include components or packages that
+ * would have otherwise been omitted from a result set by current system
+ * state.
+ */
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_ACTIVITIES,
+ GET_CONFIGURATIONS,
+ GET_GIDS,
+ GET_INSTRUMENTATION,
+ GET_INTENT_FILTERS,
+ GET_META_DATA,
+ GET_PERMISSIONS,
+ GET_PROVIDERS,
+ GET_RECEIVERS,
+ GET_SERVICES,
+ GET_SHARED_LIBRARY_FILES,
+ GET_SIGNATURES,
+ GET_SIGNING_CERTIFICATES,
+ GET_URI_PERMISSION_PATTERNS,
+ MATCH_UNINSTALLED_PACKAGES,
+ MATCH_DISABLED_COMPONENTS,
+ MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ MATCH_SYSTEM_ONLY,
+ MATCH_FACTORY_ONLY,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ MATCH_INSTANT,
+ MATCH_APEX,
+ GET_DISABLED_COMPONENTS,
+ GET_DISABLED_UNTIL_USED_COMPONENTS,
+ GET_UNINSTALLED_PACKAGES,
+ MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
+ GET_ATTRIBUTIONS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PackageInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_META_DATA,
+ GET_SHARED_LIBRARY_FILES,
+ MATCH_UNINSTALLED_PACKAGES,
+ MATCH_SYSTEM_ONLY,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ MATCH_DISABLED_COMPONENTS,
+ MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ MATCH_INSTANT,
+ MATCH_STATIC_SHARED_LIBRARIES,
+ GET_DISABLED_UNTIL_USED_COMPONENTS,
+ GET_UNINSTALLED_PACKAGES,
+ MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
+ MATCH_APEX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_META_DATA,
+ GET_SHARED_LIBRARY_FILES,
+ MATCH_ALL,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ MATCH_DEFAULT_ONLY,
+ MATCH_DISABLED_COMPONENTS,
+ MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ MATCH_DIRECT_BOOT_AUTO,
+ MATCH_DIRECT_BOOT_AWARE,
+ MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_SYSTEM_ONLY,
+ MATCH_UNINSTALLED_PACKAGES,
+ MATCH_INSTANT,
+ MATCH_STATIC_SHARED_LIBRARIES,
+ GET_DISABLED_COMPONENTS,
+ GET_DISABLED_UNTIL_USED_COMPONENTS,
+ GET_UNINSTALLED_PACKAGES,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ComponentInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_META_DATA,
+ GET_RESOLVED_FILTER,
+ GET_SHARED_LIBRARY_FILES,
+ MATCH_ALL,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ MATCH_DISABLED_COMPONENTS,
+ MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ MATCH_DEFAULT_ONLY,
+ MATCH_DIRECT_BOOT_AUTO,
+ MATCH_DIRECT_BOOT_AWARE,
+ MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_SYSTEM_ONLY,
+ MATCH_UNINSTALLED_PACKAGES,
+ MATCH_INSTANT,
+ GET_DISABLED_COMPONENTS,
+ GET_DISABLED_UNTIL_USED_COMPONENTS,
+ GET_UNINSTALLED_PACKAGES,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResolveInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ MATCH_ALL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstalledModulesFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_META_DATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_META_DATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionGroupInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ GET_META_DATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstrumentationInfoFlags {}
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * activities in the package in {@link PackageInfo#activities}.
+ */
+ public static final int GET_ACTIVITIES = 0x00000001;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * intent receivers in the package in
+ * {@link PackageInfo#receivers}.
+ */
+ public static final int GET_RECEIVERS = 0x00000002;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * services in the package in {@link PackageInfo#services}.
+ */
+ public static final int GET_SERVICES = 0x00000004;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * content providers in the package in
+ * {@link PackageInfo#providers}.
+ */
+ public static final int GET_PROVIDERS = 0x00000008;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * instrumentation in the package in
+ * {@link PackageInfo#instrumentation}.
+ */
+ public static final int GET_INSTRUMENTATION = 0x00000010;
+
+ /**
+ * {@link PackageInfo} flag: return information about the
+ * intent filters supported by the activity.
+ *
+ * @deprecated The platform does not support getting {@link IntentFilter}s for the package.
+ */
+ @Deprecated
+ public static final int GET_INTENT_FILTERS = 0x00000020;
+
+ /**
+ * {@link PackageInfo} flag: return information about the
+ * signatures included in the package.
+ *
+ * @deprecated use {@code GET_SIGNING_CERTIFICATES} instead
+ */
+ @Deprecated
+ public static final int GET_SIGNATURES = 0x00000040;
+
+ /**
+ * {@link ResolveInfo} flag: return the IntentFilter that
+ * was matched for a particular ResolveInfo in
+ * {@link ResolveInfo#filter}.
+ */
+ public static final int GET_RESOLVED_FILTER = 0x00000040;
+
+ /**
+ * {@link ComponentInfo} flag: return the {@link ComponentInfo#metaData}
+ * data {@link android.os.Bundle}s that are associated with a component.
+ * This applies for any API returning a ComponentInfo subclass.
+ */
+ public static final int GET_META_DATA = 0x00000080;
+
+ /**
+ * {@link PackageInfo} flag: return the
+ * {@link PackageInfo#gids group ids} that are associated with an
+ * application.
+ * This applies for any API returning a PackageInfo class, either
+ * directly or nested inside of another.
+ */
+ public static final int GET_GIDS = 0x00000100;
+
+ /**
+ * @deprecated replaced with {@link #MATCH_DISABLED_COMPONENTS}
+ */
+ @Deprecated
+ public static final int GET_DISABLED_COMPONENTS = 0x00000200;
+
+ /**
+ * {@link PackageInfo} flag: include disabled components in the returned info.
+ */
+ public static final int MATCH_DISABLED_COMPONENTS = 0x00000200;
+
+ /**
+ * {@link ApplicationInfo} flag: return the
+ * {@link ApplicationInfo#sharedLibraryFiles paths to the shared libraries}
+ * that are associated with an application.
+ * This applies for any API returning an ApplicationInfo class, either
+ * directly or nested inside of another.
+ */
+ public static final int GET_SHARED_LIBRARY_FILES = 0x00000400;
+
+ /**
+ * {@link ProviderInfo} flag: return the
+ * {@link ProviderInfo#uriPermissionPatterns URI permission patterns}
+ * that are associated with a content provider.
+ * This applies for any API returning a ProviderInfo class, either
+ * directly or nested inside of another.
+ */
+ public static final int GET_URI_PERMISSION_PATTERNS = 0x00000800;
+ /**
+ * {@link PackageInfo} flag: return information about
+ * permissions in the package in
+ * {@link PackageInfo#permissions}.
+ */
+ public static final int GET_PERMISSIONS = 0x00001000;
+
+ /**
+ * @deprecated replaced with {@link #MATCH_UNINSTALLED_PACKAGES}
+ */
+ @Deprecated
+ public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
+
+ /**
+ * Flag parameter to retrieve some information about all applications (even
+ * uninstalled ones) which have data directories. This state could have
+ * resulted if applications have been deleted with flag
+ * {@code DELETE_KEEP_DATA} with a possibility of being replaced or
+ * reinstalled in future.
+ * <p>
+ * Note: this flag may cause less information about currently installed
+ * applications to be returned.
+ * <p>
+ * Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES
+ * permission to see uninstalled packages.
+ */
+ public static final int MATCH_UNINSTALLED_PACKAGES = 0x00002000;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * hardware preferences in
+ * {@link PackageInfo#configPreferences PackageInfo.configPreferences},
+ * and requested features in {@link PackageInfo#reqFeatures} and
+ * {@link PackageInfo#featureGroups}.
+ */
+ public static final int GET_CONFIGURATIONS = 0x00004000;
+
+ /**
+ * @deprecated replaced with {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS}.
+ */
+ @Deprecated
+ public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000;
+
+ /**
+ * {@link PackageInfo} flag: include disabled components which are in
+ * that state only because of {@link #COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED}
+ * in the returned info. Note that if you set this flag, applications
+ * that are in this disabled state will be reported as enabled.
+ */
+ public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000;
+
+ /**
+ * Resolution and querying flag: if set, only filters that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for
+ * matching. This is a synonym for including the CATEGORY_DEFAULT in your
+ * supplied Intent.
+ */
+ public static final int MATCH_DEFAULT_ONLY = 0x00010000;
+
+ /**
+ * Querying flag: if set and if the platform is doing any filtering of the
+ * results, then the filtering will not happen. This is a synonym for saying
+ * that all results should be returned.
+ * <p>
+ * <em>This flag should be used with extreme care.</em>
+ */
+ public static final int MATCH_ALL = 0x00020000;
+
+ /**
+ * Querying flag: match components which are direct boot <em>unaware</em> in
+ * the returned info, regardless of the current user state.
+ * <p>
+ * When neither {@link #MATCH_DIRECT_BOOT_AWARE} nor
+ * {@link #MATCH_DIRECT_BOOT_UNAWARE} are specified, the default behavior is
+ * to match only runnable components based on the user state. For example,
+ * when a user is started but credentials have not been presented yet, the
+ * user is running "locked" and only {@link #MATCH_DIRECT_BOOT_AWARE}
+ * components are returned. Once the user credentials have been presented,
+ * the user is running "unlocked" and both {@link #MATCH_DIRECT_BOOT_AWARE}
+ * and {@link #MATCH_DIRECT_BOOT_UNAWARE} components are returned.
+ *
+ * @see UserManager#isUserUnlocked()
+ */
+ public static final int MATCH_DIRECT_BOOT_UNAWARE = 0x00040000;
+
+ /**
+ * Querying flag: match components which are direct boot <em>aware</em> in
+ * the returned info, regardless of the current user state.
+ * <p>
+ * When neither {@link #MATCH_DIRECT_BOOT_AWARE} nor
+ * {@link #MATCH_DIRECT_BOOT_UNAWARE} are specified, the default behavior is
+ * to match only runnable components based on the user state. For example,
+ * when a user is started but credentials have not been presented yet, the
+ * user is running "locked" and only {@link #MATCH_DIRECT_BOOT_AWARE}
+ * components are returned. Once the user credentials have been presented,
+ * the user is running "unlocked" and both {@link #MATCH_DIRECT_BOOT_AWARE}
+ * and {@link #MATCH_DIRECT_BOOT_UNAWARE} components are returned.
+ *
+ * @see UserManager#isUserUnlocked()
+ */
+ public static final int MATCH_DIRECT_BOOT_AWARE = 0x00080000;
+
+ /**
+ * Querying flag: include only components from applications that are marked
+ * with {@link ApplicationInfo#FLAG_SYSTEM}.
+ */
+ public static final int MATCH_SYSTEM_ONLY = 0x00100000;
+
+ /**
+ * Internal {@link PackageInfo} flag: include only components on the system image.
+ * This will not return information on any unbundled update to system components.
+ * @hide
+ */
+ @SystemApi
+ public static final int MATCH_FACTORY_ONLY = 0x00200000;
+
+ /**
+ * Allows querying of packages installed for any user, not just the specific one. This flag
+ * is only meant for use by apps that have INTERACT_ACROSS_USERS permission.
+ * @hide
+ */
+ @SystemApi
+ public static final int MATCH_ANY_USER = 0x00400000;
+
+ /**
+ * Combination of MATCH_ANY_USER and MATCH_UNINSTALLED_PACKAGES to mean any known
+ * package.
+ * @hide
+ */
+ @TestApi
+ public static final int MATCH_KNOWN_PACKAGES = MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER;
+
+ /**
+ * Internal {@link PackageInfo} flag: include components that are part of an
+ * instant app. By default, instant app components are not matched.
+ * @hide
+ */
+ @SystemApi
+ public static final int MATCH_INSTANT = 0x00800000;
+
+ /**
+ * Internal {@link PackageInfo} flag: include only components that are exposed to
+ * instant apps. Matched components may have been either explicitly or implicitly
+ * exposed.
+ * @hide
+ */
+ public static final int MATCH_VISIBLE_TO_INSTANT_APP_ONLY = 0x01000000;
+
+ /**
+ * Internal {@link PackageInfo} flag: include only components that have been
+ * explicitly exposed to instant apps.
+ * @hide
+ */
+ public static final int MATCH_EXPLICITLY_VISIBLE_ONLY = 0x02000000;
+
+ /**
+ * Internal {@link PackageInfo} flag: include static shared libraries.
+ * Apps that depend on static shared libs can always access the version
+ * of the lib they depend on. System/shell/root can access all shared
+ * libs regardless of dependency but need to explicitly ask for them
+ * via this flag.
+ * @hide
+ */
+ public static final int MATCH_STATIC_SHARED_LIBRARIES = 0x04000000;
+
+ /**
+ * {@link PackageInfo} flag: return the signing certificates associated with
+ * this package. Each entry is a signing certificate that the package
+ * has proven it is authorized to use, usually a past signing certificate from
+ * which it has rotated.
+ */
+ public static final int GET_SIGNING_CERTIFICATES = 0x08000000;
+
+ /**
+ * Querying flag: automatically match components based on their Direct Boot
+ * awareness and the current user state.
+ * <p>
+ * Since the default behavior is to automatically apply the current user
+ * state, this is effectively a sentinel value that doesn't change the
+ * output of any queries based on its presence or absence.
+ * <p>
+ * Instead, this value can be useful in conjunction with
+ * {@link android.os.StrictMode.VmPolicy.Builder#detectImplicitDirectBoot()}
+ * to detect when a caller is relying on implicit automatic matching,
+ * instead of confirming the explicit behavior they want, using a
+ * combination of these flags:
+ * <ul>
+ * <li>{@link #MATCH_DIRECT_BOOT_AWARE}
+ * <li>{@link #MATCH_DIRECT_BOOT_UNAWARE}
+ * <li>{@link #MATCH_DIRECT_BOOT_AUTO}
+ * </ul>
+ */
+ public static final int MATCH_DIRECT_BOOT_AUTO = 0x10000000;
+
+ /**
+ * {@link PackageInfo} flag: return all attributions declared in the package manifest
+ */
+ public static final int GET_ATTRIBUTIONS = 0x80000000;
+
+ /** @hide */
+ @Deprecated
+ public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO;
+
+ /**
+ * {@link PackageInfo} flag: include system apps that are in the uninstalled state and have
+ * been set to be hidden until installed via {@link #setSystemAppState}.
+ * @hide
+ */
+ @SystemApi
+ public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 0x20000000;
+
+ /**
+ * {@link PackageInfo} flag: include APEX packages that are currently
+ * installed. In APEX terminology, this corresponds to packages that are
+ * currently active, i.e. mounted and available to other processes of the OS.
+ * In particular, this flag alone will not match APEX files that are staged
+ * for activation at next reboot.
+ */
+ public static final int MATCH_APEX = 0x40000000;
+
+ /**
+ * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
+ * resolving an intent that matches the {@code CrossProfileIntentFilter},
+ * the current profile will be skipped. Only activities in the target user
+ * can respond to the intent.
+ *
+ * @hide
+ */
+ public static final int SKIP_CURRENT_PROFILE = 0x00000002;
+
+ /**
+ * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set:
+ * activities in the other profiles can respond to the intent only if no activity with
+ * non-negative priority in current profile can respond to the intent.
+ * @hide
+ */
+ public static final int ONLY_IF_NO_MATCH_FOUND = 0x00000004;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "MODULE_" }, value = {
+ MODULE_APEX_NAME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ModuleInfoFlags {}
+
+ /**
+ * Flag for {@link #getModuleInfo}: allow ModuleInfo to be retrieved using the apex module
+ * name, rather than the package name.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MODULE_APEX_NAME = 0x00000001;
+
+ /** @hide */
+ @IntDef(prefix = { "PERMISSION_" }, value = {
+ PERMISSION_GRANTED,
+ PERMISSION_DENIED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionResult {}
+
+ /**
+ * Permission check result: this is returned by {@link #checkPermission}
+ * if the permission has been granted to the given package.
+ */
+ public static final int PERMISSION_GRANTED = 0;
+
+ /**
+ * Permission check result: this is returned by {@link #checkPermission}
+ * if the permission has not been granted to the given package.
+ */
+ public static final int PERMISSION_DENIED = -1;
+
+ /** @hide */
+ @IntDef(prefix = { "SIGNATURE_" }, value = {
+ SIGNATURE_MATCH,
+ SIGNATURE_NEITHER_SIGNED,
+ SIGNATURE_FIRST_NOT_SIGNED,
+ SIGNATURE_SECOND_NOT_SIGNED,
+ SIGNATURE_NO_MATCH,
+ SIGNATURE_UNKNOWN_PACKAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SignatureResult {}
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if all signatures on the two packages match.
+ */
+ public static final int SIGNATURE_MATCH = 0;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if neither of the two packages is signed.
+ */
+ public static final int SIGNATURE_NEITHER_SIGNED = 1;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if the first package is not signed but the second is.
+ */
+ public static final int SIGNATURE_FIRST_NOT_SIGNED = -1;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if the second package is not signed but the first is.
+ */
+ public static final int SIGNATURE_SECOND_NOT_SIGNED = -2;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if not all signatures on both packages match.
+ */
+ public static final int SIGNATURE_NO_MATCH = -3;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if either of the packages are not valid.
+ */
+ public static final int SIGNATURE_UNKNOWN_PACKAGE = -4;
+
+ /** @hide */
+ @IntDef(prefix = { "COMPONENT_ENABLED_STATE_" }, value = {
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ COMPONENT_ENABLED_STATE_DISABLED,
+ COMPONENT_ENABLED_STATE_DISABLED_USER,
+ COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnabledState {}
+
+ /**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)} and
+ * {@link #setComponentEnabledSetting(ComponentName, int, int)}: This
+ * component or application is in its default enabled state (as specified in
+ * its manifest).
+ * <p>
+ * Explicitly setting the component state to this value restores it's
+ * enabled state to whatever is set in the manifest.
+ */
+ public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0;
+
+ /**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)}
+ * and {@link #setComponentEnabledSetting(ComponentName, int, int)}: This
+ * component or application has been explictily enabled, regardless of
+ * what it has specified in its manifest.
+ */
+ public static final int COMPONENT_ENABLED_STATE_ENABLED = 1;
+
+ /**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)}
+ * and {@link #setComponentEnabledSetting(ComponentName, int, int)}: This
+ * component or application has been explicitly disabled, regardless of
+ * what it has specified in its manifest.
+ */
+ public static final int COMPONENT_ENABLED_STATE_DISABLED = 2;
+
+ /**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: The
+ * user has explicitly disabled the application, regardless of what it has
+ * specified in its manifest. Because this is due to the user's request,
+ * they may re-enable it if desired through the appropriate system UI. This
+ * option currently <strong>cannot</strong> be used with
+ * {@link #setComponentEnabledSetting(ComponentName, int, int)}.
+ */
+ public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3;
+
+ /**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: This
+ * application should be considered, until the point where the user actually
+ * wants to use it. This means that it will not normally show up to the user
+ * (such as in the launcher), but various parts of the user interface can
+ * use {@link #GET_DISABLED_UNTIL_USED_COMPONENTS} to still see it and allow
+ * the user to select it (as for example an IME, device admin, etc). Such code,
+ * once the user has selected the app, should at that point also make it enabled.
+ * This option currently <strong>can not</strong> be used with
+ * {@link #setComponentEnabledSetting(ComponentName, int, int)}.
+ */
+ public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ RollbackDataPolicy.RESTORE,
+ RollbackDataPolicy.WIPE,
+ RollbackDataPolicy.RETAIN
+ })
+ public @interface RollbackDataPolicy {
+ /**
+ * User data will be backed up during install and restored during rollback.
+ */
+ int RESTORE = 0;
+ /**
+ * User data won't be backed up during install but will be wiped out during rollback.
+ */
+ int WIPE = 1;
+ /**
+ * User data won't be backed up during install and won't be restored during rollback.
+ * TODO: Not implemented yet.
+ */
+ int RETAIN = 2;
+ }
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "INSTALL_" }, value = {
+ INSTALL_REPLACE_EXISTING,
+ INSTALL_ALLOW_TEST,
+ INSTALL_INTERNAL,
+ INSTALL_FROM_ADB,
+ INSTALL_ALL_USERS,
+ INSTALL_REQUEST_DOWNGRADE,
+ INSTALL_GRANT_RUNTIME_PERMISSIONS,
+ INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+ INSTALL_FORCE_VOLUME_UUID,
+ INSTALL_FORCE_PERMISSION_PROMPT,
+ INSTALL_INSTANT_APP,
+ INSTALL_DONT_KILL_APP,
+ INSTALL_FULL_APP,
+ INSTALL_ALLOCATE_AGGRESSIVE,
+ INSTALL_VIRTUAL_PRELOAD,
+ INSTALL_APEX,
+ INSTALL_ENABLE_ROLLBACK,
+ INSTALL_ALLOW_DOWNGRADE,
+ INSTALL_STAGED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallFlags {}
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that you want to
+ * replace an already installed package, if one exists.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int INSTALL_REPLACE_EXISTING = 0x00000002;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that you want to
+ * allow test packages (those that have set android:testOnly in their
+ * manifest) to be installed.
+ * @hide
+ */
+ public static final int INSTALL_ALLOW_TEST = 0x00000004;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * must be installed to internal storage.
+ *
+ * @hide
+ */
+ public static final int INSTALL_INTERNAL = 0x00000010;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this install
+ * was initiated via ADB.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FROM_ADB = 0x00000020;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this install
+ * should immediately be visible to all users.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALL_USERS = 0x00000040;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that an upgrade to a lower version
+ * of a package than currently installed has been requested.
+ *
+ * <p>Note that this flag doesn't guarantee that downgrade will be performed. That decision
+ * depends
+ * on whenever:
+ * <ul>
+ * <li>An app is debuggable.
+ * <li>Or a build is debuggable.
+ * <li>Or {@link #INSTALL_ALLOW_DOWNGRADE} is set.
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int INSTALL_REQUEST_DOWNGRADE = 0x00000080;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that all runtime
+ * permissions should be granted to the package. If {@link #INSTALL_ALL_USERS}
+ * is set the runtime permissions will be granted to all users, otherwise
+ * only to the owner.
+ *
+ * @hide
+ */
+ public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that all restricted
+ * permissions should be whitelisted. If {@link #INSTALL_ALL_USERS}
+ * is set the restricted permissions will be whitelisted for all users, otherwise
+ * only to the owner.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
+
+ /** {@hide} */
+ public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that we always want to force
+ * the prompt for permission approval. This overrides any special behaviour for internal
+ * components.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FORCE_PERMISSION_PROMPT = 0x00000400;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package is
+ * to be installed as a lightweight "ephemeral" app.
+ *
+ * @hide
+ */
+ public static final int INSTALL_INSTANT_APP = 0x00000800;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package contains
+ * a feature split to an existing application and the existing application should not
+ * be killed during the installation process.
+ *
+ * @hide
+ */
+ public static final int INSTALL_DONT_KILL_APP = 0x00001000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package is
+ * to be installed as a heavy weight app. This is fundamentally the opposite of
+ * {@link #INSTALL_INSTANT_APP}.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FULL_APP = 0x00004000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * is critical to system health or security, meaning the system should use
+ * {@link StorageManager#FLAG_ALLOCATE_AGGRESSIVE} internally.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALLOCATE_AGGRESSIVE = 0x00008000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * is a virtual preload.
+ *
+ * @hide
+ */
+ public static final int INSTALL_VIRTUAL_PRELOAD = 0x00010000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * is an APEX package
+ *
+ * @hide
+ */
+ public static final int INSTALL_APEX = 0x00020000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that rollback
+ * should be enabled for this install.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ENABLE_ROLLBACK = 0x00040000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that package verification should be
+ * disabled for this package.
+ *
+ * @hide
+ */
+ public static final int INSTALL_DISABLE_VERIFICATION = 0x00080000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that
+ * {@link #INSTALL_REQUEST_DOWNGRADE} should be allowed.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALLOW_DOWNGRADE = 0x00100000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * is being installed as part of a staged install.
+ *
+ * @hide
+ */
+ public static final int INSTALL_STAGED = 0x00200000;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that check whether given APEX can be
+ * updated should be disabled for this install.
+ * @hide
+ */
+ public static final int INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK = 0x00400000;
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ DONT_KILL_APP,
+ SYNCHRONOUS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnabledFlags {}
+
+ /**
+ * Flag parameter for
+ * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
+ * that you don't want to kill the app containing the component. Be careful when you set this
+ * since changing component states can make the containing application's behavior unpredictable.
+ */
+ public static final int DONT_KILL_APP = 0x00000001;
+
+ /**
+ * Flag parameter for
+ * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
+ * that the given user's package restrictions state will be serialised to disk after the
+ * component state has been updated. Note that this is synchronous disk access, so calls using
+ * this flag should be run on a background thread.
+ */
+ public static final int SYNCHRONOUS = 0x00000002;
+
+ /** @hide */
+ @IntDef(prefix = { "INSTALL_REASON_" }, value = {
+ INSTALL_REASON_UNKNOWN,
+ INSTALL_REASON_POLICY,
+ INSTALL_REASON_DEVICE_RESTORE,
+ INSTALL_REASON_DEVICE_SETUP,
+ INSTALL_REASON_USER,
+ INSTALL_REASON_ROLLBACK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallReason {}
+
+ /**
+ * Code indicating that the reason for installing this package is unknown.
+ */
+ public static final int INSTALL_REASON_UNKNOWN = 0;
+
+ /**
+ * Code indicating that this package was installed due to enterprise policy.
+ */
+ public static final int INSTALL_REASON_POLICY = 1;
+
+ /**
+ * Code indicating that this package was installed as part of restoring from another device.
+ */
+ public static final int INSTALL_REASON_DEVICE_RESTORE = 2;
+
+ /**
+ * Code indicating that this package was installed as part of device setup.
+ */
+ public static final int INSTALL_REASON_DEVICE_SETUP = 3;
+
+ /**
+ * Code indicating that the package installation was initiated by the user.
+ */
+ public static final int INSTALL_REASON_USER = 4;
+
+ /**
+ * Code indicating that the package installation was a rollback initiated by RollbackManager.
+ *
+ * @hide
+ */
+ public static final int INSTALL_REASON_ROLLBACK = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "INSTALL_SCENARIO_" }, value = {
+ INSTALL_SCENARIO_DEFAULT,
+ INSTALL_SCENARIO_FAST,
+ INSTALL_SCENARIO_BULK,
+ INSTALL_SCENARIO_BULK_SECONDARY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallScenario {}
+
+ /**
+ * A value to indicate the lack of CUJ information, disabling all installation scenario logic.
+ */
+ public static final int INSTALL_SCENARIO_DEFAULT = 0;
+
+ /**
+ * Installation scenario providing the fastest “install button to launch" experience possible.
+ */
+ public static final int INSTALL_SCENARIO_FAST = 1;
+
+ /**
+ * Installation scenario indicating a bulk operation with the desired result of a fully
+ * optimized application. If the system is busy or resources are scarce the system will
+ * perform less work to avoid impacting system health.
+ *
+ * Examples of bulk installation scenarios might include device restore, background updates of
+ * multiple applications, or user-triggered updates for all applications.
+ *
+ * The decision to use BULK or BULK_SECONDARY should be based on the desired user experience.
+ * BULK_SECONDARY operations may take less time to complete but, when they do, will produce
+ * less optimized applications. The device state (e.g. memory usage or battery status) should
+ * not be considered when making this decision as those factors are taken into account by the
+ * Package Manager when acting on the installation scenario.
+ */
+ public static final int INSTALL_SCENARIO_BULK = 2;
+
+ /**
+ * Installation scenario indicating a bulk operation that prioritizes minimal system health
+ * impact over application optimization. The application may undergo additional optimization
+ * if the system is idle and system resources are abundant. The more elements of a bulk
+ * operation that are marked BULK_SECONDARY, the faster the entire bulk operation will be.
+ *
+ * See the comments for INSTALL_SCENARIO_BULK for more information.
+ */
+ public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3;
+
+ /** @hide */
+ @IntDef(prefix = { "UNINSTALL_REASON_" }, value = {
+ UNINSTALL_REASON_UNKNOWN,
+ UNINSTALL_REASON_USER_TYPE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UninstallReason {}
+
+ /**
+ * Code indicating that the reason for uninstalling this package is unknown.
+ * @hide
+ */
+ public static final int UNINSTALL_REASON_UNKNOWN = 0;
+
+ /**
+ * Code indicating that this package was uninstalled due to the type of user.
+ * See UserSystemPackageInstaller
+ * @hide
+ */
+ public static final int UNINSTALL_REASON_USER_TYPE = 1;
+
+ /**
+ * @hide
+ */
+ public static final int INSTALL_UNKNOWN = 0;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * on success.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_SUCCEEDED = 1;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package is already installed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package archive file is invalid.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INVALID_APK = -2;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the URI passed in is invalid.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INVALID_URI = -3;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package manager service found that the device didn't have enough storage space to
+ * install the app.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if a package is already installed with the same name.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the requested shared user does not exist.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if a previously installed package of the same name has a different signature than the new
+ * package (and the old package's data was not removed).
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package is requested a shared user which is already installed on the device and
+ * does not have matching signature.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package uses a shared library that is not available.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * when the package being replaced is a system app and the caller didn't provide the
+ * {@link #DELETE_SYSTEM_APP} flag.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed while optimizing and validating its dex files, either because there
+ * was not enough storage or the validation failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_DEXOPT = -11;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because the current SDK version is older than that required by the
+ * package.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_OLDER_SDK = -12;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because it contains a content provider with the same authority as a
+ * provider already installed in the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because the current SDK version is newer than that required by the
+ * package.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_NEWER_SDK = -14;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because it has specified that it is a test-only package and the
+ * caller has not supplied the {@link #INSTALL_ALLOW_TEST} flag.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_TEST_ONLY = -15;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package being installed contains native code, but none that is compatible with the
+ * device's CPU_ABI.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package uses a feature that is not available.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
+
+ // ------ Errors related to sdcard
+ /**
+ * Installation return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if a secure container mount point couldn't be
+ * accessed on external media.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed in the specified install location.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed in the specified install location because the media
+ * is not available.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed because the verification timed out.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed because the verification did not succeed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package changed from what the calling program expected.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package is assigned a different UID than it previously held.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_UID_CHANGED = -24;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package has an older version code than the currently installed package.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the old package has target SDK high enough to support runtime permission and the new
+ * package has target SDK low enough to not support runtime permissions.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package attempts to downgrade the target sandbox version of the app.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package requires at least one split and it was not provided.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_MISSING_SPLIT = -28;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a
+ * file, or does not end with the expected '.apk' extension.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was unable to retrieve the
+ * AndroidManifest.xml file.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered an unexpected
+ * exception.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any certificates in
+ * the .apk.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser found inconsistent certificates on
+ * the files in the .apk.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a
+ * CertificateEncodingException in one of the files in the .apk.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad or missing
+ * package name in the manifest.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
+
+ /**
+ * Installation parse return code: tthis is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad shared user id
+ * name in the manifest.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered some structural
+ * problem in the manifest.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any actionable tags
+ * (instrumentation or application) in the manifest.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
+
+ /**
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because of system issues.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
+
+ /**
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because the user is restricted from installing apps.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
+
+ /**
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because it is attempting to define a permission that is already defined by some existing
+ * package.
+ * <p>
+ * The package name of the app which has already defined the permission is passed to a
+ * {@link PackageInstallObserver}, if any, as the {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string
+ * extra; and the name of the permission being redefined is passed in the
+ * {@link #EXTRA_FAILURE_EXISTING_PERMISSION} string extra.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112;
+
+ /**
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because its packaged native code did not match any of the ABIs supported by the system.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -113;
+
+ /**
+ * Internal return code for NativeLibraryHelper methods to indicate that the package
+ * being processed did not contain any native code. This is placed here only so that
+ * it can belong to the same value space as the other install failure codes.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int NO_NATIVE_LIBRARIES = -114;
+
+ /** {@hide} */
+ public static final int INSTALL_FAILED_ABORTED = -115;
+
+ /**
+ * Installation failed return code: install type is incompatible with some other
+ * installation flags supplied for the operation; or other circumstances such as trying
+ * to upgrade a system app via an Incremental or instant app install.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_SESSION_INVALID = -116;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the dex metadata file is invalid or
+ * if there was no matching apk file for a dex metadata file.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_BAD_DEX_METADATA = -117;
+
+ /**
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if there is any signature problem.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_BAD_SIGNATURE = -118;
+
+ /**
+ * Installation failed return code: a new staged session was attempted to be committed while
+ * there is already one in-progress or new session has package that is already staged.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS = -119;
+
+ /**
+ * Installation failed return code: one of the child sessions does not match the parent session
+ * in respect to staged or rollback enabled parameters.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY = -120;
+
+ /**
+ * Installation failed return code: the required installed version code
+ * does not match the currently installed package version code.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_WRONG_INSTALLED_VERSION = -121;
+
+ /**
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because it contains a request to use a process that was not
+ * explicitly defined as part of its <processes> tag.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_PROCESS_NOT_DEFINED = -122;
+
+ /**
+ * Installation parse return code: system is in a minimal boot state, and the parser only
+ * allows the package with {@code coreApp} manifest attribute to be a valid application.
+ *
+ * @hide
+ */
+ public static final int INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED = -123;
+
+ /**
+ * Installation failed return code: the {@code resources.arsc} of one of the APKs being
+ * installed is compressed or not aligned on a 4-byte boundary. Resource tables that cannot be
+ * memory mapped exert excess memory pressure on the system and drastically slow down
+ * construction of {@link Resources} objects.
+ *
+ * @hide
+ */
+ public static final int INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED = -124;
+
+ /**
+ * Installation failed return code: the package was skipped and should be ignored.
+ *
+ * The reason for the skip is undefined.
+ * @hide
+ */
+ public static final int INSTALL_PARSE_FAILED_SKIPPED = -125;
+
+ /**
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because it is attempting to define a permission group that is already defined by some
+ * existing package.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP = -126;
+
+ /**
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because it is attempting to define a permission in a group that does not exists or that is
+ * defined by an packages with an incompatible certificate.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_BAD_PERMISSION_GROUP = -127;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "DELETE_" }, value = {
+ DELETE_KEEP_DATA,
+ DELETE_ALL_USERS,
+ DELETE_SYSTEM_APP,
+ DELETE_DONT_KILL_APP,
+ DELETE_CHATTY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeleteFlags {}
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
+ * package's data directory.
+ *
+ * @hide
+ */
+ public static final int DELETE_KEEP_DATA = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that you want the
+ * package deleted for all users.
+ *
+ * @hide
+ */
+ public static final int DELETE_ALL_USERS = 0x00000002;
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that, if you are calling
+ * uninstall on a system that has been updated, then don't do the normal process
+ * of uninstalling the update and rolling back to the older system version (which
+ * needs to happen for all users); instead, just mark the app as uninstalled for
+ * the current user.
+ *
+ * @hide
+ */
+ public static final int DELETE_SYSTEM_APP = 0x00000004;
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that, if you are calling
+ * uninstall on a package that is replaced to provide new feature splits, the
+ * existing application should not be killed during the removal process.
+ *
+ * @hide
+ */
+ public static final int DELETE_DONT_KILL_APP = 0x00000008;
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that package deletion
+ * should be chatty.
+ *
+ * @hide
+ */
+ public static final int DELETE_CHATTY = 0x80000000;
+
+ /**
+ * Return code for when package deletion succeeds. This is passed to the
+ * {@link IPackageDeleteObserver} if the system succeeded in deleting the
+ * package.
+ *
+ * @hide
+ */
+ public static final int DELETE_SUCCEEDED = 1;
+
+ /**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * for an unspecified reason.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
+
+ /**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * because it is the active DevicePolicy manager.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
+
+ /**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * since the user is restricted.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_USER_RESTRICTED = -3;
+
+ /**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * because a profile or device owner has marked the package as
+ * uninstallable.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
+
+ /** {@hide} */
+ public static final int DELETE_FAILED_ABORTED = -5;
+
+ /**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * because the packge is a shared library used by other installed packages.
+ * {@hide} */
+ public static final int DELETE_FAILED_USED_SHARED_LIBRARY = -6;
+
+ /**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * because there is an app pinned.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_APP_PINNED = -7;
+
+ /**
+ * Return code that is passed to the {@link IPackageMoveObserver} when the
+ * package has been successfully moved by the system.
+ *
+ * @hide
+ */
+ public static final int MOVE_SUCCEEDED = -100;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} when the
+ * package hasn't been successfully moved by the system because of
+ * insufficient memory on specified media.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_INSUFFICIENT_STORAGE = -1;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package doesn't exist.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_DOESNT_EXIST = -2;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package cannot be moved since its a system package.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_SYSTEM_PACKAGE = -3;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package cannot be moved to the specified location.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_INVALID_LOCATION = -5;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package cannot be moved to the specified location.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_INTERNAL_ERROR = -6;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package already has an operation pending in the queue.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_OPERATION_PENDING = -7;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if the
+ * specified package cannot be moved since it contains a device admin.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_DEVICE_ADMIN = -8;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} if system does not allow
+ * non-system apps to be moved to internal storage.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL = -9;
+
+ /** @hide */
+ public static final int MOVE_FAILED_LOCKED_USER = -10;
+
+ /**
+ * Flag parameter for {@link #movePackage} to indicate that
+ * the package should be moved to internal storage if its
+ * been installed on external media.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int MOVE_INTERNAL = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #movePackage} to indicate that
+ * the package should be moved to external media.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
+
+ /** {@hide} */
+ public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID";
+
+ /**
+ * Usable by the required verifier as the {@code verificationCode} argument
+ * for {@link PackageManager#verifyPendingInstall} to indicate that it will
+ * allow the installation to proceed without any of the optional verifiers
+ * needing to vote.
+ *
+ * @hide
+ */
+ public static final int VERIFICATION_ALLOW_WITHOUT_SUFFICIENT = 2;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyPendingInstall} to indicate that the calling
+ * package verifier allows the installation to proceed.
+ */
+ public static final int VERIFICATION_ALLOW = 1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyPendingInstall} to indicate the calling
+ * package verifier does not vote to allow the installation to proceed.
+ */
+ public static final int VERIFICATION_REJECT = -1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyIntentFilter} to indicate that the calling
+ * IntentFilter Verifier confirms that the IntentFilter is verified.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyIntentFilter} to indicate that the calling
+ * IntentFilter Verifier confirms that the IntentFilter is NOT verified.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
+
+ /**
+ * Internal status code to indicate that an IntentFilter verification result is not specified.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
+
+ /**
+ * Used as the {@code status} argument for
+ * {@link #updateIntentVerificationStatusAsUser} to indicate that the User
+ * will always be prompted the Intent Disambiguation Dialog if there are two
+ * or more Intent resolved for the IntentFilter's domain(s).
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
+
+ /**
+ * Used as the {@code status} argument for
+ * {@link #updateIntentVerificationStatusAsUser} to indicate that the User
+ * will never be prompted the Intent Disambiguation Dialog if there are two
+ * or more resolution of the Intent. The default App for the domain(s)
+ * specified in the IntentFilter will also ALWAYS be used.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
+
+ /**
+ * Used as the {@code status} argument for
+ * {@link #updateIntentVerificationStatusAsUser} to indicate that the User
+ * may be prompted the Intent Disambiguation Dialog if there are two or more
+ * Intent resolved. The default App for the domain(s) specified in the
+ * IntentFilter will also NEVER be presented to the User.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
+
+ /**
+ * Used as the {@code status} argument for
+ * {@link #updateIntentVerificationStatusAsUser} to indicate that this app
+ * should always be considered as an ambiguous candidate for handling the
+ * matching Intent even if there are other candidate apps in the "always"
+ * state. Put another way: if there are any 'always ask' apps in a set of
+ * more than one candidate app, then a disambiguation is *always* presented
+ * even if there is another candidate app with the 'always' state.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
+
+ /**
+ * Can be used as the {@code millisecondsToDelay} argument for
+ * {@link PackageManager#extendVerificationTimeout}. This is the
+ * maximum time {@code PackageManager} waits for the verification
+ * agent to return (in milliseconds).
+ */
+ public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000;
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
+ * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
+ * lag in sound input or output.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes at least one form of audio
+ * output, as defined in the Android Compatibility Definition Document (CDD)
+ * <a href="https://source.android.com/compatibility/android-cdd#7_8_audio">section 7.8 Audio</a>.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has professional audio level of functionality and performance.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of communicating with
+ * other devices via Bluetooth.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of communicating with
+ * other devices via Bluetooth Low Energy radio.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a camera facing away
+ * from the screen.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA = "android.hardware.camera";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's camera supports auto-focus.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has at least one camera pointing in
+ * some direction, or can support an external camera being connected to it.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can support having an external camera connected to it.
+ * The external camera may not always be connected or available to applications to use.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_EXTERNAL = "android.hardware.camera.external";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's camera supports flash.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a front facing camera.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+ * of the cameras on the device supports the
+ * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL full hardware}
+ * capability level.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+ * of the cameras on the device supports the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR manual sensor}
+ * capability level.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR =
+ "android.hardware.camera.capability.manual_sensor";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+ * of the cameras on the device supports the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING manual post-processing}
+ * capability level.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING =
+ "android.hardware.camera.capability.manual_post_processing";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+ * of the cameras on the device supports the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}
+ * capability level.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_CAPABILITY_RAW =
+ "android.hardware.camera.capability.raw";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+ * of the cameras on the device supports the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING
+ * MOTION_TRACKING} capability level.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_AR =
+ "android.hardware.camera.ar";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's main front and back cameras can stream
+ * concurrently as described in {@link
+ * android.hardware.camera2.CameraManager#getConcurrentCameraIds()}.
+ * </p>
+ * <p>While {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds()} and
+ * associated APIs are only available on API level 30 or newer, this feature flag may be
+ * advertised by devices on API levels below 30. If present on such a device, the same
+ * guarantees hold: The main front and main back camera can be used at the same time, with
+ * guaranteed stream configurations as defined in the table for concurrent streaming at
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_CONCURRENT = "android.hardware.camera.concurrent";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of communicating with
+ * consumer IR devices.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports a Context Hub, used to expose the
+ * functionalities in {@link android.hardware.location.ContextHubManager}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
+
+ /** {@hide} */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CTS = "android.software.cts";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
+ * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
+ * at the given feature version.
+ *
+ * <p>Known feature versions include:
+ * <ul>
+ * <li><code>202009</code>: corresponds to the features included in the Identity Credential
+ * API shipped in Android 11.
+ * <li><code>202101</code>: corresponds to the features included in the Identity Credential
+ * API shipped in Android 12.
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE =
+ "android.hardware.identity_credential";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
+ * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
+ * with direct access at the given feature version.
+ * See {@link #FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known feature versions.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS =
+ "android.hardware.identity_credential_direct_access";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports one or more methods of
+ * reporting current location.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOCATION = "android.hardware.location";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a Global Positioning System
+ * receiver and can report precise location.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can report location with coarse
+ * accuracy using a network-based geolocation system.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOCATION_NETWORK = "android.hardware.location.network";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's
+ * {@link ActivityManager#isLowRamDevice() ActivityManager.isLowRamDevice()} method returns
+ * true.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_RAM_LOW = "android.hardware.ram.low";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's
+ * {@link ActivityManager#isLowRamDevice() ActivityManager.isLowRamDevice()} method returns
+ * false.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_RAM_NORMAL = "android.hardware.ram.normal";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can record audio via a
+ * microphone.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_MICROPHONE = "android.hardware.microphone";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can communicate using Near-Field
+ * Communications (NFC).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC = "android.hardware.nfc";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports host-
+ * based NFC card emulation.
+ *
+ * TODO remove when depending apps have moved to new constant.
+ * @hide
+ * @deprecated
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_HCE = "android.hardware.nfc.hce";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports host-
+ * based NFC card emulation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports host-
+ * based NFC-F card emulation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports uicc-
+ * based NFC card emulation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC =
+ "android.hardware.nfc.uicc";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports eSE-
+ * based NFC card emulation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The Beam API is enabled on the device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports any
+ * one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION},
+ * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Open Mobile API capable UICC-based secure
+ * elements.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SE_OMAPI_UICC = "android.hardware.se.omapi.uicc";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Open Mobile API capable eSE-based secure
+ * elements.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Open Mobile API capable SD-based secure
+ * elements.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SE_OMAPI_SD = "android.hardware.se.omapi.sd";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device is
+ * compatible with Android’s security model.
+ *
+ * <p>See sections 2 and 9 in the
+ * <a href="https://source.android.com/compatibility/android-cdd">Android CDD</a> for more
+ * details.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SECURITY_MODEL_COMPATIBLE =
+ "android.hardware.security.model.compatible";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports the OpenGL ES
+ * <a href="http://www.khronos.org/registry/gles/extensions/ANDROID/ANDROID_extension_pack_es31a.txt">
+ * Android Extension Pack</a>.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
+ * implementation on this device is hardware accelerated, and the Vulkan native API will
+ * enumerate at least one {@code VkPhysicalDevice}, and the feature version will indicate what
+ * level of optional hardware features limits it supports.
+ * <p>
+ * Level 0 includes the base Vulkan requirements as well as:
+ * <ul><li>{@code VkPhysicalDeviceFeatures::textureCompressionETC2}</li></ul>
+ * <p>
+ * Level 1 additionally includes:
+ * <ul>
+ * <li>{@code VkPhysicalDeviceFeatures::fullDrawIndexUint32}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::imageCubeArray}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::independentBlend}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::geometryShader}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::tessellationShader}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::sampleRateShading}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::textureCompressionASTC_LDR}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::fragmentStoresAndAtomics}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::shaderImageGatherExtended}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::shaderUniformBufferArrayDynamicIndexing}</li>
+ * <li>{@code VkPhysicalDeviceFeatures::shaderSampledImageArrayDynamicIndexing}</li>
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
+ * implementation on this device is hardware accelerated, and the Vulkan native API will
+ * enumerate at least one {@code VkPhysicalDevice}, and the feature version will indicate what
+ * level of optional compute features that device supports beyond the Vulkan 1.0 requirements.
+ * <p>
+ * Compute level 0 indicates:
+ * <ul>
+ * <li>The {@code VK_KHR_variable_pointers} extension and
+ * {@code VkPhysicalDeviceVariablePointerFeaturesKHR::variablePointers} feature are
+ supported.</li>
+ * <li>{@code VkPhysicalDeviceLimits::maxPerStageDescriptorStorageBuffers} is at least 16.</li>
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
+ * implementation on this device is hardware accelerated, and the feature version will indicate
+ * the highest {@code VkPhysicalDeviceProperties::apiVersion} supported by the physical devices
+ * that support the hardware level indicated by {@link #FEATURE_VULKAN_HARDWARE_LEVEL}. The
+ * feature version uses the same encoding as Vulkan version numbers:
+ * <ul>
+ * <li>Major version number in bits 31-22</li>
+ * <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>
+ * A subset of devices that support Vulkan 1.1 do so via software emulation. For more
+ * information, see
+ * <a href="{@docRoot}ndk/guides/graphics/design-notes">Vulkan Design Guidelines</a>.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version
+ * specifies a date such that the device is known to pass the Vulkan dEQP test suite associated
+ * with that date. The date is encoded as follows:
+ * <ul>
+ * <li>Year in bits 31-16</li>
+ * <li>Month in bits 15-8</li>
+ * <li>Day in bits 7-0</li>
+ * </ul>
+ * <p>
+ * Example: 2019-03-01 is encoded as 0x07E30301, and would indicate that the device passes the
+ * Vulkan dEQP test suite version that was current on 2019-03-01.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version
+ * specifies a date such that the device is known to pass the OpenGLES dEQP test suite
+ * associated with that date. The date is encoded as follows:
+ * <ul>
+ * <li>Year in bits 31-16</li>
+ * <li>Month in bits 15-8</li>
+ * <li>Day in bits 7-0</li>
+ * </ul>
+ * <p>
+ * Example: 2021-03-01 is encoded as 0x07E50301, and would indicate that the device passes the
+ * OpenGL ES dEQP test suite version that was current on 2021-03-01.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_OPENGLES_DEQP_LEVEL = "android.software.opengles.deqp.level";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes broadcast radio tuner.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the
+ * device supports PIN, pattern and password as defined in Android CDD
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes an accelerometer.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a barometer (air
+ * pressure sensor.)
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a magnetometer (compass).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a gyroscope.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a light sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a proximity sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a hardware step counter.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a hardware step detector.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a heart rate monitor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The heart rate sensor on this device is an Electrocardiogram.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_HEART_RATE_ECG =
+ "android.hardware.sensor.heartrate.ecg";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a relative humidity sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY =
+ "android.hardware.sensor.relative_humidity";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes an ambient temperature sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE =
+ "android.hardware.sensor.ambient_temperature";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a hinge angle sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_HINGE_ANGLE = "android.hardware.sensor.hinge_angle";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports high fidelity sensor processing
+ * capabilities.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HIFI_SENSORS =
+ "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.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a CDMA telephony stack.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a GSM telephony stack.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports telephony carrier restriction mechanism.
+ *
+ * <p>Devices declaring this feature must have an implementation of the
+ * {@link android.telephony.TelephonyManager#getAllowedCarriers} and
+ * {@link android.telephony.TelephonyManager#setAllowedCarriers}.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_CARRIERLOCK =
+ "android.hardware.telephony.carrierlock";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports embedded subscriptions on eUICCs.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports cell-broadcast reception using the MBMS APIs.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports attaching to IMS implementations using the ImsService API in telephony.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports a single IMS registration as defined by carrier networks in the IMS service
+ * implementation using the {@link ImsService} API, {@link GbaService} API, and IRadio 1.6 HAL.
+ * <p>
+ * When set, the device must fully support the following APIs for an application to implement
+ * IMS single registration:
+ * <ul>
+ * <li> Updating RCS provisioning status using the {@link ProvisioningManager} API to supply an
+ * RCC.14 defined XML and notify IMS applications of Auto Configuration Server (ACS) or
+ * proprietary server provisioning updates.</li>
+ * <li>Opening a delegate in the device IMS service to forward SIP traffic to the carrier's
+ * network using the {@link SipDelegateManager} API</li>
+ * <li>Listening to EPS dedicated bearer establishment via the
+ * {@link ConnectivityManager#registerQosCallback}
+ * API to indicate to the application when to start/stop media traffic.</li>
+ * <li>Implementing Generic Bootstrapping Architecture (GBA) and providing the associated
+ * authentication keys to applications
+ * requesting this information via the {@link TelephonyManager#bootstrapAuthenticationRequest}
+ * API</li>
+ * <li>Implementing RCS User Capability Exchange using the {@link RcsUceAdapter} API</li>
+ * </ul>
+ * <p>
+ * This feature should only be defined if {@link #FEATURE_TELEPHONY_IMS} is also defined.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION =
+ "android.hardware.telephony.ims.singlereg";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of communicating with
+ * other devices via ultra wideband.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_UWB = "android.hardware.uwb";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports connecting to USB devices
+ * as the USB host.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports connecting to USB accessories.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The SIP API is enabled on the device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SIP = "android.software.sip";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports SIP-based VOIP.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The Connection Service API is enabled on the device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's display has a touch screen.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's touch screen supports
+ * multitouch sufficient for basic two-finger gesture detection.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's touch screen is capable of
+ * tracking two or more fingers fully independently.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's touch screen is capable of
+ * tracking a full hand of fingers fully independently -- that is, 5 or
+ * more simultaneous independent pointers.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device does not have a touch screen, but
+ * does support touch emulation for basic events. For instance, the
+ * device might use a mouse or remote control to drive a cursor, and
+ * emulate basic touch pointer events like down, up, drag, etc. All
+ * devices that support android.hardware.touchscreen or a sub-feature are
+ * presumed to also support faketouch.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FAKETOUCH = "android.hardware.faketouch";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device does not have a touch screen, but
+ * does support touch emulation for basic events that supports distinct
+ * tracking of two or more fingers. This is an extension of
+ * {@link #FEATURE_FAKETOUCH} for input devices with this capability. Note
+ * that unlike a distinct multitouch screen as defined by
+ * {@link #FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT}, these kinds of input
+ * devices will not actually provide full two-finger gestures since the
+ * input is being transformed to cursor movement on the screen. That is,
+ * single finger gestures will move a cursor; two-finger swipes will
+ * result in single-finger touch events; other two-finger gestures will
+ * result in the corresponding two-finger touch event.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device does not have a touch screen, but
+ * does support touch emulation for basic events that supports tracking
+ * a hand of fingers (5 or more fingers) fully independently.
+ * This is an extension of
+ * {@link #FEATURE_FAKETOUCH} for input devices with this capability. Note
+ * that unlike a multitouch screen as defined by
+ * {@link #FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND}, not all two finger
+ * gestures can be detected due to the limitations described for
+ * {@link #FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has biometric hardware to detect a fingerprint.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has biometric hardware to perform face authentication.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FACE = "android.hardware.biometrics.face";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has biometric hardware to perform iris authentication.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports portrait orientation
+ * screens. For backwards compatibility, you can assume that if neither
+ * this nor {@link #FEATURE_SCREEN_LANDSCAPE} is set then the device supports
+ * both portrait and landscape.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports landscape orientation
+ * screens. For backwards compatibility, you can assume that if neither
+ * this nor {@link #FEATURE_SCREEN_PORTRAIT} is set then the device supports
+ * both portrait and landscape.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports live wallpapers.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports app widgets.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports the
+ * {@link android.R.attr#cantSaveState} API.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
+
+ /**
+ * @hide
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports
+ * {@link android.service.voice.VoiceInteractionService} and
+ * {@link android.app.VoiceInteractor}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports a home screen that is replaceable
+ * by third party applications.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports adding new input methods implemented
+ * with the {@link android.inputmethodservice.InputMethodService} API.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports device policy enforcement via device admins.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports leanback UI. This is
+ * typically used in a living room television experience, but is a software
+ * feature unlike {@link #FEATURE_TELEVISION}. Devices running with this
+ * feature will use resources associated with the "television" UI mode.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LEANBACK = "android.software.leanback";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports only leanback UI. Only
+ * applications designed for this experience should be run, though this is
+ * not enforced by the system.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports live TV and can display
+ * contents from TV inputs implemented with the
+ * {@link android.media.tv.TvInputService} API.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LIVE_TV = "android.software.live_tv";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports WiFi (802.11) networking.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI = "android.hardware.wifi";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Direct networking.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Aware.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Passpoint and all
+ * Passpoint related APIs in {@link WifiManager} are supported. Refer to
+ * {@link WifiManager#addOrUpdatePasspointConfiguration} for more info.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi RTT (IEEE 802.11mc).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports LoWPAN networking.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOWPAN = "android.hardware.lowpan";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device dedicated to showing UI
+ * on a vehicle headunit. A headunit here is defined to be inside a
+ * vehicle that may or may not be moving. A headunit uses either a
+ * primary display in the center console and/or additional displays in
+ * the instrument cluster or elsewhere in the vehicle. Headunit display(s)
+ * have limited size and resolution. The user will likely be focused on
+ * driving so limiting driver distraction is a primary concern. User input
+ * can be a variety of hard buttons, touch, rotary controllers and even mouse-
+ * like interfaces.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device dedicated to showing UI
+ * on a television. Television here is defined to be a typical living
+ * room television experience: displayed on a big screen, where the user
+ * is sitting far away from it, and the dominant form of input will be
+ * something like a DPAD, not through touch or mouse.
+ * @deprecated use {@link #FEATURE_LEANBACK} instead.
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEVISION = "android.hardware.type.television";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device dedicated to showing UI
+ * on a watch. A watch here is defined to be a device worn on the body, perhaps on
+ * the wrist. The user is very close when interacting with the device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device for IoT and may not have an UI. An embedded
+ * device is defined as a full stack Android device with or without a display and no
+ * user-installable apps.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device dedicated to be primarily used
+ * with keyboard, mouse or touchpad. This includes traditional desktop
+ * computers, laptops and variants such as convertibles or detachables.
+ * Due to the larger screen, the device will most likely use the
+ * {@link #FEATURE_FREEFORM_WINDOW_MANAGEMENT} feature as well.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_PC = "android.hardware.type.pc";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports printing.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_PRINTING = "android.software.print";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports {@link android.companion.CompanionDeviceManager#associate associating}
+ * with devices via {@link android.companion.CompanionDeviceManager}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_COMPANION_DEVICE_SETUP
+ = "android.software.companion_device_setup";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device can perform backup and restore operations on installed applications.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BACKUP = "android.software.backup";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports freeform window management.
+ * Windows have title bars and can be moved and resized.
+ */
+ // If this feature is present, you also need to set
+ // com.android.internal.R.config_freeformWindowManagement to true in your configuration overlay.
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT
+ = "android.software.freeform_window_management";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports picture-in-picture multi-window mode.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports running activities on secondary displays.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS
+ = "android.software.activities_on_secondary_displays";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports creating secondary users and managed profiles via
+ * {@link DevicePolicyManager}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_MANAGED_USERS = "android.software.managed_users";
+
+ /**
+ * @hide
+ * TODO: Remove after dependencies updated b/17392243
+ */
+ public static final String FEATURE_MANAGED_PROFILES = "android.software.managed_users";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports verified boot.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports secure removal of users. When a user is deleted the data associated
+ * with that user is securely deleted and no longer available.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SECURELY_REMOVES_USERS
+ = "android.software.securely_removes_users";
+
+ /** {@hide} */
+ @TestApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FILE_BASED_ENCRYPTION
+ = "android.software.file_based_encryption";
+
+ /** {@hide} */
+ @TestApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_ADOPTABLE_STORAGE
+ = "android.software.adoptable_storage";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has a full implementation of the android.webkit.* APIs. Devices
+ * lacking this feature will not have a functioning WebView implementation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WEBVIEW = "android.software.webview";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This device supports ethernet.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This device supports HDMI-CEC.
+ * @hide
+ */
+ @TestApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has all of the inputs necessary to be considered a compatible game controller, or
+ * includes a compatible game controller in the box.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_GAMEPAD = "android.hardware.gamepad";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has a full implementation of the android.media.midi.* APIs.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_MIDI = "android.software.midi";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device implements an optimized mode for virtual reality (VR) applications that handles
+ * stereoscopic rendering of notifications, and disables most monocular system UI components
+ * while a VR application has user focus.
+ * Devices declaring this feature must include an application implementing a
+ * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via
+ * {@link android.app.Activity#setVrModeEnabled}.
+ * @deprecated use {@link #FEATURE_VR_MODE_HIGH_PERFORMANCE} instead.
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VR_MODE = "android.software.vr.mode";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device implements an optimized mode for virtual reality (VR) applications that handles
+ * stereoscopic rendering of notifications, disables most monocular system UI components
+ * while a VR application has user focus and meets extra CDD requirements to provide a
+ * high-quality VR experience.
+ * Devices declaring this feature must include an application implementing a
+ * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via
+ * {@link android.app.Activity#setVrModeEnabled}.
+ * and must meet CDD requirements to provide a high-quality VR experience.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE
+ = "android.hardware.vr.high_performance";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports autofill of user credentials, addresses, credit cards, etc
+ * via integration with {@link android.service.autofill.AutofillService autofill
+ * providers}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_AUTOFILL = "android.software.autofill";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device implements headtracking suitable for a VR device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device implements
+ * the Android Keystore backed by an isolated execution environment. The version indicates
+ * which features are implemented in the isolated execution environment:
+ * <ul>
+ * <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
+ * for app-generated attestation keys (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
+ * <li>41: Hardware enforcement of device-unlocked keys (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setUnlockedDeviceRequired(boolean)}).
+ * <li>40: Support for wrapped key import (see {@link
+ * android.security.keystore.WrappedKeyEntry}), optional support for ID attestation (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setDevicePropertiesAttestationIncluded(boolean)}),
+ * attestation (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])}),
+ * AES, HMAC, ECDSA and RSA support where the secret or private key never leaves secure
+ * hardware, and support for requiring user authentication before a key can be used.
+ * </ul>
+ * This feature version is guaranteed to be set for all devices launching with Android 12 and
+ * may be set on devices launching with an earlier version. If the feature version is set, it
+ * will at least have the value 40. If it's not set the device may have a version of
+ * hardware-backed keystore but it may not support all features listed above.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HARDWARE_KEYSTORE = "android.hardware.hardware_keystore";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures}, {@link #hasSystemFeature(String)}, and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device implements
+ * the Android Keystore backed by a dedicated secure processor referred to as
+ * <a href="https://source.android.com/security/best-practices/hardware#strongbox-keymaster">
+ * StrongBox</a>. If this feature has a version, the version number indicates which features are
+ * implemented in StrongBox:
+ * <ul>
+ * <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
+ * for app-generated attestation keys (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
+ * <li>41: Hardware enforcement of device-unlocked keys (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setUnlockedDeviceRequired(boolean)}).
+ * <li>40: Support for wrapped key import (see {@link
+ * android.security.keystore.WrappedKeyEntry}), optional support for ID attestation (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setDevicePropertiesAttestationIncluded(boolean)}),
+ * attestation (see {@link
+ * android.security.keystore.KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])}),
+ * AES, HMAC, ECDSA and RSA support where the secret or private key never leaves secure
+ * hardware, and support for requiring user authentication before a key can be used.
+ * </ul>
+ * If a device has StrongBox, this feature version number is guaranteed to be set for all
+ * devices launching with Android 12 and may be set on devices launching with an earlier
+ * version. If the feature version is set, it will at least have the value 40. If it's not
+ * set the device may have StrongBox but it may not support all features listed above.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_STRONGBOX_KEYSTORE =
+ "android.hardware.strongbox_keystore";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device does not have slices implementation.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SLICES_DISABLED = "android.software.slices_disabled";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports device-unique Keystore attestations. Only available on devices that
+ * also support {@link #FEATURE_STRONGBOX_KEYSTORE}, and can only be used by device owner
+ * apps (see {@link android.app.admin.DevicePolicyManager#generateKeyPair}).
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_DEVICE_UNIQUE_ATTESTATION =
+ "android.hardware.device_unique_attestation";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has a Keymaster implementation that supports Device ID attestation.
+ *
+ * @see DevicePolicyManager#isDeviceIdAttestationSupported
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_DEVICE_ID_ATTESTATION =
+ "android.software.device_id_attestation";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the requisite kernel support for multinetworking-capable IPsec tunnels.
+ *
+ * <p>This feature implies that the device supports XFRM Interfaces (CONFIG_XFRM_INTERFACE), or
+ * VTIs with kernel patches allowing updates of output/set mark via UPDSA.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports a system interface for the user to select
+ * and bind device control services provided by applications.
+ *
+ * @see android.service.controls.ControlsProviderService
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONTROLS = "android.software.controls";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the requisite hardware support to support reboot escrow of synthetic password for updates.
+ *
+ * <p>This feature implies that the device has the RebootEscrow HAL implementation.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the requisite kernel support to support incremental delivery aka Incremental FileSystem.
+ *
+ * feature not present - IncFs is not present on the device.
+ * 1 - IncFs v1, core features, no PerUid support. Optional in R.
+ * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
+ *
+ * @see IncrementalManager#isFeatureEnabled
+ * @see IncrementalManager#getVersion()
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_INCREMENTAL_DELIVERY =
+ "android.software.incremental_delivery";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has tuner hardware to support tuner operations.
+ *
+ * <p>This feature implies that the device has the tuner HAL implementation.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TUNER = "android.hardware.tv.tuner";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the necessary changes to support app enumeration.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * a Keystore implementation that can only enforce limited use key in hardware with max usage
+ * count equals to 1.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY =
+ "android.hardware.keystore.single_use_key";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * a Keystore implementation that can enforce limited use key in hardware with any max usage
+ * count (including count equals to 1).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
+ "android.hardware.keystore.limited_use_key";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * a Keystore implementation that can create application-specific attestation keys.
+ * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+ "android.hardware.keystore.app_attest_key";
+
+ /** @hide */
+ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
+
+ /**
+ * Extra field name for the URI to a verification file. Passed to a package
+ * verifier.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_URI = "android.content.pm.extra.VERIFICATION_URI";
+
+ /**
+ * Extra field name for the ID of a package pending verification. Passed to
+ * a package verifier and is used to call back to
+ * {@link PackageManager#verifyPendingInstall(int, int)}
+ */
+ public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
+
+ /**
+ * Extra field name for the package identifier which is trying to install
+ * the package.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_INSTALLER_PACKAGE
+ = "android.content.pm.extra.VERIFICATION_INSTALLER_PACKAGE";
+
+ /**
+ * Extra field name for the requested install flags for a package pending
+ * verification. Passed to a package verifier.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_INSTALL_FLAGS
+ = "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
+
+ /**
+ * Extra field name for the uid of who is requesting to install
+ * the package.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_INSTALLER_UID
+ = "android.content.pm.extra.VERIFICATION_INSTALLER_UID";
+
+ /**
+ * Extra field name for the package name of a package pending verification.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_PACKAGE_NAME
+ = "android.content.pm.extra.VERIFICATION_PACKAGE_NAME";
+ /**
+ * Extra field name for the result of a verification, either
+ * {@link #VERIFICATION_ALLOW}, or {@link #VERIFICATION_REJECT}.
+ * Passed to package verifiers after a package is verified.
+ */
+ public static final String EXTRA_VERIFICATION_RESULT
+ = "android.content.pm.extra.VERIFICATION_RESULT";
+
+ /**
+ * Extra field name for the version code of a package pending verification.
+ * @deprecated Use {@link #EXTRA_VERIFICATION_LONG_VERSION_CODE} instead.
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_VERIFICATION_VERSION_CODE
+ = "android.content.pm.extra.VERIFICATION_VERSION_CODE";
+
+ /**
+ * Extra field name for the long version code of a package pending verification.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_LONG_VERSION_CODE =
+ "android.content.pm.extra.VERIFICATION_LONG_VERSION_CODE";
+
+ /**
+ * Extra field name for the Merkle tree root hash of a package.
+ * <p>Passed to a package verifier both prior to verification and as a result
+ * of verification.
+ * <p>The value of the extra is a specially formatted list:
+ * {@code filename1:HASH_1;filename2:HASH_2;...;filenameN:HASH_N}
+ * <p>The extra must include an entry for every APK within an installation. If
+ * a hash is not physically present, a hash value of {@code 0} will be used.
+ * <p>The root hash is generated using SHA-256, no salt with a 4096 byte block
+ * size. See the description of the
+ * <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#merkle-tree">fs-verity merkle-tree</a>
+ * for more details.
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_ROOT_HASH =
+ "android.content.pm.extra.EXTRA_VERIFICATION_ROOT_HASH";
+
+ /**
+ * Extra field name for the ID of a intent filter pending verification.
+ * Passed to an intent filter verifier and is used to call back to
+ * {@link #verifyIntentFilter}
+ *
+ * @deprecated Use DomainVerificationManager APIs.
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID";
+
+ /**
+ * Extra field name for the scheme used for an intent filter pending verification. Passed to
+ * an intent filter verifier and is used to construct the URI to verify against.
+ *
+ * Usually this is "https"
+ *
+ * @deprecated Use DomainVerificationManager APIs.
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME";
+
+ /**
+ * Extra field name for the host names to be used for an intent filter pending verification.
+ * Passed to an intent filter verifier and is used to construct the URI to verify the
+ * intent filter.
+ *
+ * This is a space delimited list of hosts.
+ *
+ * @deprecated Use DomainVerificationManager APIs.
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS";
+
+ /**
+ * Extra field name for the package name to be used for an intent filter pending verification.
+ * Passed to an intent filter verifier and is used to check the verification responses coming
+ * from the hosts. Each host response will need to include the package name of APK containing
+ * the intent filter.
+ *
+ * @deprecated Use DomainVerificationManager APIs.
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME";
+
+ /**
+ * The action used to request that the user approve a permission request
+ * from the application.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_REQUEST_PERMISSIONS =
+ "android.content.pm.action.REQUEST_PERMISSIONS";
+
+ /**
+ * The names of the requested permissions.
+ * <p>
+ * <strong>Type:</strong> String[]
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REQUEST_PERMISSIONS_NAMES =
+ "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
+
+ /**
+ * The results from the permissions request.
+ * <p>
+ * <strong>Type:</strong> int[] of #PermissionResult
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS
+ = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
+
+ /**
+ * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
+ * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides
+ * the existing definition for the permission.
+ * @hide
+ */
+ public static final String EXTRA_FAILURE_EXISTING_PACKAGE
+ = "android.content.pm.extra.FAILURE_EXISTING_PACKAGE";
+
+ /**
+ * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
+ * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the permission that is
+ * being redundantly defined by the package being installed.
+ * @hide
+ */
+ public static final String EXTRA_FAILURE_EXISTING_PERMISSION
+ = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION";
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * by the user and apps can still request it at runtime.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_USER_SET = 1 << 0;
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * by the user and it is fixed, i.e. apps can no longer request
+ * this permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_USER_FIXED = 1 << 1;
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * by device policy and neither apps nor the user can change
+ * its state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_POLICY_FIXED = 1 << 2;
+
+ /**
+ * Permission flag: The permission is set in a granted state but
+ * access to resources it guards is restricted by other means to
+ * enable revoking a permission on legacy apps that do not support
+ * runtime permissions. If this permission is upgraded to runtime
+ * because the app was updated to support runtime permissions, the
+ * the permission will be revoked in the upgrade process.
+ *
+ * @deprecated Renamed to {@link #FLAG_PERMISSION_REVOKED_COMPAT}.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 1 << 3;
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * because the app is a component that is a part of the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4;
+
+ /**
+ * Permission flag: The permission is granted by default because it
+ * enables app functionality that is expected to work out-of-the-box
+ * for providing a smooth user experience. For example, the phone app
+ * is expected to have the phone permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5;
+
+ /**
+ * Permission flag: The permission has to be reviewed before any of
+ * the app components can run.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 1 << 6;
+
+ /**
+ * Permission flag: The permission has not been explicitly requested by
+ * the app but has been added automatically by the system. Revoke once
+ * the app does explicitly request it.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 1 << 7;
+
+ /**
+ * Permission flag: The permission's usage should be made highly visible to the user
+ * when granted.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
+
+ /**
+ * Permission flag: The permission's usage should be made highly visible to the user
+ * when denied.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
+
+ /**
+ * Permission flag: The permission is restricted but the app is exempt
+ * from the restriction and is allowed to hold this permission in its
+ * full form and the exemption is provided by the installer on record.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 1 << 11;
+
+ /**
+ * Permission flag: The permission is restricted but the app is exempt
+ * from the restriction and is allowed to hold this permission in its
+ * full form and the exemption is provided by the system due to its
+ * permission policy.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 1 << 12;
+
+ /**
+ * Permission flag: The permission is restricted but the app is exempt
+ * from the restriction and is allowed to hold this permission and the
+ * exemption is provided by the system when upgrading from an OS version
+ * where the permission was not restricted to an OS version where the
+ * permission is restricted.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 1 << 13;
+
+
+ /**
+ * Permission flag: The permission is disabled but may be granted. If
+ * disabled the data protected by the permission should be protected
+ * by a no-op (empty list, default error, etc) instead of crashing the
+ * client.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 1 << 14;
+
+ /**
+ * Permission flag: The permission is granted because the application holds a role.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 1 << 15;
+
+ /**
+ * Permission flag: The permission should have been revoked but is kept granted for
+ * compatibility. The data protected by the permission should be protected by a no-op (empty
+ * list, default error, etc) instead of crashing the client. The permission will be revoked if
+ * the app is upgraded to supports it.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_REVOKED_COMPAT = FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
+ /**
+ * Permission flag: The permission is one-time and should be revoked automatically on app
+ * inactivity
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_ONE_TIME = 1 << 16;
+
+ /**
+ * Permission flag: Whether permission was revoked by auto-revoke.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17;
+
+ /**
+ * Permission flag: This location permission is selected as the level of granularity of
+ * location accuracy.
+ * Example: If this flag is set for ACCESS_FINE_LOCATION, FINE location is the selected location
+ * accuracy for location permissions.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY = 1 << 19;
+
+ /**
+ * Permission flags: Reserved for use by the permission controller. The platform and any
+ * packages besides the permission controller should not assume any definition about these
+ * flags.
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = 1 << 28 | 1 << 29
+ | 1 << 30 | 1 << 31;
+
+ /**
+ * Permission flags: Bitwise or of all permission flags allowing an
+ * exemption for a restricted permission.
+ * @hide
+ */
+ public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
+ FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
+ | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+ | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+
+ /**
+ * Mask for all permission flags.
+ *
+ * @hide
+ *
+ * @deprecated Don't use - does not capture all flags.
+ */
+ @Deprecated
+ @SystemApi
+ public static final int MASK_PERMISSION_FLAGS = 0xFF;
+
+ /**
+ * Mask for all permission flags.
+ *
+ * @hide
+ */
+ public static final int MASK_PERMISSION_FLAGS_ALL = FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_POLICY_FIXED
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+ | FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_GRANTED_BY_DEFAULT
+ | FLAG_PERMISSION_REVIEW_REQUIRED
+ | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+ | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+ | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
+ | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+ | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+ | FLAG_PERMISSION_APPLY_RESTRICTION
+ | FLAG_PERMISSION_GRANTED_BY_ROLE
+ | FLAG_PERMISSION_REVOKED_COMPAT
+ | FLAG_PERMISSION_ONE_TIME
+ | FLAG_PERMISSION_AUTO_REVOKED;
+
+ /**
+ * Injected activity in app that forwards user to setting activity of that app.
+ *
+ * @hide
+ */
+ public static final String APP_DETAILS_ACTIVITY_CLASS_NAME = AppDetailsActivity.class.getName();
+
+ /**
+ * Permission whitelist flag: permissions whitelisted by the system.
+ * Permissions can also be whitelisted by the installer, on upgrade, or on
+ * role grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ */
+ public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1 << 0;
+
+ /**
+ * Permission whitelist flag: permissions whitelisted by the installer.
+ * Permissions can also be whitelisted by the system, on upgrade, or on role
+ * grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ */
+ public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 1 << 1;
+
+ /**
+ * Permission whitelist flag: permissions whitelisted by the system
+ * when upgrading from an OS version where the permission was not
+ * restricted to an OS version where the permission is restricted.
+ * Permissions can also be whitelisted by the installer, the system, or on
+ * role grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ */
+ public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = {
+ FLAG_PERMISSION_WHITELIST_SYSTEM,
+ FLAG_PERMISSION_WHITELIST_INSTALLER,
+ FLAG_PERMISSION_WHITELIST_UPGRADE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionWhitelistFlags {}
+
+ /**
+ * This is a library that contains components apps can invoke. For
+ * example, a services for apps to bind to, or standard chooser UI,
+ * etc. This library is versioned and backwards compatible. Clients
+ * should check its version via {@link android.ext.services.Version
+ * #getVersionCode()} and avoid calling APIs added in later versions.
+ * <p>
+ * This shared library no longer exists since Android R.
+ *
+ * @see #getServicesSystemSharedLibraryPackageName()
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
+
+ /**
+ * This is a library that contains components apps can dynamically
+ * load. For example, new widgets, helper classes, etc. This library
+ * is versioned and backwards compatible. Clients should check its
+ * version via {@link android.ext.shared.Version#getVersionCode()}
+ * and avoid calling APIs added in later versions.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
+
+ /**
+ * Used when starting a process for an Activity.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_ACTIVITY = 0;
+
+ /**
+ * Used when starting a process for a Service.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_SERVICE = 1;
+
+ /**
+ * Used when moving a Service to the foreground.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE = 2;
+
+ /**
+ * Used when starting a process for a BroadcastReceiver.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER = 3;
+
+ /**
+ * Used when starting a process for a ContentProvider.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_CONTENT_PROVIDER = 4;
+
+ /**
+ * Used when starting a process for a BroadcastReceiver.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_BACKUP = 5;
+
+ /**
+ * Used with Context.getClassLoader() across Android packages.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_CROSS_PACKAGE = 6;
+
+ /**
+ * Used when starting a package within a process for Instrumentation.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_INSTRUMENTATION = 7;
+
+ /**
+ * Total number of usage reasons.
+ *
+ * @hide
+ */
+ public static final int NOTIFY_PACKAGE_USE_REASONS_COUNT = 8;
+
+ /**
+ * Constant for specifying the highest installed package version code.
+ */
+ public static final int VERSION_CODE_HIGHEST = -1;
+
+ /**
+ * Apps targeting Android R and above will need to declare the packages and intents they intend
+ * to use to get details about other apps on a device. Such declarations must be made via the
+ * {@code <queries>} tag in the manifest.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long FILTER_APPLICATION_QUERY = 135549675L;
+
+ /** {@hide} */
+ @IntDef(prefix = {"SYSTEM_APP_STATE_"}, value = {
+ SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN,
+ SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE,
+ SYSTEM_APP_STATE_INSTALLED,
+ SYSTEM_APP_STATE_UNINSTALLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemAppState {}
+
+ /**
+ * Constant for use with {@link #setSystemAppState} to mark a system app as hidden until
+ * installation.
+ * @hide
+ */
+ @SystemApi
+ public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0;
+
+ /**
+ * Constant for use with {@link #setSystemAppState} to mark a system app as not hidden until
+ * installation.
+ * @hide
+ */
+ @SystemApi
+ public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1;
+
+ /**
+ * Constant for use with {@link #setSystemAppState} to change a system app's state to installed.
+ * @hide
+ */
+ @SystemApi
+ public static final int SYSTEM_APP_STATE_INSTALLED = 2;
+
+ /**
+ * Constant for use with {@link #setSystemAppState} to change a system app's state to
+ * uninstalled.
+ * @hide
+ */
+ @SystemApi
+ public static final int SYSTEM_APP_STATE_UNINSTALLED = 3;
+
+ /**
+ * A manifest property to control app's participation in {@code adb backup}. Should only
+ * be used by system / privileged apps.
+ *
+ * @hide
+ */
+ public static final String PROPERTY_ALLOW_ADB_BACKUP = "android.backup.ALLOW_ADB_BACKUP";
+
+ /** {@hide} */
+ public int getUserId() {
+ return UserHandle.myUserId();
+ }
+
+ /**
+ * @deprecated Do not instantiate or subclass - obtain an instance from
+ * {@link Context#getPackageManager}
+ */
+ @Deprecated
+ public PackageManager() {}
+
+ /**
+ * Retrieve overall information about an application package that is
+ * installed on the system.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A PackageInfo object containing information about the package. If
+ * flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the package
+ * is not found in the list of installed applications, the package
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract PackageInfo getPackageInfo(@NonNull String packageName,
+ @PackageInfoFlags int flags)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve overall information about an application package that is
+ * installed on the system. This method can be used for retrieving
+ * information about packages for which multiple versions can be installed
+ * at the time. Currently only packages hosting static shared libraries can
+ * have multiple installed versions. The method can also be used to get info
+ * for a package that has a single version installed by passing
+ * {@link #VERSION_CODE_HIGHEST} in the {@link VersionedPackage}
+ * constructor.
+ *
+ * @param versionedPackage The versioned package for which to query.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A PackageInfo object containing information about the package. If
+ * flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the package
+ * is not found in the list of installed applications, the package
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage,
+ @PackageInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve overall information about an application package that is
+ * installed on the system.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user id.
+ * @return A PackageInfo object containing information about the package. If
+ * flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the package
+ * is not found in the list of installed applications, the package
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage
+ public abstract PackageInfo getPackageInfoAsUser(@NonNull String packageName,
+ @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Map from the current package names in use on the device to whatever
+ * the current canonical name of that package is.
+ * @param packageNames Array of current names to be mapped.
+ * @return Returns an array of the same size as the original, containing
+ * the canonical name for each package.
+ */
+ public abstract String[] currentToCanonicalPackageNames(@NonNull String[] packageNames);
+
+ /**
+ * Map from a packages canonical name to the current name in use on the device.
+ * @param packageNames Array of new names to be mapped.
+ * @return Returns an array of the same size as the original, containing
+ * the current name for each package.
+ */
+ public abstract String[] canonicalToCurrentPackageNames(@NonNull String[] packageNames);
+
+ /**
+ * Returns a "good" intent to launch a front-door activity in a package.
+ * This is used, for example, to implement an "open" button when browsing
+ * through packages. The current implementation looks first for a main
+ * activity in the category {@link Intent#CATEGORY_INFO}, and next for a
+ * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns
+ * <code>null</code> if neither are found.
+ *
+ * @param packageName The name of the package to inspect.
+ *
+ * @return A fully-qualified {@link Intent} that can be used to launch the
+ * main activity in the package. Returns <code>null</code> if the package
+ * does not contain such an activity, or if <em>packageName</em> is not
+ * recognized.
+ */
+ public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
+
+ /**
+ * Return a "good" intent to launch a front-door Leanback activity in a
+ * package, for use for example to implement an "open" button when browsing
+ * through packages. The current implementation will look for a main
+ * activity in the category {@link Intent#CATEGORY_LEANBACK_LAUNCHER}, or
+ * return null if no main leanback activities are found.
+ *
+ * @param packageName The name of the package to inspect.
+ * @return Returns either a fully-qualified Intent that can be used to launch
+ * the main Leanback activity in the package, or null if the package
+ * does not contain such an activity.
+ */
+ public abstract @Nullable Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName);
+
+ /**
+ * Return a "good" intent to launch a front-door Car activity in a
+ * package, for use for example to implement an "open" button when browsing
+ * through packages. The current implementation will look for a main
+ * activity in the category {@link Intent#CATEGORY_CAR_LAUNCHER}, or
+ * return null if no main car activities are found.
+ *
+ * @param packageName The name of the package to inspect.
+ * @return Returns either a fully-qualified Intent that can be used to launch
+ * the main Car activity in the package, or null if the package
+ * does not contain such an activity.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract @Nullable Intent getCarLaunchIntentForPackage(@NonNull String packageName);
+
+ /**
+ * Return an array of all of the POSIX secondary group IDs that have been
+ * assigned to the given package.
+ * <p>
+ * Note that the same package may have different GIDs under different
+ * {@link UserHandle} on the same device.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @return Returns an int array of the assigned GIDs, or null if there are
+ * none.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract int[] getPackageGids(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Return an array of all of the POSIX secondary group IDs that have been
+ * assigned to the given package.
+ * <p>
+ * Note that the same package may have different GIDs under different
+ * {@link UserHandle} on the same device.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @return Returns an int array of the assigned gids, or null if there are
+ * none.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract int[] getPackageGids(@NonNull String packageName, @PackageInfoFlags int flags)
+ throws NameNotFoundException;
+
+ /**
+ * Return the UID associated with the given package name.
+ * <p>
+ * Note that the same package will have different UIDs under different
+ * {@link UserHandle} on the same device.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @return Returns an integer UID who owns the given package name.
+ * @throws NameNotFoundException if a package with the given name can not be
+ * found on the system.
+ */
+ public abstract int getPackageUid(@NonNull String packageName, @PackageInfoFlags int flags)
+ throws NameNotFoundException;
+
+ /**
+ * Return the UID associated with the given package name.
+ * <p>
+ * Note that the same package will have different UIDs under different
+ * {@link UserHandle} on the same device.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param userId The user handle identifier to look up the package under.
+ * @return Returns an integer UID who owns the given package name.
+ * @throws NameNotFoundException if a package with the given name can not be
+ * found on the system.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract int getPackageUidAsUser(@NonNull String packageName, @UserIdInt int userId)
+ throws NameNotFoundException;
+
+ /**
+ * Return the UID associated with the given package name.
+ * <p>
+ * Note that the same package will have different UIDs under different
+ * {@link UserHandle} on the same device.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param userId The user handle identifier to look up the package under.
+ * @return Returns an integer UID who owns the given package name.
+ * @throws NameNotFoundException if a package with the given name can not be
+ * found on the system.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract int getPackageUidAsUser(@NonNull String packageName,
+ @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular permission.
+ *
+ * @param permName The fully qualified name (i.e. com.google.permission.LOGIN)
+ * of the permission you are interested in.
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a {@link PermissionInfo} containing information about the
+ * permission.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ //@Deprecated
+ public abstract PermissionInfo getPermissionInfo(@NonNull String permName,
+ @PermissionInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Query for all of the permissions associated with a particular group.
+ *
+ * @param permissionGroup The fully qualified name (i.e. com.google.permission.LOGIN)
+ * of the permission group you are interested in. Use {@code null} to
+ * find all of the permissions not associated with a group.
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a list of {@link PermissionInfo} containing information
+ * about all of the permissions in the given group.
+ * @throws NameNotFoundException if a group with the given name cannot be
+ * found on the system.
+ */
+ //@Deprecated
+ @NonNull
+ public abstract List<PermissionInfo> queryPermissionsByGroup(@Nullable String permissionGroup,
+ @PermissionInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Returns true if some permissions are individually controlled.
+ *
+ * <p>The user usually grants and revokes permission-groups. If this option is set some
+ * dangerous system permissions can be revoked/granted by the user separately from their group.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ public abstract boolean arePermissionsIndividuallyControlled();
+
+ /**
+ * Returns true if wireless consent mode is enabled
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract boolean isWirelessConsentModeEnabled();
+
+ /**
+ * Retrieve all of the information we know about a particular group of
+ * permissions.
+ *
+ * @param groupName The fully qualified name (i.e.
+ * com.google.permission_group.APPS) of the permission you are
+ * interested in.
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a {@link PermissionGroupInfo} containing information
+ * about the permission.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ //@Deprecated
+ @NonNull
+ public abstract PermissionGroupInfo getPermissionGroupInfo(@NonNull String groupName,
+ @PermissionGroupInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the known permission groups in the system.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a list of {@link PermissionGroupInfo} containing
+ * information about all of the known permission groups.
+ */
+ //@Deprecated
+ @NonNull
+ public abstract List<PermissionGroupInfo> getAllPermissionGroups(
+ @PermissionGroupInfoFlags int flags);
+
+ /**
+ * Get the platform-defined permissions which belong to a particular permission group.
+ *
+ * @param permissionGroupName the permission group whose permissions are desired
+ * @param executor the {@link Executor} on which to invoke the callback
+ * @param callback the callback which will receive a list of the platform-defined permissions in
+ * the group, or empty if the group is not a valid platform-defined permission
+ * group, or there was an exception
+ */
+ public void getPlatformPermissionsForGroup(@NonNull String permissionGroupName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<List<String>> callback) {}
+
+ /**
+ * Get the platform-defined permission group of a particular permission, if the permission is a
+ * platform-defined permission.
+ *
+ * @param permissionName the permission whose group is desired
+ * @param executor the {@link Executor} on which to invoke the callback
+ * @param callback the callback which will receive the name of the permission group this
+ * permission belongs to, or {@code null} if it has no group, is not a
+ * platform-defined permission, or there was an exception
+ */
+ public void getGroupOfPlatformPermission(@NonNull String permissionName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<String> callback) {}
+
+ /**
+ * Retrieve all of the information we know about a particular
+ * package/application.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of an
+ * application.
+ * @param flags Additional option flags to modify the data returned.
+ * @return An {@link ApplicationInfo} containing information about the
+ * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if
+ * the package is not found in the list of installed applications,
+ * the application information is retrieved from the list of
+ * uninstalled applications (which includes installed applications
+ * as well as applications with data directory i.e. applications
+ * which had been deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public abstract ApplicationInfo getApplicationInfo(@NonNull String packageName,
+ @ApplicationInfoFlags int flags) throws NameNotFoundException;
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+ @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular
+ * package/application, for a specific user.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of an
+ * application.
+ * @param flags Additional option flags to modify the data returned.
+ * @return An {@link ApplicationInfo} containing information about the
+ * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if
+ * the package is not found in the list of installed applications,
+ * the application information is retrieved from the list of
+ * uninstalled applications (which includes installed applications
+ * as well as applications with data directory i.e. applications
+ * which had been deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+ @ApplicationInfoFlags int flags, @NonNull UserHandle user)
+ throws NameNotFoundException {
+ return getApplicationInfoAsUser(packageName, flags, user.getIdentifier());
+ }
+
+ /**
+ * @return The target SDK version for the given package name.
+ * @throws NameNotFoundException if a package with the given name cannot be found on the system.
+ */
+ @IntRange(from = 0)
+ public int getTargetSdkVersion(@NonNull String packageName) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve all of the information we know about a particular activity
+ * class.
+ *
+ * @param component The full component name (i.e.
+ * com.google.apps.contacts/com.google.apps.contacts.
+ * ContactsList) of an Activity class.
+ * @param flags Additional option flags to modify the data returned.
+ * @return An {@link ActivityInfo} containing information about the
+ * activity.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component,
+ @ComponentInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular receiver
+ * class.
+ *
+ * @param component The full component name (i.e.
+ * com.google.apps.calendar/com.google.apps.calendar.
+ * CalendarAlarm) of a Receiver class.
+ * @param flags Additional option flags to modify the data returned.
+ * @return An {@link ActivityInfo} containing information about the
+ * receiver.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public abstract ActivityInfo getReceiverInfo(@NonNull ComponentName component,
+ @ComponentInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular service class.
+ *
+ * @param component The full component name (i.e.
+ * com.google.apps.media/com.google.apps.media.
+ * BackgroundPlayback) of a Service class.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link ServiceInfo} object containing information about the
+ * service.
+ * @throws NameNotFoundException if the component cannot be found on the system.
+ */
+ @NonNull
+ public abstract ServiceInfo getServiceInfo(@NonNull ComponentName component,
+ @ComponentInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular content
+ * provider class.
+ *
+ * @param component The full component name (i.e.
+ * com.google.providers.media/com.google.providers.media.
+ * MediaProvider) of a ContentProvider class.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link ProviderInfo} object containing information about the
+ * provider.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public abstract ProviderInfo getProviderInfo(@NonNull ComponentName component,
+ @ComponentInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve information for a particular module.
+ *
+ * @param packageName The name of the module.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link ModuleInfo} object containing information about the
+ * module.
+ * @throws NameNotFoundException if a module with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public ModuleInfo getModuleInfo(@NonNull String packageName, @ModuleInfoFlags int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException(
+ "getModuleInfo not implemented in subclass");
+ }
+
+ /**
+ * Return a List of all modules that are installed.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link List} of {@link ModuleInfo} objects, one for each installed
+ * module, containing information about the module. In the unlikely case
+ * there are no installed modules, an empty list is returned.
+ */
+ @NonNull
+ public List<ModuleInfo> getInstalledModules(@InstalledModulesFlags int flags) {
+ throw new UnsupportedOperationException(
+ "getInstalledModules not implemented in subclass");
+ }
+
+ /**
+ * Return a List of all packages that are installed for the current user.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return A List of PackageInfo objects, one for each installed package,
+ * containing information about the package. In the unlikely case
+ * there are no installed packages, an empty list is returned. If
+ * flag {@code MATCH_UNINSTALLED_PACKAGES} is set, the package
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ */
+ @NonNull
+ public abstract List<PackageInfo> getInstalledPackages(@PackageInfoFlags int flags);
+
+ /**
+ * Return a List of all installed packages that are currently holding any of
+ * the given permissions.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return A List of PackageInfo objects, one for each installed package
+ * that holds any of the permissions that were provided, containing
+ * information about the package. If no installed packages hold any
+ * of the permissions, an empty list is returned. If flag
+ * {@code MATCH_UNINSTALLED_PACKAGES} is set, the package
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ */
+ @NonNull
+ public abstract List<PackageInfo> getPackagesHoldingPermissions(
+ @NonNull String[] permissions, @PackageInfoFlags int flags);
+
+ /**
+ * Return a List of all packages that are installed on the device, for a
+ * specific user.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user for whom the installed packages are to be listed
+ * @return A List of PackageInfo objects, one for each installed package,
+ * containing information about the package. In the unlikely case
+ * there are no installed packages, an empty list is returned. If
+ * flag {@code MATCH_UNINSTALLED_PACKAGES} is set, the package
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public abstract List<PackageInfo> getInstalledPackagesAsUser(@PackageInfoFlags int flags,
+ @UserIdInt int userId);
+
+ /**
+ * Check whether a particular package has been granted a particular
+ * permission.
+ *
+ * @param permName The name of the permission you are checking for.
+ * @param packageName The name of the package you are checking against.
+ *
+ * @return If the package has the permission, PERMISSION_GRANTED is
+ * returned. If it does not have the permission, PERMISSION_DENIED
+ * is returned.
+ *
+ * @see #PERMISSION_GRANTED
+ * @see #PERMISSION_DENIED
+ */
+ @CheckResult
+ @PermissionResult
+ public abstract int checkPermission(@NonNull String permName,
+ @NonNull String packageName);
+
+ /**
+ * Checks whether a particular permissions has been revoked for a
+ * package by policy. Typically the device owner or the profile owner
+ * may apply such a policy. The user cannot grant policy revoked
+ * permissions, hence the only way for an app to get such a permission
+ * is by a policy change.
+ *
+ * @param permName The name of the permission you are checking for.
+ * @param packageName The name of the package you are checking against.
+ *
+ * @return Whether the permission is restricted by policy.
+ */
+ @CheckResult
+ //@Deprecated
+ public abstract boolean isPermissionRevokedByPolicy(@NonNull String permName,
+ @NonNull String packageName);
+
+ /**
+ * Gets the package name of the component controlling runtime permissions.
+ *
+ * @return the package name of the component controlling runtime permissions
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @UnsupportedAppUsage
+ public String getPermissionControllerPackageName() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Add a new dynamic permission to the system. For this to work, your
+ * package must have defined a permission tree through the
+ * {@link android.R.styleable#AndroidManifestPermissionTree
+ * <permission-tree>} tag in its manifest. A package can only add
+ * permissions to trees that were defined by either its own package or
+ * another with the same user id; a permission is in a tree if it
+ * matches the name of the permission tree + ".": for example,
+ * "com.foo.bar" is a member of the permission tree "com.foo".
+ *
+ * <p>It is good to make your permission tree name descriptive, because you
+ * are taking possession of that entire set of permission names. Thus, it
+ * must be under a domain you control, with a suffix that will not match
+ * any normal permissions that may be declared in any applications that
+ * are part of that domain.
+ *
+ * <p>New permissions must be added before
+ * any .apks are installed that use those permissions. Permissions you
+ * add through this method are remembered across reboots of the device.
+ * If the given permission already exists, the info you supply here
+ * will be used to update it.
+ *
+ * @param info Description of the permission to be added.
+ *
+ * @return Returns true if a new permission was created, false if an
+ * existing one was updated.
+ *
+ * @throws SecurityException if you are not allowed to add the
+ * given permission name.
+ *
+ * @see #removePermission(String)
+ */
+ //@Deprecated
+ public abstract boolean addPermission(@NonNull PermissionInfo info);
+
+ /**
+ * Like {@link #addPermission(PermissionInfo)} but asynchronously
+ * persists the package manager state after returning from the call,
+ * allowing it to return quicker and batch a series of adds at the
+ * expense of no guarantee the added permission will be retained if
+ * the device is rebooted before it is written.
+ */
+ //@Deprecated
+ public abstract boolean addPermissionAsync(@NonNull PermissionInfo info);
+
+ /**
+ * Removes a permission that was previously added with
+ * {@link #addPermission(PermissionInfo)}. The same ownership rules apply
+ * -- you are only allowed to remove permissions that you are allowed
+ * to add.
+ *
+ * @param permName The name of the permission to remove.
+ *
+ * @throws SecurityException if you are not allowed to remove the
+ * given permission name.
+ *
+ * @see #addPermission(PermissionInfo)
+ */
+ //@Deprecated
+ public abstract void removePermission(@NonNull String permName);
+
+ /**
+ * Permission flags set when granting or revoking a permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ @IntDef(prefix = { "FLAG_PERMISSION_" }, value = {
+ FLAG_PERMISSION_USER_SET,
+ FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_POLICY_FIXED,
+ FLAG_PERMISSION_REVOKE_ON_UPGRADE,
+ FLAG_PERMISSION_SYSTEM_FIXED,
+ FLAG_PERMISSION_GRANTED_BY_DEFAULT,
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED,
+ /*
+ FLAG_PERMISSION_REVOKE_WHEN_REQUESED
+ */
+ FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+ FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT,
+ FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT,
+ FLAG_PERMISSION_APPLY_RESTRICTION,
+ FLAG_PERMISSION_GRANTED_BY_ROLE,
+ FLAG_PERMISSION_REVOKED_COMPAT,
+ FLAG_PERMISSION_ONE_TIME,
+ FLAG_PERMISSION_AUTO_REVOKED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionFlags {}
+
+ /**
+ * Grant a runtime permission to an application which the application does not
+ * already have. The permission must have been requested by the application.
+ * If the application is not allowed to hold the permission, a {@link
+ * java.lang.SecurityException} is thrown. If the package or permission is
+ * invalid, a {@link java.lang.IllegalArgumentException} is thrown.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * android.permission.GRANT_RUNTIME_PERMISSIONS and if the user id is
+ * not the current user android.permission.INTERACT_ACROSS_USERS_FULL.
+ * </p>
+ *
+ * @param packageName The package to which to grant the permission.
+ * @param permName The permission name to grant.
+ * @param user The user for which to grant the permission.
+ *
+ * @see #revokeRuntimePermission(String, String, android.os.UserHandle)
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ public abstract void grantRuntimePermission(@NonNull String packageName,
+ @NonNull String permName, @NonNull UserHandle user);
+
+ /**
+ * Revoke a runtime permission that was previously granted by {@link
+ * #grantRuntimePermission(String, String, android.os.UserHandle)}. The
+ * permission must have been requested by and granted to the application.
+ * If the application is not allowed to hold the permission, a {@link
+ * java.lang.SecurityException} is thrown. If the package or permission is
+ * invalid, a {@link java.lang.IllegalArgumentException} is thrown.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * android.permission.REVOKE_RUNTIME_PERMISSIONS and if the user id is
+ * not the current user android.permission.INTERACT_ACROSS_USERS_FULL.
+ * </p>
+ *
+ * @param packageName The package from which to revoke the permission.
+ * @param permName The permission name to revoke.
+ * @param user The user for which to revoke the permission.
+ *
+ * @see #grantRuntimePermission(String, String, android.os.UserHandle)
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+ public abstract void revokeRuntimePermission(@NonNull String packageName,
+ @NonNull String permName, @NonNull UserHandle user);
+
+ /**
+ * Revoke a runtime permission that was previously granted by {@link
+ * #grantRuntimePermission(String, String, android.os.UserHandle)}. The
+ * permission must have been requested by and granted to the application.
+ * If the application is not allowed to hold the permission, a {@link
+ * java.lang.SecurityException} is thrown. If the package or permission is
+ * invalid, a {@link java.lang.IllegalArgumentException} is thrown.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * android.permission.REVOKE_RUNTIME_PERMISSIONS and if the user id is
+ * not the current user android.permission.INTERACT_ACROSS_USERS_FULL.
+ * </p>
+ *
+ * @param packageName The package from which to revoke the permission.
+ * @param permName The permission name to revoke.
+ * @param user The user for which to revoke the permission.
+ * @param reason The reason for the revoke.
+ *
+ * @see #grantRuntimePermission(String, String, android.os.UserHandle)
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+ public void revokeRuntimePermission(@NonNull String packageName,
+ @NonNull String permName, @NonNull UserHandle user, @NonNull String reason) {
+ revokeRuntimePermission(packageName, permName, user);
+ }
+
+ /**
+ * Gets the state flags associated with a permission.
+ *
+ * @param permName The permission for which to get the flags.
+ * @param packageName The package name for which to get the flags.
+ * @param user The user for which to get permission flags.
+ * @return The permission flags.
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.GET_RUNTIME_PERMISSIONS
+ })
+ @PermissionFlags
+ public abstract int getPermissionFlags(@NonNull String permName,
+ @NonNull String packageName, @NonNull UserHandle user);
+
+ /**
+ * Updates the flags associated with a permission by replacing the flags in
+ * the specified mask with the provided flag values.
+ *
+ * @param permName The permission for which to update the flags.
+ * @param packageName The package name for which to update the flags.
+ * @param flagMask The flags which to replace.
+ * @param flagValues The flags with which to replace.
+ * @param user The user for which to update the permission flags.
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ })
+ public abstract void updatePermissionFlags(@NonNull String permName,
+ @NonNull String packageName, @PermissionFlags int flagMask,
+ @PermissionFlags int flagValues, @NonNull UserHandle user);
+
+ /**
+ * Gets the restricted permissions that have been whitelisted and the app
+ * is allowed to have them granted in their full form.
+ *
+ * <p> Permissions can be hard restricted which means that the app cannot hold
+ * them or soft restricted where the app can hold the permission but in a weaker
+ * form. Whether a permission is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard
+ * restricted} or {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted}
+ * depends on the permission declaration. Whitelisting a hard restricted permission
+ * allows for the to hold that permission and whitelisting a soft restricted
+ * permission allows the app to hold the permission in its full, unrestricted form.
+ *
+ * <p><ol>There are four allowlists:
+ *
+ * <li>one for cases where the system permission policy whitelists a permission
+ * This list corresponds to the{@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
+ * Can only be accessed by pre-installed holders of a dedicated permission.
+ *
+ * <li>one for cases where the system whitelists the permission when upgrading
+ * from an OS version in which the permission was not restricted to an OS version
+ * in which the permission is restricted. This list corresponds to the {@link
+ * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by pre-installed
+ * holders of a dedicated permission or the installer on record.
+ *
+ * <li>one for cases where the installer of the package whitelists a permission.
+ * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag.
+ * Can be accessed by pre-installed holders of a dedicated permission or the
+ * installer on record.
+ * </ol>
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @param packageName The app for which to get whitelisted permissions.
+ * @param whitelistFlag The flag to determine which whitelist to query. Only one flag
+ * can be passed.s
+ * @return The whitelisted permissions that are on any of the whitelists you query for.
+ *
+ * @see #addWhitelistedRestrictedPermission(String, String, int)
+ * @see #removeWhitelistedRestrictedPermission(String, String, int)
+ * @see #FLAG_PERMISSION_WHITELIST_SYSTEM
+ * @see #FLAG_PERMISSION_WHITELIST_UPGRADE
+ * @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ *
+ * @throws SecurityException if you try to access a whitelist that you have no access to.
+ */
+ //@Deprecated
+ @RequiresPermission(value = Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS,
+ conditional = true)
+ public @NonNull Set<String> getWhitelistedRestrictedPermissions(
+ @NonNull String packageName, @PermissionWhitelistFlags int whitelistFlag) {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Adds a whitelisted restricted permission for an app.
+ *
+ * <p> Permissions can be hard restricted which means that the app cannot hold
+ * them or soft restricted where the app can hold the permission but in a weaker
+ * form. Whether a permission is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard
+ * restricted} or {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted}
+ * depends on the permission declaration. Whitelisting a hard restricted permission
+ * allows for the to hold that permission and whitelisting a soft restricted
+ * permission allows the app to hold the permission in its full, unrestricted form.
+ *
+ * <p><ol>There are four whitelists:
+ *
+ * <li>one for cases where the system permission policy whitelists a permission
+ * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
+ * Can only be modified by pre-installed holders of a dedicated permission.
+ *
+ * <li>one for cases where the system whitelists the permission when upgrading
+ * from an OS version in which the permission was not restricted to an OS version
+ * in which the permission is restricted. This list corresponds to the {@link
+ * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed
+ * holders of a dedicated permission. The installer on record can only remove
+ * permissions from this whitelist.
+ *
+ * <li>one for cases where the installer of the package whitelists a permission.
+ * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag.
+ * Can be modified by pre-installed holders of a dedicated permission or the installer
+ * on record.
+ * </ol>
+ *
+ * <p>You need to specify the whitelists for which to set the whitelisted permissions
+ * which will clear the previous whitelisted permissions and replace them with the
+ * provided ones.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @param packageName The app for which to get whitelisted permissions.
+ * @param permName The whitelisted permission to add.
+ * @param whitelistFlags The whitelists to which to add. Passing multiple flags
+ * updates all specified whitelists.
+ * @return Whether the permission was added to the whitelist.
+ *
+ * @see #getWhitelistedRestrictedPermissions(String, int)
+ * @see #removeWhitelistedRestrictedPermission(String, String, int)
+ * @see #FLAG_PERMISSION_WHITELIST_SYSTEM
+ * @see #FLAG_PERMISSION_WHITELIST_UPGRADE
+ * @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ *
+ * @throws SecurityException if you try to modify a whitelist that you have no access to.
+ */
+ //@Deprecated
+ @RequiresPermission(value = Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS,
+ conditional = true)
+ public boolean addWhitelistedRestrictedPermission(@NonNull String packageName,
+ @NonNull String permName, @PermissionWhitelistFlags int whitelistFlags) {
+ return false;
+ }
+
+ /**
+ * Removes a whitelisted restricted permission for an app.
+ *
+ * <p> Permissions can be hard restricted which means that the app cannot hold
+ * them or soft restricted where the app can hold the permission but in a weaker
+ * form. Whether a permission is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard
+ * restricted} or {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted}
+ * depends on the permission declaration. Whitelisting a hard restricted permission
+ * allows for the to hold that permission and whitelisting a soft restricted
+ * permission allows the app to hold the permission in its full, unrestricted form.
+ *
+ * <p><ol>There are four whitelists:
+ *
+ * <li>one for cases where the system permission policy whitelists a permission
+ * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
+ * Can only be modified by pre-installed holders of a dedicated permission.
+ *
+ * <li>one for cases where the system whitelists the permission when upgrading
+ * from an OS version in which the permission was not restricted to an OS version
+ * in which the permission is restricted. This list corresponds to the {@link
+ * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed
+ * holders of a dedicated permission. The installer on record can only remove
+ * permissions from this whitelist.
+ *
+ * <li>one for cases where the installer of the package whitelists a permission.
+ * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag.
+ * Can be modified by pre-installed holders of a dedicated permission or the installer
+ * on record.
+ *
+ * <li>one for cases where the system exempts the permission when upgrading
+ * from an OS version in which the permission was not restricted to an OS version
+ * in which the permission is restricted. This list corresponds to the {@link
+ * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed
+ * holders of a dedicated permission. The installer on record can only remove
+ * permissions from this allowlist.
+ * </ol>
+ *
+ * <p>You need to specify the whitelists for which to set the whitelisted permissions
+ * which will clear the previous whitelisted permissions and replace them with the
+ * provided ones.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @param packageName The app for which to get whitelisted permissions.
+ * @param permName The whitelisted permission to remove.
+ * @param whitelistFlags The whitelists from which to remove. Passing multiple flags
+ * updates all specified whitelists.
+ * @return Whether the permission was removed from the whitelist.
+ *
+ * @see #getWhitelistedRestrictedPermissions(String, int)
+ * @see #addWhitelistedRestrictedPermission(String, String, int)
+ * @see #FLAG_PERMISSION_WHITELIST_SYSTEM
+ * @see #FLAG_PERMISSION_WHITELIST_UPGRADE
+ * @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ *
+ * @throws SecurityException if you try to modify a whitelist that you have no access to.
+ */
+ //@Deprecated
+ @RequiresPermission(value = Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS,
+ conditional = true)
+ public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName,
+ @NonNull String permName, @PermissionWhitelistFlags int whitelistFlags) {
+ return false;
+ }
+
+ /**
+ * Marks an application exempt from having its permissions be automatically revoked when
+ * the app is unused for an extended period of time.
+ *
+ * Only the installer on record that installed the given package is allowed to call this.
+ *
+ * Packages start in whitelisted state, and it is the installer's responsibility to
+ * un-whitelist the packages it installs, unless auto-revoking permissions from that package
+ * would cause breakages beyond having to re-request the permission(s).
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @param packageName The app for which to set exemption.
+ * @param whitelisted Whether the app should be whitelisted.
+ *
+ * @return whether any change took effect.
+ *
+ * @see #isAutoRevokeWhitelisted
+ *
+ * @throws SecurityException if you you have no access to modify this.
+ */
+ //@Deprecated
+ @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS,
+ conditional = true)
+ public boolean setAutoRevokeWhitelisted(@NonNull String packageName, boolean whitelisted) {
+ return false;
+ }
+
+ /**
+ * Checks whether an application is exempt from having its permissions be automatically revoked
+ * when the app is unused for an extended period of time.
+ *
+ * Only the installer on record that installed the given package, or a holder of
+ * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @param packageName The app for which to set exemption.
+ *
+ * @return Whether the app is whitelisted.
+ *
+ * @see #setAutoRevokeWhitelisted
+ *
+ * @throws SecurityException if you you have no access to this.
+ */
+ //@Deprecated
+ @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS,
+ conditional = true)
+ public boolean isAutoRevokeWhitelisted(@NonNull String packageName) {
+ return false;
+ }
+
+
+ /**
+ * Gets whether you should show UI with rationale for requesting a permission.
+ * You should do this only if you do not have the permission and the context in
+ * which the permission is requested does not clearly communicate to the user
+ * what would be the benefit from grating this permission.
+ *
+ * @param permName A permission your app wants to request.
+ * @return Whether you can show permission rationale UI.
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
+
+ /**
+ * Gets the localized label that corresponds to the option in settings for granting
+ * background access.
+ *
+ * <p>The intended use is for apps to reference this label in its instruction for users to grant
+ * a background permission.
+ *
+ * @return the localized label that corresponds to the settings option for granting
+ * background access
+ */
+ @NonNull
+ public CharSequence getBackgroundPermissionOptionLabel() {
+ return "";
+ }
+
+ /**
+ * Returns an {@link android.content.Intent} suitable for passing to
+ * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
+ * which prompts the user to grant permissions to this application.
+ *
+ * @throws NullPointerException if {@code permissions} is {@code null} or empty.
+ *
+ * @hide
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
+ if (ArrayUtils.isEmpty(permissions)) {
+ throw new IllegalArgumentException("permission cannot be null or empty");
+ }
+ Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
+ intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
+ intent.setPackage(getPermissionControllerPackageName());
+ return intent;
+ }
+
+ /**
+ * Compare the signatures of two packages to determine if the same
+ * signature appears in both of them. If they do contain the same
+ * signature, then they are allowed special privileges when working
+ * with each other: they can share the same user-id, run instrumentation
+ * against each other, etc.
+ *
+ * @param packageName1 First package name whose signature will be compared.
+ * @param packageName2 Second package name whose signature will be compared.
+ *
+ * @return Returns an integer indicating whether all signatures on the
+ * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if
+ * all signatures match or < 0 if there is not a match ({@link
+ * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}).
+ *
+ * @see #checkSignatures(int, int)
+ */
+ @CheckResult
+ @SignatureResult
+ public abstract int checkSignatures(@NonNull String packageName1,
+ @NonNull String packageName2);
+
+ /**
+ * Like {@link #checkSignatures(String, String)}, but takes UIDs of
+ * the two packages to be checked. This can be useful, for example,
+ * when doing the check in an IPC, where the UID is the only identity
+ * available. It is functionally identical to determining the package
+ * associated with the UIDs and checking their signatures.
+ *
+ * @param uid1 First UID whose signature will be compared.
+ * @param uid2 Second UID whose signature will be compared.
+ *
+ * @return Returns an integer indicating whether all signatures on the
+ * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if
+ * all signatures match or < 0 if there is not a match ({@link
+ * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}).
+ *
+ * @see #checkSignatures(String, String)
+ */
+ @CheckResult
+ public abstract @SignatureResult int checkSignatures(int uid1, int uid2);
+
+ /**
+ * Retrieve the names of all packages that are associated with a particular
+ * user id. In most cases, this will be a single package name, the package
+ * that has been assigned that user id. Where there are multiple packages
+ * sharing the same user id through the "sharedUserId" mechanism, all
+ * packages with that id will be returned.
+ *
+ * @param uid The user id for which you would like to retrieve the
+ * associated packages.
+ *
+ * @return Returns an array of one or more packages assigned to the user
+ * id, or null if there are no known packages with the given id.
+ */
+ public abstract @Nullable String[] getPackagesForUid(int uid);
+
+ /**
+ * Retrieve the official name associated with a uid. This name is
+ * guaranteed to never change, though it is possible for the underlying
+ * uid to be changed. That is, if you are storing information about
+ * uids in persistent storage, you should use the string returned
+ * by this function instead of the raw uid.
+ *
+ * @param uid The uid for which you would like to retrieve a name.
+ * @return Returns a unique name for the given uid, or null if the
+ * uid is not currently assigned.
+ */
+ public abstract @Nullable String getNameForUid(int uid);
+
+ /**
+ * Retrieves the official names associated with each given uid.
+ * @see #getNameForUid(int)
+ *
+ * @hide
+ */
+ @SuppressWarnings({"HiddenAbstractMethod", "NullableCollection"})
+ @TestApi
+ public abstract @Nullable String[] getNamesForUids(int[] uids);
+
+ /**
+ * Return the user id associated with a shared user name. Multiple
+ * applications can specify a shared user name in their manifest and thus
+ * end up using a common uid. This might be used for new applications
+ * that use an existing shared user name and need to know the uid of the
+ * shared user.
+ *
+ * @param sharedUserName The shared user name whose uid is to be retrieved.
+ * @return Returns the UID associated with the shared user.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract int getUidForSharedUser(@NonNull String sharedUserName)
+ throws NameNotFoundException;
+
+ /**
+ * Return a List of all application packages that are installed for the
+ * current user. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
+ * applications including those deleted with {@code DELETE_KEEP_DATA}
+ * (partially installed apps with data directory) will be returned.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return A List of ApplicationInfo objects, one for each installed
+ * application. In the unlikely case there are no installed
+ * packages, an empty list is returned. If flag
+ * {@code MATCH_UNINSTALLED_PACKAGES} is set, the application
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ */
+ @NonNull
+ public abstract List<ApplicationInfo> getInstalledApplications(@ApplicationInfoFlags int flags);
+
+ /**
+ * Return a List of all application packages that are installed on the
+ * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
+ * set, a list of all applications including those deleted with
+ * {@code DELETE_KEEP_DATA} (partially installed apps with data directory)
+ * will be returned.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user for whom the installed applications are to be
+ * listed
+ * @return A List of ApplicationInfo objects, one for each installed
+ * application. In the unlikely case there are no installed
+ * packages, an empty list is returned. If flag
+ * {@code MATCH_UNINSTALLED_PACKAGES} is set, the application
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @TestApi
+ public abstract List<ApplicationInfo> getInstalledApplicationsAsUser(
+ @ApplicationInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Gets the instant applications the user recently used.
+ *
+ * @return The instant app list.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+ public abstract @NonNull List<InstantAppInfo> getInstantApps();
+
+ /**
+ * Gets the icon for an instant application.
+ *
+ * @param packageName The app package name.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+ public abstract @Nullable Drawable getInstantAppIcon(String packageName);
+
+ /**
+ * Gets whether this application is an instant app.
+ *
+ * @return Whether caller is an instant app.
+ *
+ * @see #isInstantApp(String)
+ * @see #updateInstantAppCookie(byte[])
+ * @see #getInstantAppCookie()
+ * @see #getInstantAppCookieMaxBytes()
+ */
+ public abstract boolean isInstantApp();
+
+ /**
+ * Gets whether the given package is an instant app.
+ *
+ * @param packageName The package to check
+ * @return Whether the given package is an instant app.
+ *
+ * @see #isInstantApp()
+ * @see #updateInstantAppCookie(byte[])
+ * @see #getInstantAppCookie()
+ * @see #getInstantAppCookieMaxBytes()
+ * @see #clearInstantAppCookie()
+ */
+ public abstract boolean isInstantApp(@NonNull String packageName);
+
+ /**
+ * Gets the maximum size in bytes of the cookie data an instant app
+ * can store on the device.
+ *
+ * @return The max cookie size in bytes.
+ *
+ * @see #isInstantApp()
+ * @see #isInstantApp(String)
+ * @see #updateInstantAppCookie(byte[])
+ * @see #getInstantAppCookie()
+ * @see #clearInstantAppCookie()
+ */
+ public abstract int getInstantAppCookieMaxBytes();
+
+ /**
+ * deprecated
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract int getInstantAppCookieMaxSize();
+
+ /**
+ * Gets the instant application cookie for this app. Non
+ * instant apps and apps that were instant but were upgraded
+ * to normal apps can still access this API. For instant apps
+ * this cookie is cached for some time after uninstall while for
+ * normal apps the cookie is deleted after the app is uninstalled.
+ * The cookie is always present while the app is installed.
+ *
+ * @return The cookie.
+ *
+ * @see #isInstantApp()
+ * @see #isInstantApp(String)
+ * @see #updateInstantAppCookie(byte[])
+ * @see #getInstantAppCookieMaxBytes()
+ * @see #clearInstantAppCookie()
+ */
+ public abstract @NonNull byte[] getInstantAppCookie();
+
+ /**
+ * Clears the instant application cookie for the calling app.
+ *
+ * @see #isInstantApp()
+ * @see #isInstantApp(String)
+ * @see #getInstantAppCookieMaxBytes()
+ * @see #getInstantAppCookie()
+ * @see #clearInstantAppCookie()
+ */
+ public abstract void clearInstantAppCookie();
+
+ /**
+ * Updates the instant application cookie for the calling app. Non
+ * instant apps and apps that were instant but were upgraded
+ * to normal apps can still access this API. For instant apps
+ * this cookie is cached for some time after uninstall while for
+ * normal apps the cookie is deleted after the app is uninstalled.
+ * The cookie is always present while the app is installed. The
+ * cookie size is limited by {@link #getInstantAppCookieMaxBytes()}.
+ * Passing <code>null</code> or an empty array clears the cookie.
+ * </p>
+ *
+ * @param cookie The cookie data.
+ *
+ * @see #isInstantApp()
+ * @see #isInstantApp(String)
+ * @see #getInstantAppCookieMaxBytes()
+ * @see #getInstantAppCookie()
+ * @see #clearInstantAppCookie()
+ *
+ * @throws IllegalArgumentException if the array exceeds max cookie size.
+ */
+ public abstract void updateInstantAppCookie(@Nullable byte[] cookie);
+
+ /**
+ * @removed
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract boolean setInstantAppCookie(@Nullable byte[] cookie);
+
+ /**
+ * Get a list of shared libraries that are available on the
+ * system.
+ *
+ * @return An array of shared library names that are
+ * available on the system, or null if none are installed.
+ *
+ */
+ @Nullable
+ public abstract String[] getSystemSharedLibraryNames();
+
+ /**
+ * Get a list of shared libraries on the device.
+ *
+ * @param flags To filter the libraries to return.
+ * @return The shared library list.
+ *
+ * @see #MATCH_UNINSTALLED_PACKAGES
+ */
+ public abstract @NonNull List<SharedLibraryInfo> getSharedLibraries(
+ @InstallFlags int flags);
+
+ /**
+ * Get a list of shared libraries on the device.
+ *
+ * @param flags To filter the libraries to return.
+ * @param userId The user to query for.
+ * @return The shared library list.
+ *
+ * @see #MATCH_FACTORY_ONLY
+ * @see #MATCH_KNOWN_PACKAGES
+ * @see #MATCH_ANY_USER
+ * @see #MATCH_UNINSTALLED_PACKAGES
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(
+ @InstallFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Get the list of shared libraries declared by a package.
+ *
+ * @param packageName the package name to query
+ * @param flags the flags to filter packages
+ * @return the shared library list
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @RequiresPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES)
+ @SystemApi
+ public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName,
+ @InstallFlags int flags) {
+ throw new UnsupportedOperationException(
+ "getDeclaredSharedLibraries() not implemented in subclass");
+ }
+
+ /**
+ * Get the name of the package hosting the services shared library.
+ * <p>
+ * Note that this package is no longer a shared library since Android R. It is now a package
+ * that hosts for a bunch of updatable services that the system binds to.
+ *
+ * @return The library host package.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ @TestApi
+ public abstract @NonNull String getServicesSystemSharedLibraryPackageName();
+
+ /**
+ * Get the name of the package hosting the shared components shared library.
+ *
+ * @return The library host package.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ @TestApi
+ public abstract @NonNull String getSharedSystemSharedLibraryPackageName();
+
+ /**
+ * Returns the names of the packages that have been changed
+ * [eg. added, removed or updated] since the given sequence
+ * number.
+ * <p>If no packages have been changed, returns <code>null</code>.
+ * <p>The sequence number starts at <code>0</code> and is
+ * reset every boot.
+ * @param sequenceNumber The first sequence number for which to retrieve package changes.
+ * @see android.provider.Settings.Global#BOOT_COUNT
+ */
+ public abstract @Nullable ChangedPackages getChangedPackages(
+ @IntRange(from=0) int sequenceNumber);
+
+ /**
+ * Get a list of features that are available on the
+ * system.
+ *
+ * @return An array of FeatureInfo classes describing the features
+ * that are available on the system, or null if there are none(!!).
+ */
+ @NonNull
+ public abstract FeatureInfo[] getSystemAvailableFeatures();
+
+ /**
+ * Check whether the given feature name is one of the available features as
+ * returned by {@link #getSystemAvailableFeatures()}. This tests for the
+ * presence of <em>any</em> version of the given feature name; use
+ * {@link #hasSystemFeature(String, int)} to check for a minimum version.
+ *
+ * @return Returns true if the devices supports the feature, else false.
+ */
+ public abstract boolean hasSystemFeature(@NonNull String featureName);
+
+ /**
+ * Check whether the given feature name and version is one of the available
+ * features as returned by {@link #getSystemAvailableFeatures()}. Since
+ * features are defined to always be backwards compatible, this returns true
+ * if the available feature version is greater than or equal to the
+ * requested version.
+ *
+ * @return Returns true if the devices supports the feature, else false.
+ */
+ public abstract boolean hasSystemFeature(@NonNull String featureName, int version);
+
+ /**
+ * Determine the best action to perform for a given Intent. This is how
+ * {@link Intent#resolveActivity} finds an activity if a class has not been
+ * explicitly specified.
+ * <p>
+ * <em>Note:</em> if using an implicit Intent (without an explicit
+ * ComponentName specified), be sure to consider whether to set the
+ * {@link #MATCH_DEFAULT_ONLY} only flag. You need to do so to resolve the
+ * activity in the same way that
+ * {@link android.content.Context#startActivity(Intent)} and
+ * {@link android.content.Intent#resolveActivity(PackageManager)
+ * Intent.resolveActivity(PackageManager)} do.
+ * </p>
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}.
+ * @return Returns a ResolveInfo object containing the final activity intent
+ * that was determined to be the best action. Returns null if no
+ * matching activity was found. If multiple matching activities are
+ * found and there is no default set, returns a ResolveInfo object
+ * containing something else, such as the activity resolver.
+ */
+ @Nullable
+ public abstract ResolveInfo resolveActivity(@NonNull Intent intent,
+ @ResolveInfoFlags int flags);
+
+ /**
+ * Determine the best action to perform for a given Intent for a given user.
+ * This is how {@link Intent#resolveActivity} finds an activity if a class
+ * has not been explicitly specified.
+ * <p>
+ * <em>Note:</em> if using an implicit Intent (without an explicit
+ * ComponentName specified), be sure to consider whether to set the
+ * {@link #MATCH_DEFAULT_ONLY} only flag. You need to do so to resolve the
+ * activity in the same way that
+ * {@link android.content.Context#startActivity(Intent)} and
+ * {@link android.content.Intent#resolveActivity(PackageManager)
+ * Intent.resolveActivity(PackageManager)} do.
+ * </p>
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}.
+ * @param userId The user id.
+ * @return Returns a ResolveInfo object containing the final activity intent
+ * that was determined to be the best action. Returns null if no
+ * matching activity was found. If multiple matching activities are
+ * found and there is no default set, returns a ResolveInfo object
+ * containing something else, such as the activity resolver.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @UnsupportedAppUsage
+ public abstract ResolveInfo resolveActivityAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Retrieve all activities that can be performed for the given intent.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set
+ * {@link #MATCH_ALL} to prevent any filtering of the results.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching activity, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveActivity}. If there are no matching activities, an
+ * empty list is returned.
+ */
+ @NonNull
+ public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent,
+ @ResolveInfoFlags int flags);
+
+ /**
+ * Retrieve all activities that can be performed for the given intent, for a
+ * specific user.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set
+ * {@link #MATCH_ALL} to prevent any filtering of the results.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching activity, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveActivity}. If there are no matching activities, an
+ * empty list is returned.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Retrieve all activities that can be performed for the given intent, for a
+ * specific user.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set
+ * {@link #MATCH_ALL} to prevent any filtering of the results.
+ * @param user The user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching activity, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveActivity}. If there are no matching activities, an
+ * empty list is returned.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+ return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier());
+ }
+
+ /**
+ * Retrieve a set of activities that should be presented to the user as
+ * similar options. This is like {@link #queryIntentActivities}, except it
+ * also allows you to supply a list of more explicit Intents that you would
+ * like to resolve to particular options, and takes care of returning the
+ * final ResolveInfo list in a reasonable order, with no duplicates, based
+ * on those inputs.
+ *
+ * @param caller The class name of the activity that is making the request.
+ * This activity will never appear in the output list. Can be
+ * null.
+ * @param specifics An array of Intents that should be resolved to the first
+ * specific results. Can be null.
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching activity. The list is ordered first by all of the
+ * intents resolved in <var>specifics</var> and then any additional
+ * activities that can handle <var>intent</var> but did not get
+ * included by one of the <var>specifics</var> intents. If there are
+ * no matching activities, an empty list is returned.
+ */
+ @NonNull
+ public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
+ @Nullable Intent[] specifics, @NonNull Intent intent, @ResolveInfoFlags int flags);
+
+ /**
+ * Retrieve all receivers that can handle a broadcast of the given intent.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching receiver, ordered from best to worst. If there are
+ * no matching receivers, an empty list or null is returned.
+ */
+ @NonNull
+ public abstract List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent,
+ @ResolveInfoFlags int flags);
+
+ /**
+ * Retrieve all receivers that can handle a broadcast of the given intent,
+ * for a specific user.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned.
+ * @param userHandle UserHandle of the user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching receiver, ordered from best to worst. If there are
+ * no matching receivers, an empty list or null is returned.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, UserHandle userHandle) {
+ return queryBroadcastReceiversAsUser(intent, flags, userHandle.getIdentifier());
+ }
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+
+ /** @deprecated @hide */
+ @NonNull
+ @Deprecated
+ @UnsupportedAppUsage
+ public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId) {
+ final String msg = "Shame on you for calling the hidden API "
+ + "queryBroadcastReceivers(). Shame!";
+ if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(msg);
+ } else {
+ Log.d(TAG, msg);
+ return queryBroadcastReceiversAsUser(intent, flags, userId);
+ }
+ }
+
+ /**
+ * Determine the best service to handle for a given Intent.
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a ResolveInfo object containing the final service intent
+ * that was determined to be the best action. Returns null if no
+ * matching service was found.
+ */
+ @Nullable
+ public abstract ResolveInfo resolveService(@NonNull Intent intent, @ResolveInfoFlags int flags);
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ public abstract ResolveInfo resolveServiceAsUser(@NonNull 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().
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching service, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveService}. If there are no matching services, an
+ * empty list or null is returned.
+ */
+ @NonNull
+ public abstract List<ResolveInfo> queryIntentServices(@NonNull Intent intent,
+ @ResolveInfoFlags int flags);
+
+ /**
+ * Retrieve all services that can match the given intent for a given user.
+ *
+ * @param intent The desired intent as per resolveService().
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user id.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching service, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveService}. If there are no matching services, an
+ * empty list or null is returned.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Retrieve all services that can match the given intent for a given user.
+ *
+ * @param intent The desired intent as per resolveService().
+ * @param flags Additional option flags to modify the data returned.
+ * @param user The user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching service, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveService}. If there are no matching services, an
+ * empty list or null is returned.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+ return queryIntentServicesAsUser(intent, flags, user.getIdentifier());
+ }
+
+ /**
+ * Retrieve all providers that can match the given intent.
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user id.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching provider, ordered from best to worst. If there are
+ * no matching services, an empty list or null is returned.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract List<ResolveInfo> queryIntentContentProvidersAsUser(
+ @NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Retrieve all providers that can match the given intent.
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned.
+ * @param user The user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching provider, ordered from best to worst. If there are
+ * no matching services, an empty list or null is returned.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+ return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier());
+ }
+
+ /**
+ * Retrieve all providers that can match the given intent.
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching provider, ordered from best to worst. If there are
+ * no matching services, an empty list or null is returned.
+ */
+ @NonNull
+ public abstract List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent,
+ @ResolveInfoFlags int flags);
+
+ /**
+ * Find a single content provider by its authority.
+ * <p>
+ * Example:<p>
+ * <pre>
+ * Uri uri = Uri.parse("content://com.example.app.provider/table1");
+ * ProviderInfo info = packageManager.resolveContentProvider(uri.getAuthority(), flags);
+ * </pre>
+ *
+ * @param authority The authority of the provider to find.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link ProviderInfo} object containing information about the
+ * provider. If a provider was not found, returns null.
+ */
+ @Nullable
+ public abstract ProviderInfo resolveContentProvider(@NonNull String authority,
+ @ComponentInfoFlags int flags);
+
+ /**
+ * Find a single content provider by its base path name.
+ *
+ * @param providerName The name of the provider to find.
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user id.
+ * @return A {@link ProviderInfo} object containing information about the
+ * provider. If a provider was not found, returns null.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @UnsupportedAppUsage
+ public abstract ProviderInfo resolveContentProviderAsUser(@NonNull String providerName,
+ @ComponentInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * Retrieve content provider information.
+ * <p>
+ * <em>Note: unlike most other methods, an empty result set is indicated
+ * by a null return instead of an empty list.</em>
+ *
+ * @param processName If non-null, limits the returned providers to only
+ * those that are hosted by the given process. If null, all
+ * content providers are returned.
+ * @param uid If <var>processName</var> is non-null, this is the required
+ * uid owning the requested content providers.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A list of {@link ProviderInfo} objects containing one entry for
+ * each provider either matching <var>processName</var> or, if
+ * <var>processName</var> is null, all known content providers.
+ * <em>If there are no matching providers, null is returned.</em>
+ */
+ @NonNull
+ public abstract List<ProviderInfo> queryContentProviders(
+ @Nullable String processName, int uid, @ComponentInfoFlags int flags);
+
+ /**
+ * Same as {@link #queryContentProviders}, except when {@code metaDataKey} is not null,
+ * it only returns providers which have metadata with the {@code metaDataKey} key.
+ *
+ * <p>DO NOT USE the {@code metaDataKey} parameter, unless you're the contacts provider.
+ * You really shouldn't need it. Other apps should use {@link #queryIntentContentProviders}
+ * instead.
+ *
+ * <p>The {@code metaDataKey} parameter was added to allow the contacts provider to quickly
+ * scan the GAL providers on the device. Unfortunately the discovery protocol used metadata
+ * to mark GAL providers, rather than intent filters, so we can't use
+ * {@link #queryIntentContentProviders} for that.
+ *
+ * @hide
+ */
+ @NonNull
+ public List<ProviderInfo> queryContentProviders(@Nullable String processName,
+ int uid, @ComponentInfoFlags int flags, String metaDataKey) {
+ // Provide the default implementation for mocks.
+ return queryContentProviders(processName, uid, flags);
+ }
+
+ /**
+ * Retrieve all of the information we know about a particular
+ * instrumentation class.
+ *
+ * @param className The full name (i.e.
+ * com.google.apps.contacts.InstrumentList) of an Instrumentation
+ * class.
+ * @param flags Additional option flags to modify the data returned.
+ * @return An {@link InstrumentationInfo} object containing information
+ * about the instrumentation.
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public abstract InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName className,
+ @InstrumentationInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve information about available instrumentation code. May be used to
+ * retrieve either all instrumentation code, or only the code targeting a
+ * particular package.
+ *
+ * @param targetPackage If null, all instrumentation is returned; only the
+ * instrumentation targeting this package name is returned.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A list of {@link InstrumentationInfo} objects containing one
+ * entry for each matching instrumentation. If there are no
+ * instrumentation available, returns an empty list.
+ */
+ @NonNull
+ public abstract List<InstrumentationInfo> queryInstrumentation(@NonNull String targetPackage,
+ @InstrumentationInfoFlags int flags);
+
+ /**
+ * Retrieve an image from a package. This is a low-level API used by
+ * the various package manager info structures (such as
+ * {@link ComponentInfo} to implement retrieval of their associated
+ * icon.
+ *
+ * @param packageName The name of the package that this icon is coming from.
+ * Cannot be null.
+ * @param resid The resource identifier of the desired image. Cannot be 0.
+ * @param appInfo Overall information about <var>packageName</var>. This
+ * may be null, in which case the application information will be retrieved
+ * for you if needed; if you already have this information around, it can
+ * be much more efficient to supply it here.
+ *
+ * @return Returns a Drawable holding the requested image. Returns null if
+ * an image could not be found for any reason.
+ */
+ @Nullable
+ public abstract Drawable getDrawable(@NonNull String packageName, @DrawableRes int resid,
+ @Nullable ApplicationInfo appInfo);
+
+ /**
+ * Retrieve the icon associated with an activity. Given the full name of
+ * an activity, retrieves the information about it and calls
+ * {@link ComponentInfo#loadIcon ComponentInfo.loadIcon()} to return its icon.
+ * If the activity cannot be found, NameNotFoundException is thrown.
+ *
+ * @param activityName Name of the activity whose icon is to be retrieved.
+ *
+ * @return Returns the image of the icon, or the default activity icon if
+ * it could not be found. Does not return null.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * activity could not be loaded.
+ *
+ * @see #getActivityIcon(Intent)
+ */
+ @NonNull
+ public abstract Drawable getActivityIcon(@NonNull ComponentName activityName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the icon associated with an Intent. If intent.getClassName() is
+ * set, this simply returns the result of
+ * getActivityIcon(intent.getClassName()). Otherwise it resolves the intent's
+ * component and returns the icon associated with the resolved component.
+ * If intent.getClassName() cannot be found or the Intent cannot be resolved
+ * to a component, NameNotFoundException is thrown.
+ *
+ * @param intent The intent for which you would like to retrieve an icon.
+ *
+ * @return Returns the image of the icon, or the default activity icon if
+ * it could not be found. Does not return null.
+ * @throws NameNotFoundException Thrown if the resources for application
+ * matching the given intent could not be loaded.
+ *
+ * @see #getActivityIcon(ComponentName)
+ */
+ @NonNull
+ public abstract Drawable getActivityIcon(@NonNull Intent intent)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the banner associated with an activity. Given the full name of
+ * an activity, retrieves the information about it and calls
+ * {@link ComponentInfo#loadIcon ComponentInfo.loadIcon()} to return its
+ * banner. If the activity cannot be found, NameNotFoundException is thrown.
+ *
+ * @param activityName Name of the activity whose banner is to be retrieved.
+ * @return Returns the image of the banner, or null if the activity has no
+ * banner specified.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * activity could not be loaded.
+ * @see #getActivityBanner(Intent)
+ */
+ @Nullable
+ public abstract Drawable getActivityBanner(@NonNull ComponentName activityName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the banner associated with an Intent. If intent.getClassName()
+ * is set, this simply returns the result of
+ * getActivityBanner(intent.getClassName()). Otherwise it resolves the
+ * intent's component and returns the banner associated with the resolved
+ * component. If intent.getClassName() cannot be found or the Intent cannot
+ * be resolved to a component, NameNotFoundException is thrown.
+ *
+ * @param intent The intent for which you would like to retrieve a banner.
+ * @return Returns the image of the banner, or null if the activity has no
+ * banner specified.
+ * @throws NameNotFoundException Thrown if the resources for application
+ * matching the given intent could not be loaded.
+ * @see #getActivityBanner(ComponentName)
+ */
+ @Nullable
+ public abstract Drawable getActivityBanner(@NonNull Intent intent)
+ throws NameNotFoundException;
+
+ /**
+ * Return the generic icon for an activity that is used when no specific
+ * icon is defined.
+ *
+ * @return Drawable Image of the icon.
+ */
+ @NonNull
+ public abstract Drawable getDefaultActivityIcon();
+
+ /**
+ * Retrieve the icon associated with an application. If it has not defined
+ * an icon, the default app icon is returned. Does not return null.
+ *
+ * @param info Information about application being queried.
+ *
+ * @return Returns the image of the icon, or the default application icon
+ * if it could not be found.
+ *
+ * @see #getApplicationIcon(String)
+ */
+ @NonNull
+ public abstract Drawable getApplicationIcon(@NonNull ApplicationInfo info);
+
+ /**
+ * Retrieve the icon associated with an application. Given the name of the
+ * application's package, retrieves the information about it and calls
+ * getApplicationIcon() to return its icon. If the application cannot be
+ * found, NameNotFoundException is thrown.
+ *
+ * @param packageName Name of the package whose application icon is to be
+ * retrieved.
+ *
+ * @return Returns the image of the icon, or the default application icon
+ * if it could not be found. Does not return null.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded.
+ *
+ * @see #getApplicationIcon(ApplicationInfo)
+ */
+ @NonNull
+ public abstract Drawable getApplicationIcon(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the banner associated with an application.
+ *
+ * @param info Information about application being queried.
+ * @return Returns the image of the banner or null if the application has no
+ * banner specified.
+ * @see #getApplicationBanner(String)
+ */
+ @Nullable
+ public abstract Drawable getApplicationBanner(@NonNull ApplicationInfo info);
+
+ /**
+ * Retrieve the banner associated with an application. Given the name of the
+ * application's package, retrieves the information about it and calls
+ * getApplicationIcon() to return its banner. If the application cannot be
+ * found, NameNotFoundException is thrown.
+ *
+ * @param packageName Name of the package whose application banner is to be
+ * retrieved.
+ * @return Returns the image of the banner or null if the application has no
+ * banner specified.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded.
+ * @see #getApplicationBanner(ApplicationInfo)
+ */
+ @Nullable
+ public abstract Drawable getApplicationBanner(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the logo associated with an activity. Given the full name of an
+ * activity, retrieves the information about it and calls
+ * {@link ComponentInfo#loadLogo ComponentInfo.loadLogo()} to return its
+ * logo. If the activity cannot be found, NameNotFoundException is thrown.
+ *
+ * @param activityName Name of the activity whose logo is to be retrieved.
+ * @return Returns the image of the logo or null if the activity has no logo
+ * specified.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * activity could not be loaded.
+ * @see #getActivityLogo(Intent)
+ */
+ @Nullable
+ public abstract Drawable getActivityLogo(@NonNull ComponentName activityName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the logo associated with an Intent. If intent.getClassName() is
+ * set, this simply returns the result of
+ * getActivityLogo(intent.getClassName()). Otherwise it resolves the intent's
+ * component and returns the logo associated with the resolved component.
+ * If intent.getClassName() cannot be found or the Intent cannot be resolved
+ * to a component, NameNotFoundException is thrown.
+ *
+ * @param intent The intent for which you would like to retrieve a logo.
+ *
+ * @return Returns the image of the logo, or null if the activity has no
+ * logo specified.
+ *
+ * @throws NameNotFoundException Thrown if the resources for application
+ * matching the given intent could not be loaded.
+ *
+ * @see #getActivityLogo(ComponentName)
+ */
+ @Nullable
+ public abstract Drawable getActivityLogo(@NonNull Intent intent)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the logo associated with an application. If it has not specified
+ * a logo, this method returns null.
+ *
+ * @param info Information about application being queried.
+ *
+ * @return Returns the image of the logo, or null if no logo is specified
+ * by the application.
+ *
+ * @see #getApplicationLogo(String)
+ */
+ @Nullable
+ public abstract Drawable getApplicationLogo(@NonNull ApplicationInfo info);
+
+ /**
+ * Retrieve the logo associated with an application. Given the name of the
+ * application's package, retrieves the information about it and calls
+ * getApplicationLogo() to return its logo. If the application cannot be
+ * found, NameNotFoundException is thrown.
+ *
+ * @param packageName Name of the package whose application logo is to be
+ * retrieved.
+ *
+ * @return Returns the image of the logo, or null if no application logo
+ * has been specified.
+ *
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded.
+ *
+ * @see #getApplicationLogo(ApplicationInfo)
+ */
+ @Nullable
+ public abstract Drawable getApplicationLogo(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * If the target user is a managed profile, then this returns a badged copy of the given icon
+ * to be able to distinguish it from the original icon. For badging an arbitrary drawable use
+ * {@link #getUserBadgedDrawableForDensity(
+ * android.graphics.drawable.Drawable, UserHandle, android.graphics.Rect, int)}.
+ * <p>
+ * If the original drawable is a BitmapDrawable and the backing bitmap is
+ * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging
+ * is performed in place and the original drawable is returned.
+ * </p>
+ *
+ * @param drawable The drawable to badge.
+ * @param user The target user.
+ * @return A drawable that combines the original icon and a badge as
+ * determined by the system.
+ */
+ @NonNull
+ public abstract Drawable getUserBadgedIcon(@NonNull Drawable drawable,
+ @NonNull UserHandle user);
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a badged copy of the given
+ * drawable allowing the user to distinguish it from the original drawable.
+ * The caller can specify the location in the bounds of the drawable to be
+ * badged where the badge should be applied as well as the density of the
+ * badge to be used.
+ * <p>
+ * If the original drawable is a BitmapDrawable and the backing bitmap is
+ * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging
+ * is performed in place and the original drawable is returned.
+ * </p>
+ *
+ * @param drawable The drawable to badge.
+ * @param user The target user.
+ * @param badgeLocation Where in the bounds of the badged drawable to place
+ * the badge. If it's {@code null}, the badge is applied on top of the entire
+ * drawable being badged.
+ * @param badgeDensity The optional desired density for the badge as per
+ * {@link android.util.DisplayMetrics#densityDpi}. If it's not positive,
+ * the density of the display is used.
+ * @return A drawable that combines the original drawable and a badge as
+ * determined by the system.
+ */
+ @NonNull
+ public abstract Drawable getUserBadgedDrawableForDensity(@NonNull Drawable drawable,
+ @NonNull UserHandle user, @Nullable Rect badgeLocation, int badgeDensity);
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a drawable to use as a small
+ * icon to include in a view to distinguish it from the original icon.
+ *
+ * @param user The target user.
+ * @param density The optional desired density for the badge as per
+ * {@link android.util.DisplayMetrics#densityDpi}. If not provided
+ * the density of the current display is used.
+ * @return the drawable or null if no drawable is required.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @UnsupportedAppUsage
+ public abstract Drawable getUserBadgeForDensity(@NonNull UserHandle user, int density);
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a drawable to use as a small
+ * icon to include in a view to distinguish it from the original icon. This version
+ * doesn't have background protection and should be used over a light background instead of
+ * a badge.
+ *
+ * @param user The target user.
+ * @param density The optional desired density for the badge as per
+ * {@link android.util.DisplayMetrics#densityDpi}. If not provided
+ * the density of the current display is used.
+ * @return the drawable or null if no drawable is required.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @UnsupportedAppUsage
+ public abstract Drawable getUserBadgeForDensityNoBackground(@NonNull UserHandle user,
+ int density);
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a copy of the label with
+ * badging for accessibility services like talkback. E.g. passing in "Email"
+ * and it might return "Work Email" for Email in the work profile.
+ *
+ * @param label The label to change.
+ * @param user The target user.
+ * @return A label that combines the original label and a badge as
+ * determined by the system.
+ */
+ @NonNull
+ public abstract CharSequence getUserBadgedLabel(@NonNull CharSequence label,
+ @NonNull UserHandle user);
+
+ /**
+ * Retrieve text from a package. This is a low-level API used by
+ * the various package manager info structures (such as
+ * {@link ComponentInfo} to implement retrieval of their associated
+ * labels and other text.
+ *
+ * @param packageName The name of the package that this text is coming from.
+ * Cannot be null.
+ * @param resid The resource identifier of the desired text. Cannot be 0.
+ * @param appInfo Overall information about <var>packageName</var>. This
+ * may be null, in which case the application information will be retrieved
+ * for you if needed; if you already have this information around, it can
+ * be much more efficient to supply it here.
+ *
+ * @return Returns a CharSequence holding the requested text. Returns null
+ * if the text could not be found for any reason.
+ */
+ @Nullable
+ public abstract CharSequence getText(@NonNull String packageName, @StringRes int resid,
+ @Nullable ApplicationInfo appInfo);
+
+ /**
+ * Retrieve an XML file from a package. This is a low-level API used to
+ * retrieve XML meta data.
+ *
+ * @param packageName The name of the package that this xml is coming from.
+ * Cannot be null.
+ * @param resid The resource identifier of the desired xml. Cannot be 0.
+ * @param appInfo Overall information about <var>packageName</var>. This
+ * may be null, in which case the application information will be retrieved
+ * for you if needed; if you already have this information around, it can
+ * be much more efficient to supply it here.
+ *
+ * @return Returns an XmlPullParser allowing you to parse out the XML
+ * data. Returns null if the xml resource could not be found for any
+ * reason.
+ */
+ @Nullable
+ public abstract XmlResourceParser getXml(@NonNull String packageName, @XmlRes int resid,
+ @Nullable ApplicationInfo appInfo);
+
+ /**
+ * Return the label to use for this application.
+ *
+ * @return Returns a {@link CharSequence} containing the label associated with
+ * this application, or its name the item does not have a label.
+ * @param info The {@link ApplicationInfo} of the application to get the label of.
+ */
+ @NonNull
+ public abstract CharSequence getApplicationLabel(@NonNull ApplicationInfo info);
+
+ /**
+ * Retrieve the resources associated with an activity. Given the full
+ * name of an activity, retrieves the information about it and calls
+ * getResources() to return its application's resources. If the activity
+ * cannot be found, NameNotFoundException is thrown.
+ *
+ * @param activityName Name of the activity whose resources are to be
+ * retrieved.
+ *
+ * @return Returns the application's Resources.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded.
+ *
+ * @see #getResourcesForApplication(ApplicationInfo)
+ */
+ @NonNull
+ public abstract Resources getResourcesForActivity(@NonNull ComponentName activityName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the resources for an application. Throws NameNotFoundException
+ * if the package is no longer installed.
+ *
+ * @param app Information about the desired application.
+ *
+ * @return Returns the application's Resources.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded (most likely because it was uninstalled).
+ */
+ @NonNull
+ public abstract Resources getResourcesForApplication(@NonNull ApplicationInfo app)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the resources for an application for the provided configuration.
+ *
+ * @param app Information about the desired application.
+ * @param configuration Overridden configuration when loading the Resources
+ *
+ * @return Returns the application's Resources.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded (most likely because it was uninstalled).
+ */
+ @NonNull
+ public Resources getResourcesForApplication(@NonNull ApplicationInfo app, @Nullable
+ Configuration configuration) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve the resources associated with an application. Given the full
+ * package name of an application, retrieves the information about it and
+ * calls getResources() to return its application's resources. If the
+ * appPackageName cannot be found, NameNotFoundException is thrown.
+ *
+ * @param packageName Package name of the application whose resources
+ * are to be retrieved.
+ *
+ * @return Returns the application's Resources.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded.
+ *
+ * @see #getResourcesForApplication(ApplicationInfo)
+ */
+ @NonNull
+ public abstract Resources getResourcesForApplication(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Please don't use this function because it is no longer supported.
+ *
+ * @deprecated Instead of using this function, please use
+ * {@link Context#createContextAsUser(UserHandle, int)} to create the specified user
+ * context, {@link Context#getPackageManager()} to get PackageManager instance for
+ * the specified user, and then
+ * {@link PackageManager#getResourcesForApplication(String)} to get the same
+ * Resources instance.
+ * @see {@link Context#createContextAsUser(android.os.UserHandle, int)}
+ * @see {@link Context#getPackageManager()}
+ * @see {@link android.content.pm.PackageManager#getResourcesForApplication(java.lang.String)}
+ * TODO(b/170852794): mark maxTargetSdk as {@code Build.VERSION_CODES.S}
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170928809,
+ publicAlternatives = "Use {@code Context#createContextAsUser(UserHandle, int)}"
+ + " to create the relevant user context,"
+ + " {@link android.content.Context#getPackageManager()} and"
+ + " {@link android.content.pm.PackageManager#getResourcesForApplication("
+ + "java.lang.String)}"
+ + " instead.")
+ @Deprecated
+ public abstract Resources getResourcesForApplicationAsUser(@NonNull String packageName,
+ @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Retrieve overall information about an application package defined in a
+ * package archive file
+ *
+ * @param archiveFilePath The path to the archive file
+ * @param flags Additional option flags to modify the data returned.
+ * @return A PackageInfo object containing information about the package
+ * archive. If the package could not be parsed, returns null.
+ */
+ @Nullable
+ public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
+ @PackageInfoFlags int flags) {
+ throw new UnsupportedOperationException(
+ "getPackageArchiveInfo() not implemented in subclass");
+ }
+
+ /**
+ * If there is already an application with the given package name installed
+ * on the system for other users, also install it for the calling user.
+ * @hide
+ *
+ * @deprecated use {@link PackageInstaller#installExistingPackage()} instead.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @SystemApi
+ public abstract int installExistingPackage(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * If there is already an application with the given package name installed
+ * on the system for other users, also install it for the calling user.
+ * @hide
+ *
+ * @deprecated use {@link PackageInstaller#installExistingPackage()} instead.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @SystemApi
+ public abstract int installExistingPackage(@NonNull String packageName,
+ @InstallReason int installReason) throws NameNotFoundException;
+
+ /**
+ * If there is already an application with the given package name installed
+ * on the system for other users, also install it for the specified user.
+ * @hide
+ *
+ * @deprecated use {@link PackageInstaller#installExistingPackage()} instead.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_EXISTING_PACKAGES,
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @UnsupportedAppUsage
+ public abstract int installExistingPackageAsUser(@NonNull String packageName,
+ @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Allows a package listening to the
+ * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification
+ * broadcast} to respond to the package manager. The response must include
+ * the {@code verificationCode} which is one of
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}.
+ *
+ * @param id pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationCode either {@link PackageManager#VERIFICATION_ALLOW}
+ * or {@link PackageManager#VERIFICATION_REJECT}.
+ * @throws SecurityException if the caller does not have the
+ * PACKAGE_VERIFICATION_AGENT permission.
+ */
+ public abstract void verifyPendingInstall(int id, int verificationCode);
+
+ /**
+ * Allows a package listening to the
+ * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification
+ * broadcast} to extend the default timeout for a response and declare what
+ * action to perform after the timeout occurs. The response must include
+ * the {@code verificationCodeAtTimeout} which is one of
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}.
+ *
+ * This method may only be called once per package id. Additional calls
+ * will have no effect.
+ *
+ * @param id pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationCodeAtTimeout either
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}. If
+ * {@code verificationCodeAtTimeout} is neither
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}, then
+ * {@code verificationCodeAtTimeout} will default to
+ * {@link PackageManager#VERIFICATION_REJECT}.
+ * @param millisecondsToDelay the amount of time requested for the timeout.
+ * Must be positive and less than
+ * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. If
+ * {@code millisecondsToDelay} is out of bounds,
+ * {@code millisecondsToDelay} will be set to the closest in
+ * bounds value; namely, 0 or
+ * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}.
+ * @throws SecurityException if the caller does not have the
+ * PACKAGE_VERIFICATION_AGENT permission.
+ */
+ public abstract void extendVerificationTimeout(int id,
+ int verificationCodeAtTimeout, long millisecondsToDelay);
+
+ /**
+ * Allows a package listening to the
+ * {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION} intent filter verification
+ * broadcast to respond to the package manager. The response must include
+ * the {@code verificationCode} which is one of
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} or
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ *
+ * @param verificationId pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationCode either {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS}
+ * or {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ * @param failedDomains a list of failed domains if the verificationCode is
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}, otherwise null;
+ * @throws SecurityException if the caller does not have the
+ * INTENT_FILTER_VERIFICATION_AGENT permission.
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT)
+ public abstract void verifyIntentFilter(int verificationId, int verificationCode,
+ @NonNull List<String> failedDomains);
+
+ /**
+ * Get the status of a Domain Verification Result for an IntentFilter. This is
+ * related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and
+ * {@link android.content.IntentFilter#getAutoVerify()}
+ *
+ * This is used by the ResolverActivity to change the status depending on what the User select
+ * in the Disambiguation Dialog and also used by the Settings App for changing the default App
+ * for a domain.
+ *
+ * @param packageName The package name of the Activity associated with the IntentFilter.
+ * @param userId The user id.
+ *
+ * @return The status to set to. This can be
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED}
+ *
+ * @deprecated Use {@link DomainVerificationManager} APIs.
+ * @hide
+ */
+ @Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public abstract int getIntentVerificationStatusAsUser(@NonNull String packageName,
+ @UserIdInt int userId);
+
+ /**
+ * Allow to change the status of a Intent Verification status for all IntentFilter of an App.
+ * This is related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and
+ * {@link android.content.IntentFilter#getAutoVerify()}
+ *
+ * This is used by the ResolverActivity to change the status depending on what the User select
+ * in the Disambiguation Dialog and also used by the Settings App for changing the default App
+ * for a domain.
+ *
+ * @param packageName The package name of the Activity associated with the IntentFilter.
+ * @param status The status to set to. This can be
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER}
+ * @param userId The user id.
+ *
+ * @return true if the status has been set. False otherwise.
+ *
+ * @deprecated This API represents a very dangerous behavior where Settings or a system app with
+ * the right permissions can force an application to be verified for all of its declared
+ * domains. This has been removed to prevent unintended usage, and no longer does anything,
+ * always returning false. If a caller truly wishes to grant <i></i>every</i> declared web
+ * domain to an application, use
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
+ * passing in all of the domains returned inside
+ * {@link DomainVerificationManager#getDomainVerificationUserState(String)}.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+ public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String packageName,
+ int status, @UserIdInt int userId);
+
+ /**
+ * Get the list of IntentFilterVerificationInfo for a specific package and User.
+ *
+ * @param packageName the package name. When this parameter is set to a non null value,
+ * the results will be filtered by the package name provided.
+ * Otherwise, there will be no filtering and it will return a list
+ * corresponding for all packages
+ *
+ * @return a list of IntentFilterVerificationInfo for a specific package.
+ *
+ * @deprecated Use {@link DomainVerificationManager} instead.
+ * @hide
+ */
+ @Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @SystemApi
+ public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+ @NonNull String packageName);
+
+ /**
+ * Get the list of IntentFilter for a specific package.
+ *
+ * @param packageName the package name. This parameter is set to a non null value,
+ * the list will contain all the IntentFilter for that package.
+ * Otherwise, the list will be empty.
+ *
+ * @return a list of IntentFilter for a specific package.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @SystemApi
+ public abstract List<IntentFilter> getAllIntentFilters(@NonNull String packageName);
+
+ /**
+ * Get the default Browser package name for a specific user.
+ *
+ * @param userId The user id.
+ *
+ * @return the package name of the default Browser for the specified user. If the user id passed
+ * is -1 (all users) it will return a null value.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public abstract String getDefaultBrowserPackageNameAsUser(@UserIdInt int userId);
+
+ /**
+ * Set the default Browser package name for a specific user.
+ *
+ * @param packageName The package name of the default Browser.
+ * @param userId The user id.
+ *
+ * @return true if the default Browser for the specified user has been set,
+ * otherwise return false. If the user id passed is -1 (all users) this call will not
+ * do anything and just return false.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.SET_PREFERRED_APPLICATIONS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ public abstract boolean setDefaultBrowserPackageNameAsUser(@Nullable String packageName,
+ @UserIdInt int userId);
+
+ /**
+ * Change the installer associated with a given package. There are limitations
+ * on how the installer package can be changed; in particular:
+ * <ul>
+ * <li> A SecurityException will be thrown if <var>installerPackageName</var>
+ * is not signed with the same certificate as the calling application.
+ * <li> A SecurityException will be thrown if <var>targetPackage</var> already
+ * has an installer package, and that installer package is not signed with
+ * the same certificate as the calling application.
+ * </ul>
+ *
+ * @param targetPackage The installed package whose installer will be changed.
+ * @param installerPackageName The package name of the new installer. May be
+ * null to clear the association.
+ */
+ public abstract void setInstallerPackageName(@NonNull String targetPackage,
+ @Nullable String installerPackageName);
+
+ /** @hide */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public abstract void setUpdateAvailable(@NonNull String packageName, boolean updateAvaialble);
+
+ /**
+ * Attempts to delete a package. Since this may take a little while, the
+ * result will be posted back to the given observer. A deletion will fail if
+ * the calling context lacks the
+ * {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
+ * named package cannot be found, or if the named package is a system
+ * package.
+ *
+ * @param packageName The name of the package to delete
+ * @param observer An observer callback to get notified when the package
+ * deletion is complete.
+ * {@link android.content.pm.IPackageDeleteObserver#packageDeleted}
+ * will be called when that happens. observer may be null to
+ * indicate that no callback is desired.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
+ @UnsupportedAppUsage
+ public abstract void deletePackage(@NonNull String packageName,
+ @Nullable IPackageDeleteObserver observer, @DeleteFlags int flags);
+
+ /**
+ * Attempts to delete a package. Since this may take a little while, the
+ * result will be posted back to the given observer. A deletion will fail if
+ * the named package cannot be found, or if the named package is a system
+ * package.
+ *
+ * @param packageName The name of the package to delete
+ * @param observer An observer callback to get notified when the package
+ * deletion is complete.
+ * {@link android.content.pm.IPackageDeleteObserver#packageDeleted}
+ * will be called when that happens. observer may be null to
+ * indicate that no callback is desired.
+ * @param userId The user Id
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ @UnsupportedAppUsage
+ public abstract void deletePackageAsUser(@NonNull String packageName,
+ @Nullable IPackageDeleteObserver observer, @DeleteFlags int flags,
+ @UserIdInt int userId);
+
+ /**
+ * Retrieve the package name of the application that installed a package. This identifies
+ * 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
+ *
+ * @deprecated use {@link #getInstallSourceInfo(String)} instead
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @Nullable
+ public abstract String getInstallerPackageName(@NonNull String packageName);
+
+ /**
+ * Retrieves information about how a package was installed or updated.
+ * <p>
+ * If the calling application does not hold the INSTALL_PACKAGES permission then
+ * the result will always return {@code null} from
+ * {@link InstallSourceInfo#getOriginatingPackageName()}.
+ * <p>
+ * If the package that requested the install has been uninstalled, then information about it
+ * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and
+ * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is
+ * requesting its own install information and is not an instant app.
+ *
+ * @param packageName The name of the package to query
+ * @throws NameNotFoundException if the given package name is not installed
+ */
+ @NonNull
+ public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException("getInstallSourceInfo not implemented");
+ }
+
+ /**
+ * Attempts to clear the user data directory of an application.
+ * Since this may take a little while, the result will
+ * be posted back to the given observer. A deletion will fail if the
+ * named package cannot be found, or if the named package is a "system package".
+ *
+ * @param packageName The name of the package
+ * @param observer An observer callback to get notified when the operation is finished
+ * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)}
+ * will be called when that happens. observer may be null to indicate that
+ * no callback is desired.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void clearApplicationUserData(@NonNull String packageName,
+ @Nullable IPackageDataObserver observer);
+ /**
+ * Attempts to delete the cache files associated with an application.
+ * Since this may take a little while, the result will
+ * be posted back to the given observer. A deletion will fail if the calling context
+ * lacks the {@link android.Manifest.permission#DELETE_CACHE_FILES} permission, if the
+ * named package cannot be found, or if the named package is a "system package".
+ *
+ * @param packageName The name of the package to delete
+ * @param observer An observer callback to get notified when the cache file deletion
+ * is complete.
+ * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)}
+ * will be called when that happens. observer may be null to indicate that
+ * no callback is desired.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void deleteApplicationCacheFiles(@NonNull String packageName,
+ @Nullable IPackageDataObserver observer);
+
+ /**
+ * Attempts to delete the cache files associated with an application for a given user. Since
+ * this may take a little while, the result will be posted back to the given observer. A
+ * deletion will fail if the calling context lacks the
+ * {@link android.Manifest.permission#DELETE_CACHE_FILES} permission, if the named package
+ * cannot be found, or if the named package is a "system package". If {@code userId} does not
+ * belong to the calling user, the caller must have
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ *
+ * @param packageName The name of the package to delete
+ * @param userId the user for which the cache files needs to be deleted
+ * @param observer An observer callback to get notified when the cache file deletion is
+ * complete.
+ * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)}
+ * will be called when that happens. observer may be null to indicate that no
+ * callback is desired.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void deleteApplicationCacheFilesAsUser(@NonNull String packageName,
+ @UserIdInt int userId, @Nullable IPackageDataObserver observer);
+
+ /**
+ * Free storage by deleting LRU sorted list of cache files across
+ * all applications. If the currently available free storage
+ * on the device is greater than or equal to the requested
+ * free storage, no cache files are cleared. If the currently
+ * available storage on the device is less than the requested
+ * free storage, some or all of the cache files across
+ * all applications are deleted (based on last accessed time)
+ * to increase the free storage space on the device to
+ * the requested value. There is no guarantee that clearing all
+ * the cache files from all applications will clear up
+ * enough storage to achieve the desired value.
+ * @param freeStorageSize The number of bytes of storage to be
+ * freed by the system. Say if freeStorageSize is XX,
+ * and the current free storage is YY,
+ * if XX is less than YY, just return. if not free XX-YY number
+ * of bytes if possible.
+ * @param observer call back used to notify when
+ * the operation is completed
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void freeStorageAndNotify(long freeStorageSize,
+ @Nullable IPackageDataObserver observer) {
+ freeStorageAndNotify(null, freeStorageSize, observer);
+ }
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize,
+ @Nullable IPackageDataObserver observer);
+
+ /**
+ * Free storage by deleting LRU sorted list of cache files across
+ * all applications. If the currently available free storage
+ * on the device is greater than or equal to the requested
+ * free storage, no cache files are cleared. If the currently
+ * available storage on the device is less than the requested
+ * free storage, some or all of the cache files across
+ * all applications are deleted (based on last accessed time)
+ * to increase the free storage space on the device to
+ * the requested value. There is no guarantee that clearing all
+ * the cache files from all applications will clear up
+ * enough storage to achieve the desired value.
+ * @param freeStorageSize The number of bytes of storage to be
+ * freed by the system. Say if freeStorageSize is XX,
+ * and the current free storage is YY,
+ * if XX is less than YY, just return. if not free XX-YY number
+ * of bytes if possible.
+ * @param pi IntentSender call back used to
+ * notify when the operation is completed.May be null
+ * to indicate that no call back is desired.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void freeStorage(long freeStorageSize, @Nullable IntentSender pi) {
+ freeStorage(null, freeStorageSize, pi);
+ }
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void freeStorage(@Nullable String volumeUuid, long freeStorageSize,
+ @Nullable IntentSender pi);
+
+ /**
+ * Retrieve the size information for a package.
+ * Since this may take a little while, the result will
+ * be posted back to the given observer. The calling context
+ * should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission.
+ *
+ * @param packageName The name of the package whose size information is to be retrieved
+ * @param userId The user whose size information should be retrieved.
+ * @param observer An observer callback to get notified when the operation
+ * is complete.
+ * {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)}
+ * The observer's callback is invoked with a PackageStats object(containing the
+ * code, data and cache sizes of the package) and a boolean value representing
+ * the status of the operation. observer may be null to indicate that
+ * no callback is desired.
+ *
+ * @deprecated use {@link StorageStatsManager} instead.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @UnsupportedAppUsage
+ public abstract void getPackageSizeInfoAsUser(@NonNull String packageName,
+ @UserIdInt int userId, @Nullable IPackageStatsObserver observer);
+
+ /**
+ * Like {@link #getPackageSizeInfoAsUser(String, int, IPackageStatsObserver)}, but
+ * returns the size for the calling user.
+ *
+ * @deprecated use {@link StorageStatsManager} instead.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void getPackageSizeInfo(@NonNull String packageName, IPackageStatsObserver observer) {
+ getPackageSizeInfoAsUser(packageName, getUserId(), observer);
+ }
+
+ /**
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ public abstract void addPackageToPreferred(@NonNull String packageName);
+
+ /**
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ public abstract void removePackageFromPreferred(@NonNull String packageName);
+
+ /**
+ * Retrieve the list of all currently configured preferred packages. The
+ * first package on the list is the most preferred, the last is the least
+ * preferred.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return A List of PackageInfo objects, one for each preferred
+ * application, in order of preference.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @NonNull
+ @Deprecated
+ public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags);
+
+ /**
+ * Add a new preferred activity mapping to the system. This will be used
+ * to automatically select the given activity component when
+ * {@link Context#startActivity(Intent) Context.startActivity()} finds
+ * multiple matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be
+ * made preferred.
+ * @param match The IntentFilter match category that this preference
+ * applies to.
+ * @param set The set of activities that the user was picking from when
+ * this preference was made.
+ * @param activity The component name of the activity that is to be
+ * preferred.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ public abstract void addPreferredActivity(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity);
+
+ /**
+ * Same as {@link #addPreferredActivity(IntentFilter, int,
+ ComponentName[], ComponentName)}, but with a specific userId to apply the preference
+ to.
+ * @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void addPreferredActivityAsUser(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity, @UserIdInt int userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Replaces an existing preferred activity mapping to the system, and if that were not present
+ * adds a new preferred activity. This will be used
+ * to automatically select the given activity component when
+ * {@link Context#startActivity(Intent) Context.startActivity()} finds
+ * multiple matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be
+ * made preferred.
+ * @param match The IntentFilter match category that this preference
+ * applies to.
+ * @param set The set of activities that the user was picking from when
+ * this preference was made.
+ * @param activity The component name of the activity that is to be
+ * preferred.
+ *
+ * @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Deprecated
+ @UnsupportedAppUsage
+ public abstract void replacePreferredActivity(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity);
+
+ /**
+ * Replaces an existing preferred activity mapping to the system, and if that were not present
+ * adds a new preferred activity. This will be used to automatically select the given activity
+ * component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple
+ * matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be made preferred.
+ * @param match The IntentFilter match category that this preference applies to. Should be a
+ * combination of {@link IntentFilter#MATCH_CATEGORY_MASK} and
+ * {@link IntentFilter#MATCH_ADJUSTMENT_MASK}).
+ * @param set The set of activities that the user was picking from when this preference was
+ * made.
+ * @param activity The component name of the activity that is to be preferred.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void replacePreferredActivity(@NonNull IntentFilter filter, int match,
+ @NonNull List<ComponentName> set, @NonNull ComponentName activity) {
+ replacePreferredActivity(filter, match, set.toArray(new ComponentName[0]), activity);
+ }
+
+ /**
+ * @hide
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void replacePreferredActivityAsUser(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity, @UserIdInt int userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Remove all preferred activity mappings, previously added with
+ * {@link #addPreferredActivity}, from the
+ * system whose activities are implemented in the given package name.
+ * An application can only clear its own package(s).
+ *
+ * @param packageName The name of the package whose preferred activity
+ * mappings are to be removed.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ public abstract void clearPackagePreferredActivities(@NonNull String packageName);
+
+ /**
+ * Same as {@link #addPreferredActivity(IntentFilter, int, ComponentName[], ComponentName)},
+ * but removes all existing entries that match this filter.
+ * @hide
+ */
+ public void addUniquePreferredActivity(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity) {
+ throw new UnsupportedOperationException(
+ "addUniquePreferredActivity not implemented in subclass");
+ }
+
+ /**
+ * Retrieve all preferred activities, previously added with
+ * {@link #addPreferredActivity}, that are
+ * currently registered with the system.
+ *
+ * @param outFilters A required list in which to place the filters of all of the
+ * preferred activities.
+ * @param outActivities A required list in which to place the component names of
+ * all of the preferred activities.
+ * @param packageName An optional package in which you would like to limit
+ * the list. If null, all activities will be returned; if non-null, only
+ * those activities in the given package are returned.
+ *
+ * @return Returns the total number of registered preferred activities
+ * (the number of distinct IntentFilter records, not the number of unique
+ * activity components) that were found.
+ *
+ * @deprecated This function no longer does anything. It is the platform's
+ * responsibility to assign preferred activities and this cannot be modified
+ * directly. To determine the activities resolved by the platform, use
+ * {@link #resolveActivity} or {@link #queryIntentActivities}. To configure
+ * an app to be responsible for a particular role and to check current role
+ * holders, see {@link android.app.role.RoleManager}.
+ */
+ @Deprecated
+ public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
+ @NonNull List<ComponentName> outActivities, @Nullable String packageName);
+
+ /**
+ * Ask for the set of available 'home' activities and the current explicit
+ * default, if any.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @UnsupportedAppUsage
+ public abstract ComponentName getHomeActivities(@NonNull List<ResolveInfo> outActivities);
+
+ /**
+ * Set the enabled setting for a package component (activity, receiver, service, provider).
+ * This setting will override any enabled state which may have been set by the component in its
+ * manifest.
+ *
+ * @param componentName The component to enable
+ * @param newState The new enabled state for the component.
+ * @param flags Optional behavior flags.
+ */
+ @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
+ conditional = true)
+ public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName,
+ @EnabledState int newState, @EnabledFlags int flags);
+
+ /**
+ * Return the enabled setting for a package component (activity,
+ * receiver, service, provider). This returns the last value set by
+ * {@link #setComponentEnabledSetting(ComponentName, int, int)}; in most
+ * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since
+ * the value originally specified in the manifest has not been modified.
+ *
+ * @param componentName The component to retrieve.
+ * @return Returns the current enabled state for the component.
+ */
+ public abstract @EnabledState int getComponentEnabledSetting(
+ @NonNull ComponentName componentName);
+
+ /**
+ * Set whether a synthetic app details activity will be generated if the app has no enabled
+ * launcher activity. Disabling this allows the app to have no launcher icon.
+ *
+ * @param packageName The package name of the app
+ * @param enabled The new enabled state for the synthetic app details activity.
+ *
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
+ conditional = true)
+ @SystemApi
+ public void setSyntheticAppDetailsActivityEnabled(@NonNull String packageName,
+ boolean enabled) {
+ throw new UnsupportedOperationException(
+ "setSyntheticAppDetailsActivityEnabled not implemented");
+ }
+
+
+ /**
+ * Return whether a synthetic app details activity will be generated if the app has no enabled
+ * launcher activity.
+ *
+ * @param packageName The package name of the app
+ * @return Returns the enabled state for the synthetic app details activity.
+ *
+ *
+ */
+ public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String packageName) {
+ throw new UnsupportedOperationException(
+ "getSyntheticAppDetailsActivityEnabled not implemented");
+ }
+
+ /**
+ * Set the enabled setting for an application
+ * This setting will override any enabled state which may have been set by the application in
+ * its manifest. It also overrides the enabled state set in the manifest for any of the
+ * application's components. It does not override any enabled state set by
+ * {@link #setComponentEnabledSetting} for any of the application's components.
+ *
+ * @param packageName The package name of the application to enable
+ * @param newState The new enabled state for the application.
+ * @param flags Optional behavior flags.
+ */
+ @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
+ conditional = true)
+ public abstract void setApplicationEnabledSetting(@NonNull String packageName,
+ @EnabledState int newState, @EnabledFlags int flags);
+
+ /**
+ * Return the enabled setting for an application. This returns
+ * the last value set by
+ * {@link #setApplicationEnabledSetting(String, int, int)}; in most
+ * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since
+ * the value originally specified in the manifest has not been modified.
+ *
+ * @param packageName The package name of the application to retrieve.
+ * @return Returns the current enabled state for the application.
+ * @throws IllegalArgumentException if the named package does not exist.
+ */
+ public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName);
+
+ /**
+ * Flush the package restrictions for a given user to disk. This forces the package restrictions
+ * like component and package enabled settings to be written to disk and avoids the delay that
+ * is otherwise present when changing those settings.
+ *
+ * @param userId Ther userId of the user whose restrictions are to be flushed.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void flushPackageRestrictionsAsUser(@UserIdInt int userId);
+
+ /**
+ * Puts the package in a hidden state, which is almost like an uninstalled state,
+ * making the package unavailable, but it doesn't remove the data or the actual
+ * package file. Application can be unhidden by either resetting the hidden state
+ * or by installing it, such as with {@link #installExistingPackage(String)}
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean setApplicationHiddenSettingAsUser(@NonNull String packageName,
+ boolean hidden, @NonNull UserHandle userHandle);
+
+ /**
+ * Returns the hidden state of a package.
+ * @see #setApplicationHiddenSettingAsUser(String, boolean, UserHandle)
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean getApplicationHiddenSettingAsUser(@NonNull String packageName,
+ @NonNull UserHandle userHandle);
+
+ /**
+ * Sets the state of a system app.
+ *
+ * This method can be used to change a system app's hidden-until-installed state (via
+ * {@link #SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN} and
+ * {@link #SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE} or its installation state (via
+ * {@link #SYSTEM_APP_STATE_INSTALLED} and {@link #SYSTEM_APP_STATE_UNINSTALLED}.
+ *
+ * This API may only be called from {@link android.os.Process#SYSTEM_UID} or
+ * {@link android.os.Process#PHONE_UID}.
+ *
+ * @param packageName Package name of the app.
+ * @param state State of the app.
+ * @hide
+ */
+ @SystemApi
+ public void setSystemAppState(@NonNull String packageName, @SystemAppState int state) {
+ throw new RuntimeException("Not implemented. Must override in a subclass");
+ }
+
+ /**
+ * Return whether the device has been booted into safe mode.
+ */
+ public abstract boolean isSafeMode();
+
+ /**
+ * Adds a listener for permission changes for installed packages.
+ *
+ * @param listener The listener to add.
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
+ public abstract void addOnPermissionsChangeListener(
+ @NonNull OnPermissionsChangedListener listener);
+
+ /**
+ * Remvoes a listener for permission changes for installed packages.
+ *
+ * @param listener The listener to remove.
+ *
+ * @hide
+ */
+ //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
+ public abstract void removeOnPermissionsChangeListener(
+ @NonNull OnPermissionsChangedListener listener);
+
+ /**
+ * Return the {@link KeySet} associated with the String alias for this
+ * application.
+ *
+ * @param alias The alias for a given {@link KeySet} as defined in the
+ * application's AndroidManifest.xml.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias);
+
+ /** Return the signing {@link KeySet} for this application.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract KeySet getSigningKeySet(@NonNull String packageName);
+
+ /**
+ * Return whether the package denoted by packageName has been signed by all
+ * of the keys specified by the {@link KeySet} ks. This will return true if
+ * the package has been signed by additional keys (a superset) as well.
+ * Compare to {@link #isSignedByExactly(String packageName, KeySet ks)}.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean isSignedBy(@NonNull String packageName, @NonNull KeySet ks);
+
+ /**
+ * Return whether the package denoted by packageName has been signed by all
+ * of, and only, the keys specified by the {@link KeySet} ks. Compare to
+ * {@link #isSignedBy(String packageName, KeySet ks)}.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean isSignedByExactly(@NonNull String packageName, @NonNull KeySet ks);
+
+ /**
+ * Flag to denote no restrictions. This should be used to clear any restrictions that may have
+ * been previously set for the package.
+ * @hide
+ * @see #setDistractingPackageRestrictions(String[], int)
+ */
+ @SystemApi
+ public static final int RESTRICTION_NONE = 0x0;
+
+ /**
+ * Flag to denote that a package should be hidden from any suggestions to the user.
+ * @hide
+ * @see #setDistractingPackageRestrictions(String[], int)
+ */
+ @SystemApi
+ public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 0x00000001;
+
+ /**
+ * Flag to denote that a package's notifications should be hidden.
+ * @hide
+ * @see #setDistractingPackageRestrictions(String[], int)
+ */
+ @SystemApi
+ public static final int RESTRICTION_HIDE_NOTIFICATIONS = 0x00000002;
+
+ /**
+ * Restriction flags to set on a package that is considered as distracting to the user.
+ * These should help the user to restrict their usage of these apps.
+ *
+ * @see #setDistractingPackageRestrictions(String[], int)
+ * @hide
+ */
+ @IntDef(flag = true, prefix = {"RESTRICTION_"}, value = {
+ RESTRICTION_NONE,
+ RESTRICTION_HIDE_FROM_SUGGESTIONS,
+ RESTRICTION_HIDE_NOTIFICATIONS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DistractionRestriction {}
+
+ /**
+ * Mark or unmark the given packages as distracting to the user.
+ * These packages can have certain restrictions set that should discourage the user to launch
+ * them often. For example, notifications from such an app can be hidden, or the app can be
+ * removed from launcher suggestions, so the user is able to restrict their use of these apps.
+ *
+ * <p>The caller must hold {@link android.Manifest.permission#SUSPEND_APPS} to use this API.
+ *
+ * @param packages Packages to mark as distracting.
+ * @param restrictionFlags Any combination of restrictions to impose on the given packages.
+ * {@link #RESTRICTION_NONE} can be used to clear any existing
+ * restrictions.
+ * @return A list of packages that could not have the {@code restrictionFlags} set. The system
+ * may prevent restricting critical packages to preserve normal device function.
+ *
+ * @hide
+ * @see #RESTRICTION_NONE
+ * @see #RESTRICTION_HIDE_FROM_SUGGESTIONS
+ * @see #RESTRICTION_HIDE_NOTIFICATIONS
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SUSPEND_APPS)
+ @NonNull
+ public String[] setDistractingPackageRestrictions(@NonNull String[] packages,
+ @DistractionRestriction int restrictionFlags) {
+ throw new UnsupportedOperationException(
+ "setDistractingPackageRestrictions not implemented");
+ }
+
+ /**
+ * 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's notifications
+ * will be hidden, any of its started activities will be stopped and it will not be able to
+ * show toasts or system alert windows or ring the device.
+ *
+ * <p>When the user tries to launch a suspended app, a system dialog with the given
+ * {@code dialogMessage} will be shown instead. Since the message is supplied to the system as
+ * a {@link String}, the caller needs to take care of localization as needed.
+ * The dialog message can optionally contain a placeholder for the name of the suspended app.
+ * The system uses {@link String#format(Locale, String, Object...) String.format} to insert the
+ * app name into the message, so an example format string could be {@code "The app %1$s is
+ * currently suspended"}. This makes it easier for callers to provide a single message which
+ * works for all the packages being suspended in a single call.
+ *
+ * <p>The package must already be installed. If the package is uninstalled while 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>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API.
+ *
+ * @param packageNames The names of the packages to set the suspended status.
+ * @param suspended If set to {@code true}, the packages will be suspended, if set to
+ * {@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 could not be set as
+ * requested in this method. Returns {@code null} if {@code packageNames} was {@code null}.
+ *
+ * @deprecated use {@link #setPackagesSuspended(String[], boolean, PersistableBundle,
+ * PersistableBundle, android.content.pm.SuspendDialogInfo)} instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+ @Nullable
+ public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
+ @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
+ @Nullable String dialogMessage) {
+ throw new UnsupportedOperationException("setPackagesSuspended not implemented");
+ }
+
+ /**
+ * Puts the given packages in a suspended state, where attempts at starting activities are
+ * denied.
+ *
+ * <p>The suspended application's notifications and all of its windows will be hidden, any
+ * of its started activities will be stopped and it won't be able to ring the device.
+ * It doesn't remove the data or the actual package file.
+ *
+ * <p>When the user tries to launch a suspended app, a system dialog alerting them that the app
+ * is suspended will be shown instead.
+ * The caller can optionally customize the dialog by passing a {@link SuspendDialogInfo} object
+ * to this API. This dialog will have a button that starts the
+ * {@link Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} intent if the suspending app declares an
+ * activity which handles this action.
+ *
+ * <p>The packages being suspended must already be installed. If a package is uninstalled, it
+ * will no longer be suspended.
+ *
+ * <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>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API.
+ *
+ * @param packageNames The names of the packages to set the suspended status.
+ * @param suspended If set to {@code true}, the packages will be suspended, if set to
+ * {@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 dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
+ * should be shown to the user when they try to launch a suspended app.
+ * Ignored if {@code suspended} is false.
+ *
+ * @return an array of package names for which the suspended status could not be set as
+ * requested in this method. Returns {@code null} if {@code packageNames} was {@code null}.
+ *
+ * @see #isPackageSuspended
+ * @see SuspendDialogInfo
+ * @see SuspendDialogInfo.Builder
+ * @see Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+ @Nullable
+ public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
+ @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
+ @Nullable SuspendDialogInfo dialogInfo) {
+ throw new UnsupportedOperationException("setPackagesSuspended not implemented");
+ }
+
+ /**
+ * Returns any packages in a given set of packages that cannot be suspended via a call to {@link
+ * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical
+ * packages to keep the device in a functioning state, e.g. the default dialer and launcher.
+ * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this API.
+ *
+ * <p>
+ * Note that this set of critical packages can change with time, so even though a package name
+ * was not returned by this call, it does not guarantee that a subsequent call to
+ * {@link #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo) setPackagesSuspended} for that package will succeed, especially if
+ * significant time elapsed between the two calls.
+ *
+ * @param packageNames The packages to check.
+ * @return A list of packages that can not be currently suspended by the system.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+ @NonNull
+ public String[] getUnsuspendablePackages(@NonNull String[] packageNames) {
+ throw new UnsupportedOperationException("getUnsuspendablePackages not implemented");
+ }
+
+ /**
+ * @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
+ * suspended.
+ * @throws IllegalArgumentException if the package was not found.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean isPackageSuspendedForUser(@NonNull String packageName, int userId);
+
+ /**
+ * Query if an app is currently suspended.
+ *
+ * @return {@code true} if the given package is suspended, {@code false} otherwise
+ * @throws NameNotFoundException if the package could not be found.
+ *
+ * @see #isPackageSuspended()
+ */
+ public boolean isPackageSuspended(@NonNull String packageName) throws NameNotFoundException {
+ 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 play audio.
+ * 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. Apps are expected to
+ * use these to gracefully deal with transitions to and from this state.
+ *
+ * @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");
+ }
+
+ /**
+ * Returns a {@link Bundle} of extras that was meant to be sent to the calling app when it was
+ * suspended. An app with the permission {@code android.permission.SUSPEND_APPS} can supply this
+ * to the system at the time of suspending an app.
+ *
+ * <p>This is the same {@link Bundle} that is sent along with the broadcast
+ * {@link Intent#ACTION_MY_PACKAGE_SUSPENDED}, whenever the app is suspended. The contents of
+ * this {@link Bundle} are a contract between the suspended app and the suspending app.
+ *
+ * <p>Note: These extras are optional, so if no extras were supplied to the system, this method
+ * will return {@code null}, even when the calling app has been suspended.
+ *
+ * @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
+ * @see Intent#EXTRA_SUSPENDED_PACKAGE_EXTRAS
+ */
+ 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>
+ * This hint can only be set by the app which installed this package, as
+ * determined by {@link #getInstallerPackageName(String)}.
+ *
+ * @param packageName the package to change the category hint for.
+ * @param categoryHint the category hint to set.
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract void setApplicationCategoryHint(@NonNull String packageName,
+ @ApplicationInfo.Category int categoryHint);
+
+ /** {@hide} */
+ public static boolean isMoveStatusFinished(int status) {
+ return (status < 0 || status > 100);
+ }
+
+ /** {@hide} */
+ public static abstract class MoveCallback {
+ public void onCreated(int moveId, Bundle extras) {}
+ public abstract void onStatusChanged(int moveId, int status, long estMillis);
+ }
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract int getMoveStatus(int moveId);
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void registerMoveCallback(@NonNull MoveCallback callback,
+ @NonNull Handler handler);
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void unregisterMoveCallback(@NonNull MoveCallback callback);
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public abstract int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol);
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public abstract @Nullable VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app);
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public abstract List<VolumeInfo> getPackageCandidateVolumes(
+ @NonNull ApplicationInfo app);
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract int movePrimaryStorage(@NonNull VolumeInfo vol);
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume();
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ public abstract @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes();
+
+ /**
+ * Returns the device identity that verifiers can use to associate their scheme to a particular
+ * device. This should not be used by anything other than a package verifier.
+ *
+ * @return identity that uniquely identifies current device
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
+
+ /**
+ * Returns true if the device is upgrading, such as first boot after OTA.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean isUpgrade();
+
+ /**
+ * Returns true if the device is upgrading, such as first boot after OTA.
+ */
+ public boolean isDeviceUpgrading() {
+ return false;
+ }
+
+ /**
+ * Return interface that offers the ability to install, upgrade, and remove
+ * applications on the device.
+ */
+ public abstract @NonNull PackageInstaller getPackageInstaller();
+
+ /**
+ * Adds a {@code CrossProfileIntentFilter}. After calling this method all
+ * intents sent from the user with id sourceUserId can also be be resolved
+ * by activities in the user with id targetUserId if they match the
+ * specified intent filter.
+ *
+ * @param filter The {@link IntentFilter} the intent has to match
+ * @param sourceUserId The source user id.
+ * @param targetUserId The target user id.
+ * @param flags The possible values are {@link #SKIP_CURRENT_PROFILE} and
+ * {@link #ONLY_IF_NO_MATCH_FOUND}.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void addCrossProfileIntentFilter(@NonNull IntentFilter filter,
+ @UserIdInt int sourceUserId, @UserIdInt int targetUserId, int flags);
+
+ /**
+ * Clearing {@code CrossProfileIntentFilter}s which have the specified user
+ * as their source, and have been set by the app calling this method.
+ *
+ * @param sourceUserId The source user id.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract void clearCrossProfileIntentFilters(@UserIdInt int sourceUserId);
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo,
+ @Nullable ApplicationInfo appInfo);
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo,
+ @Nullable ApplicationInfo appInfo);
+
+ /** {@hide} */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @UnsupportedAppUsage
+ public abstract boolean isPackageAvailable(@NonNull String packageName);
+
+ /** {@hide} */
+ @NonNull
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static String installStatusToString(int status, @Nullable String msg) {
+ final String str = installStatusToString(status);
+ if (msg != null) {
+ return str + ": " + msg;
+ } else {
+ return str;
+ }
+ }
+
+ /** {@hide} */
+ @NonNull
+ @UnsupportedAppUsage
+ public static String installStatusToString(int status) {
+ switch (status) {
+ case INSTALL_SUCCEEDED: return "INSTALL_SUCCEEDED";
+ case INSTALL_FAILED_ALREADY_EXISTS: return "INSTALL_FAILED_ALREADY_EXISTS";
+ case INSTALL_FAILED_INVALID_APK: return "INSTALL_FAILED_INVALID_APK";
+ case INSTALL_FAILED_INVALID_URI: return "INSTALL_FAILED_INVALID_URI";
+ case INSTALL_FAILED_INSUFFICIENT_STORAGE: return "INSTALL_FAILED_INSUFFICIENT_STORAGE";
+ case INSTALL_FAILED_DUPLICATE_PACKAGE: return "INSTALL_FAILED_DUPLICATE_PACKAGE";
+ case INSTALL_FAILED_NO_SHARED_USER: return "INSTALL_FAILED_NO_SHARED_USER";
+ case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return "INSTALL_FAILED_UPDATE_INCOMPATIBLE";
+ case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return "INSTALL_FAILED_SHARED_USER_INCOMPATIBLE";
+ case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return "INSTALL_FAILED_MISSING_SHARED_LIBRARY";
+ case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return "INSTALL_FAILED_REPLACE_COULDNT_DELETE";
+ case INSTALL_FAILED_DEXOPT: return "INSTALL_FAILED_DEXOPT";
+ case INSTALL_FAILED_OLDER_SDK: return "INSTALL_FAILED_OLDER_SDK";
+ case INSTALL_FAILED_CONFLICTING_PROVIDER: return "INSTALL_FAILED_CONFLICTING_PROVIDER";
+ case INSTALL_FAILED_NEWER_SDK: return "INSTALL_FAILED_NEWER_SDK";
+ case INSTALL_FAILED_TEST_ONLY: return "INSTALL_FAILED_TEST_ONLY";
+ case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return "INSTALL_FAILED_CPU_ABI_INCOMPATIBLE";
+ case INSTALL_FAILED_MISSING_FEATURE: return "INSTALL_FAILED_MISSING_FEATURE";
+ case INSTALL_FAILED_CONTAINER_ERROR: return "INSTALL_FAILED_CONTAINER_ERROR";
+ case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return "INSTALL_FAILED_INVALID_INSTALL_LOCATION";
+ case INSTALL_FAILED_MEDIA_UNAVAILABLE: return "INSTALL_FAILED_MEDIA_UNAVAILABLE";
+ case INSTALL_FAILED_VERIFICATION_TIMEOUT: return "INSTALL_FAILED_VERIFICATION_TIMEOUT";
+ case INSTALL_FAILED_VERIFICATION_FAILURE: return "INSTALL_FAILED_VERIFICATION_FAILURE";
+ case INSTALL_FAILED_PACKAGE_CHANGED: return "INSTALL_FAILED_PACKAGE_CHANGED";
+ case INSTALL_FAILED_UID_CHANGED: return "INSTALL_FAILED_UID_CHANGED";
+ case INSTALL_FAILED_VERSION_DOWNGRADE: return "INSTALL_FAILED_VERSION_DOWNGRADE";
+ case INSTALL_PARSE_FAILED_NOT_APK: return "INSTALL_PARSE_FAILED_NOT_APK";
+ case INSTALL_PARSE_FAILED_BAD_MANIFEST: return "INSTALL_PARSE_FAILED_BAD_MANIFEST";
+ case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return "INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION";
+ case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return "INSTALL_PARSE_FAILED_NO_CERTIFICATES";
+ case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return "INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES";
+ case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return "INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING";
+ case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return "INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME";
+ case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return "INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID";
+ case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return "INSTALL_PARSE_FAILED_MANIFEST_MALFORMED";
+ case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return "INSTALL_PARSE_FAILED_MANIFEST_EMPTY";
+ case INSTALL_FAILED_INTERNAL_ERROR: return "INSTALL_FAILED_INTERNAL_ERROR";
+ case INSTALL_FAILED_USER_RESTRICTED: return "INSTALL_FAILED_USER_RESTRICTED";
+ case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION";
+ case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS";
+ case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
+ case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA";
+ case INSTALL_FAILED_MISSING_SPLIT: return "INSTALL_FAILED_MISSING_SPLIT";
+ case INSTALL_FAILED_BAD_SIGNATURE: return "INSTALL_FAILED_BAD_SIGNATURE";
+ case INSTALL_FAILED_WRONG_INSTALLED_VERSION: return "INSTALL_FAILED_WRONG_INSTALLED_VERSION";
+ case INSTALL_FAILED_PROCESS_NOT_DEFINED: return "INSTALL_FAILED_PROCESS_NOT_DEFINED";
+ case INSTALL_FAILED_SESSION_INVALID: return "INSTALL_FAILED_SESSION_INVALID";
+ default: return Integer.toString(status);
+ }
+ }
+
+ /** {@hide} */
+ public static int installStatusToPublicStatus(int status) {
+ switch (status) {
+ case INSTALL_SUCCEEDED: return PackageInstaller.STATUS_SUCCESS;
+ case INSTALL_FAILED_ALREADY_EXISTS: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_INVALID_APK: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_INVALID_URI: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_INSUFFICIENT_STORAGE: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_DUPLICATE_PACKAGE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_NO_SHARED_USER: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_DEXOPT: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_OLDER_SDK: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_CONFLICTING_PROVIDER: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_NEWER_SDK: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_TEST_ONLY: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_MISSING_FEATURE: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_CONTAINER_ERROR: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_MEDIA_UNAVAILABLE: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_VERIFICATION_TIMEOUT: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case INSTALL_FAILED_VERIFICATION_FAILURE: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case INSTALL_FAILED_PACKAGE_CHANGED: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_UID_CHANGED: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_VERSION_DOWNGRADE: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_NOT_APK: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_BAD_MANIFEST: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_BAD_DEX_METADATA: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_BAD_SIGNATURE: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
+ case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_NO_MATCHING_ABIS: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ default: return PackageInstaller.STATUS_FAILURE;
+ }
+ }
+
+ /** {@hide} */
+ @NonNull
+ public static String deleteStatusToString(int status, @Nullable String msg) {
+ final String str = deleteStatusToString(status);
+ if (msg != null) {
+ return str + ": " + msg;
+ } else {
+ return str;
+ }
+ }
+
+ /** {@hide} */
+ @NonNull
+ @UnsupportedAppUsage
+ public static String deleteStatusToString(int status) {
+ switch (status) {
+ case DELETE_SUCCEEDED: return "DELETE_SUCCEEDED";
+ case DELETE_FAILED_INTERNAL_ERROR: return "DELETE_FAILED_INTERNAL_ERROR";
+ case DELETE_FAILED_DEVICE_POLICY_MANAGER: return "DELETE_FAILED_DEVICE_POLICY_MANAGER";
+ case DELETE_FAILED_USER_RESTRICTED: return "DELETE_FAILED_USER_RESTRICTED";
+ case DELETE_FAILED_OWNER_BLOCKED: return "DELETE_FAILED_OWNER_BLOCKED";
+ case DELETE_FAILED_ABORTED: return "DELETE_FAILED_ABORTED";
+ case DELETE_FAILED_USED_SHARED_LIBRARY: return "DELETE_FAILED_USED_SHARED_LIBRARY";
+ case DELETE_FAILED_APP_PINNED: return "DELETE_FAILED_APP_PINNED";
+ default: return Integer.toString(status);
+ }
+ }
+
+ /** {@hide} */
+ public static int deleteStatusToPublicStatus(int status) {
+ switch (status) {
+ case DELETE_SUCCEEDED: return PackageInstaller.STATUS_SUCCESS;
+ case DELETE_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
+ case DELETE_FAILED_DEVICE_POLICY_MANAGER: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case DELETE_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case DELETE_FAILED_OWNER_BLOCKED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case DELETE_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case DELETE_FAILED_USED_SHARED_LIBRARY: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case DELETE_FAILED_APP_PINNED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ default: return PackageInstaller.STATUS_FAILURE;
+ }
+ }
+
+ /** {@hide} */
+ @NonNull
+ public static String permissionFlagToString(int flag) {
+ switch (flag) {
+ case FLAG_PERMISSION_GRANTED_BY_DEFAULT: return "GRANTED_BY_DEFAULT";
+ case FLAG_PERMISSION_POLICY_FIXED: return "POLICY_FIXED";
+ case FLAG_PERMISSION_SYSTEM_FIXED: return "SYSTEM_FIXED";
+ case FLAG_PERMISSION_USER_SET: return "USER_SET";
+ case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
+ case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
+ case FLAG_PERMISSION_REVOKE_WHEN_REQUESTED: return "REVOKE_WHEN_REQUESTED";
+ case FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED: return "USER_SENSITIVE_WHEN_GRANTED";
+ case FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED: return "USER_SENSITIVE_WHEN_DENIED";
+ case FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT: return "RESTRICTION_INSTALLER_EXEMPT";
+ case FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT: return "RESTRICTION_SYSTEM_EXEMPT";
+ case FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT: return "RESTRICTION_UPGRADE_EXEMPT";
+ case FLAG_PERMISSION_APPLY_RESTRICTION: return "APPLY_RESTRICTION";
+ case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE";
+ case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT";
+ case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME";
+ case FLAG_PERMISSION_AUTO_REVOKED: return "AUTO_REVOKED";
+ default: return Integer.toString(flag);
+ }
+ }
+
+ /** {@hide} */
+ public static class LegacyPackageDeleteObserver extends PackageDeleteObserver {
+ private final IPackageDeleteObserver mLegacy;
+
+ public LegacyPackageDeleteObserver(IPackageDeleteObserver legacy) {
+ mLegacy = legacy;
+ }
+
+ @Override
+ public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
+ if (mLegacy == null) return;
+ try {
+ mLegacy.packageDeleted(basePackageName, returnCode);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Return the install reason that was recorded when a package was first
+ * installed for a specific user. Requesting the install reason for another
+ * user will require the permission INTERACT_ACROSS_USERS_FULL.
+ *
+ * @param packageName The package for which to retrieve the install reason
+ * @param user The user for whom to retrieve the install reason
+ * @return The install reason. If the package is not installed for the given
+ * user, {@code INSTALL_REASON_UNKNOWN} is returned.
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @TestApi
+ @InstallReason
+ public abstract int getInstallReason(@NonNull String packageName, @NonNull UserHandle user);
+
+ /**
+ * Checks whether the calling package is allowed to request package installs through package
+ * installer. Apps are encouraged to call this API before launching the package installer via
+ * intent {@link android.content.Intent#ACTION_INSTALL_PACKAGE}. Starting from Android O, the
+ * user can explicitly choose what external sources they trust to install apps on the device.
+ * If this API returns false, the install request will be blocked by the package installer and
+ * a dialog will be shown to the user with an option to launch settings to change their
+ * preference. An application must target Android O or higher and declare permission
+ * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES} in order to use this API.
+ *
+ * @return true if the calling package is trusted by the user to request install packages on
+ * the device, false otherwise.
+ * @see android.content.Intent#ACTION_INSTALL_PACKAGE
+ * @see android.provider.Settings#ACTION_MANAGE_UNKNOWN_APP_SOURCES
+ */
+ public abstract boolean canRequestPackageInstalls();
+
+ /**
+ * Return the {@link ComponentName} of the activity providing Settings for the Instant App
+ * resolver.
+ *
+ * @see {@link android.content.Intent#ACTION_INSTANT_APP_RESOLVER_SETTINGS}
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @SystemApi
+ public abstract ComponentName getInstantAppResolverSettingsComponent();
+
+ /**
+ * Return the {@link ComponentName} of the activity responsible for installing instant
+ * applications.
+ *
+ * @see {@link android.content.Intent#ACTION_INSTALL_INSTANT_APP_PACKAGE}
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ @SystemApi
+ public abstract ComponentName getInstantAppInstallerComponent();
+
+ /**
+ * Return the Android Id for a given Instant App.
+ *
+ * @see {@link android.provider.Settings.Secure#ANDROID_ID}
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @Nullable
+ public abstract String getInstantAppAndroidId(@NonNull String packageName,
+ @NonNull UserHandle user);
+
+ /**
+ * Callback use to notify the callers of module registration that the operation
+ * has finished.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static abstract class DexModuleRegisterCallback {
+ public abstract void onDexModuleRegistered(String dexModulePath, boolean success,
+ String message);
+ }
+
+ /**
+ * Register an application dex module with the package manager.
+ * The package manager will keep track of the given module for future optimizations.
+ *
+ * Dex module optimizations will disable the classpath checking at runtime. The client bares
+ * the responsibility to ensure that the static assumptions on classes in the optimized code
+ * hold at runtime (e.g. there's no duplicate classes in the classpath).
+ *
+ * Note that the package manager already keeps track of dex modules loaded with
+ * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
+ * This can be called for an eager registration.
+ *
+ * The call might take a while and the results will be posted on the main thread, using
+ * the given callback.
+ *
+ * If the module is intended to be shared with other apps, make sure that the file
+ * permissions allow for it.
+ * If at registration time the permissions allow for others to read it, the module would
+ * be marked as a shared module which might undergo a different optimization strategy.
+ * (usually shared modules will generated larger optimizations artifacts,
+ * taking more disk space).
+ *
+ * @param dexModulePath the absolute path of the dex module.
+ * @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will
+ * be called once the registration finishes.
+ *
+ * @hide
+ */
+ @SuppressWarnings("HiddenAbstractMethod")
+ @SystemApi
+ public abstract void registerDexModule(@NonNull String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback);
+
+ /**
+ * Returns the {@link ArtManager} associated with this package manager.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull ArtManager getArtManager() {
+ throw new UnsupportedOperationException("getArtManager not implemented in subclass");
+ }
+
+ /**
+ * Sets or clears the harmful app warning details for the given app.
+ *
+ * When set, any attempt to launch an activity in this package will be intercepted and a
+ * warning dialog will be shown to the user instead, with the given warning. The user
+ * will have the option to proceed with the activity launch, or to uninstall the application.
+ *
+ * @param packageName The full name of the package to warn on.
+ * @param warning A warning string to display to the user describing the threat posed by the
+ * application, or null to clear the warning.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_HARMFUL_APP_WARNINGS)
+ @SystemApi
+ public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning) {
+ throw new UnsupportedOperationException("setHarmfulAppWarning not implemented in subclass");
+ }
+
+ /**
+ * Returns the harmful app warning string for the given app, or null if there is none set.
+ *
+ * @param packageName The full name of the desired package.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_HARMFUL_APP_WARNINGS)
+ @Nullable
+ @SystemApi
+ public CharSequence getHarmfulAppWarning(@NonNull String packageName) {
+ throw new UnsupportedOperationException("getHarmfulAppWarning not implemented in subclass");
+ }
+
+ /** @hide */
+ @IntDef(prefix = { "CERT_INPUT_" }, value = {
+ CERT_INPUT_RAW_X509,
+ CERT_INPUT_SHA256
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CertificateInputType {}
+
+ /**
+ * Certificate input bytes: the input bytes represent an encoded X.509 Certificate which could
+ * be generated using an {@code CertificateFactory}
+ */
+ public static final int CERT_INPUT_RAW_X509 = 0;
+
+ /**
+ * Certificate input bytes: the input bytes represent the SHA256 output of an encoded X.509
+ * Certificate.
+ */
+ public static final int CERT_INPUT_SHA256 = 1;
+
+ /**
+ * Searches the set of signing certificates by which the given package has proven to have been
+ * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES}
+ * since it takes into account the possibility of signing certificate rotation, except in the
+ * case of packages that are signed by multiple certificates, for which signing certificate
+ * rotation is not supported. This method is analogous to using {@code getPackageInfo} with
+ * {@code GET_SIGNING_CERTIFICATES} and then searching through the resulting {@code
+ * signingInfo} field to see if the desired certificate is present.
+ *
+ * @param packageName package whose signing certificates to check
+ * @param certificate signing certificate for which to search
+ * @param type representation of the {@code certificate}
+ * @return true if this package was or is signed by exactly the certificate {@code certificate}
+ */
+ public boolean hasSigningCertificate(@NonNull String packageName, @NonNull byte[] certificate,
+ @CertificateInputType int type) {
+ throw new UnsupportedOperationException(
+ "hasSigningCertificate not implemented in subclass");
+ }
+
+ /**
+ * Searches the set of signing certificates by which the package(s) for the given uid has proven
+ * to have been signed. For multiple packages sharing the same uid, this will return the
+ * signing certificates found in the signing history of the "newest" package, where "newest"
+ * indicates the package with the newest signing certificate in the shared uid group. This
+ * method should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES}
+ * since it takes into account the possibility of signing certificate rotation, except in the
+ * case of packages that are signed by multiple certificates, for which signing certificate
+ * rotation is not supported. This method is analogous to using {@code getPackagesForUid}
+ * followed by {@code getPackageInfo} with {@code GET_SIGNING_CERTIFICATES}, selecting the
+ * {@code PackageInfo} of the newest-signed bpackage , and finally searching through the
+ * resulting {@code signingInfo} field to see if the desired certificate is there.
+ *
+ * @param uid uid whose signing certificates to check
+ * @param certificate signing certificate for which to search
+ * @param type representation of the {@code certificate}
+ * @return true if this package was or is signed by exactly the certificate {@code certificate}
+ */
+ public boolean hasSigningCertificate(
+ int uid, @NonNull byte[] certificate, @CertificateInputType int type) {
+ throw new UnsupportedOperationException(
+ "hasSigningCertificate not implemented in subclass");
+ }
+
+ /**
+ * Trust any Installer to provide checksums for the package.
+ * @see #requestChecksums
+ */
+ public static final @NonNull List<Certificate> TRUST_ALL = Collections.singletonList(null);
+
+ /**
+ * Don't trust any Installer to provide checksums for the package.
+ * This effectively disables optimized Installer-enforced checksums.
+ * @see #requestChecksums
+ */
+ public static final @NonNull List<Certificate> TRUST_NONE = Collections.singletonList(null);
+
+ /** Listener that gets notified when checksums are available. */
+ @FunctionalInterface
+ public interface OnChecksumsReadyListener {
+ /**
+ * Called when the checksums are available.
+ *
+ * @param checksums array of checksums.
+ */
+ void onChecksumsReady(@NonNull List<ApkChecksum> checksums);
+ }
+
+ /**
+ * Requesting the checksums for APKs within a package.
+ * The checksums will be returned asynchronously via onChecksumsReadyListener.
+ *
+ * By default returns all readily available checksums:
+ * - enforced by platform,
+ * - enforced by installer.
+ * If caller needs a specific checksum kind, they can specify it as required.
+ *
+ * <b>Caution: Android can not verify installer-provided checksums. Make sure you specify
+ * trusted installers.</b>
+ *
+ * @param packageName whose checksums to return.
+ * @param includeSplits whether to include checksums for non-base splits.
+ * @param required explicitly request the checksum types. May incur significant
+ * CPU/memory/disk usage.
+ * @param trustedInstallers for checksums enforced by installer, which installers are to be
+ * trusted.
+ * {@link #TRUST_ALL} will return checksums from any installer,
+ * {@link #TRUST_NONE} disables optimized installer-enforced checksums,
+ * otherwise the list has to be non-empty list of certificates.
+ * @param onChecksumsReadyListener called once when the results are available.
+ * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
+ * @throws IllegalArgumentException if the list of trusted installer certificates is empty.
+ * @throws NameNotFoundException if a package with the given name cannot be found on the system.
+ */
+ public void requestChecksums(@NonNull String packageName, boolean includeSplits,
+ @Checksum.TypeMask int required, @NonNull List<Certificate> trustedInstallers,
+ @NonNull OnChecksumsReadyListener onChecksumsReadyListener)
+ throws CertificateEncodingException, NameNotFoundException {
+ throw new UnsupportedOperationException("requestChecksums not implemented in subclass");
+ }
+
+ /**
+ * @return the default text classifier package name, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public String getDefaultTextClassifierPackageName() {
+ throw new UnsupportedOperationException(
+ "getDefaultTextClassifierPackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the system defined text classifier package names, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public String getSystemTextClassifierPackageName() {
+ throw new UnsupportedOperationException(
+ "getSystemTextClassifierPackageName not implemented in subclass");
+ }
+
+ /**
+ * @return attention service package name, or null if there's none.
+ *
+ * @hide
+ */
+ public String getAttentionServicePackageName() {
+ throw new UnsupportedOperationException(
+ "getAttentionServicePackageName not implemented in subclass");
+ }
+
+ /**
+ * @return rotation resolver service's package name, or null if there's none.
+ *
+ * @hide
+ */
+ public String getRotationResolverPackageName() {
+ throw new UnsupportedOperationException(
+ "getRotationResolverPackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the wellbeing app package name, or null if it's not defined by the OEM.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public String getWellbeingPackageName() {
+ throw new UnsupportedOperationException(
+ "getWellbeingPackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the system defined app predictor package name, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getAppPredictionServicePackageName() {
+ throw new UnsupportedOperationException(
+ "getAppPredictionServicePackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the system defined content capture service package name, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getSystemCaptionsServicePackageName() {
+ throw new UnsupportedOperationException(
+ "getSystemCaptionsServicePackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the system defined setup wizard package name, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getSetupWizardPackageName() {
+ throw new UnsupportedOperationException(
+ "getSetupWizardPackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the system defined content capture package name, or null if there's none.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public String getContentCaptureServicePackageName() {
+ throw new UnsupportedOperationException(
+ "getContentCaptureServicePackageName not implemented in subclass");
+ }
+
+ /**
+ * @return the incident report approver app package name, or null if it's not defined
+ * by the OEM.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getIncidentReportApproverPackageName() {
+ throw new UnsupportedOperationException(
+ "getIncidentReportApproverPackageName 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(@NonNull String packageName, @UserIdInt int userId) {
+ throw new UnsupportedOperationException(
+ "isPackageStateProtected not implemented in subclass");
+ }
+
+ /**
+ * Notify to the rest of the system that a new device configuration has
+ * been prepared and that it is time to refresh caches.
+ *
+ * @see android.content.Intent#ACTION_DEVICE_CUSTOMIZATION_READY
+ *
+ * @hide
+ */
+ @SystemApi
+ public void sendDeviceCustomizationReadyBroadcast() {
+ throw new UnsupportedOperationException(
+ "sendDeviceCustomizationReadyBroadcast not implemented in subclass");
+ }
+
+ /**
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @return whether this package is whitelisted from having its runtime permission be
+ * auto-revoked if unused for an extended period of time.
+ */
+ public boolean isAutoRevokeWhitelisted() {
+ throw new UnsupportedOperationException(
+ "isAutoRevokeWhitelisted not implemented in subclass");
+ }
+
+ /**
+ * Returns if the provided drawable represents the default activity icon provided by the system.
+ *
+ * PackageManager silently returns a default application icon for any package/activity if the
+ * app itself does not define one or if the system encountered any error when loading the icon.
+ *
+ * Developers can use this to check implement app specific logic around retrying or caching.
+ *
+ * @return true if the drawable represents the default activity icon, false otherwise
+ * @see #getDefaultActivityIcon()
+ * @see #getActivityIcon
+ * @see LauncherActivityInfo#getIcon(int)
+ */
+ public boolean isDefaultApplicationIcon(@NonNull Drawable drawable) {
+ int resId = drawable instanceof AdaptiveIconDrawable
+ ? ((AdaptiveIconDrawable) drawable).getSourceDrawableResId() : Resources.ID_NULL;
+ return resId == com.android.internal.R.drawable.sym_def_app_icon
+ || resId == com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon;
+ }
+
+ /**
+ * Sets MIME group's MIME types.
+ *
+ * Libraries should use a reverse-DNS prefix followed by a ':' character and library-specific
+ * group name to avoid namespace collisions, e.g. "com.example:myFeature".
+ *
+ * @param mimeGroup MIME group to modify.
+ * @param mimeTypes new MIME types contained by MIME group.
+ * @throws IllegalArgumentException if the MIME group was not declared in the manifest.
+ */
+ public void setMimeGroup(@NonNull String mimeGroup, @NonNull Set<String> mimeTypes) {
+ throw new UnsupportedOperationException(
+ "setMimeGroup not implemented in subclass");
+ }
+
+ /**
+ * Gets all MIME types contained by MIME group.
+ *
+ * Libraries should use a reverse-DNS prefix followed by a ':' character and library-specific
+ * group name to avoid namespace collisions, e.g. "com.example:myFeature".
+ *
+ * @param mimeGroup MIME group to retrieve.
+ * @return MIME types contained by the MIME group.
+ * @throws IllegalArgumentException if the MIME group was not declared in the manifest.
+ */
+ @NonNull
+ public Set<String> getMimeGroup(@NonNull String mimeGroup) {
+ throw new UnsupportedOperationException(
+ "getMimeGroup not implemented in subclass");
+ }
+
+ /**
+ * Returns the property defined in the given package's <appliction> tag.
+ *
+ * @throws NameNotFoundException if either the given package is not installed or if the
+ * given property is not defined within the <application> tag.
+ */
+ @NonNull
+ public Property getProperty(@NonNull String propertyName, @NonNull String packageName)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException(
+ "getProperty not implemented in subclass");
+ }
+
+ /**
+ * Returns the property defined in the given component declaration.
+ *
+ * @throws NameNotFoundException if either the given component does not exist or if the
+ * given property is not defined within the component declaration.
+ */
+ @NonNull
+ public Property getProperty(@NonNull String propertyName, @NonNull ComponentName component)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException(
+ "getProperty not implemented in subclass");
+ }
+
+ /**
+ * Returns the property definition for all <application> tags.
+ * <p>If the property is not defined with any <application> tag,
+ * returns and empty list.
+ */
+ @NonNull
+ public List<Property> queryApplicationProperty(@NonNull String propertyName) {
+ throw new UnsupportedOperationException(
+ "qeuryApplicationProperty not implemented in subclass");
+ }
+
+ /**
+ * Returns the property definition for all <activity> and <activity-alias> tags.
+ * <p>If the property is not defined with any <activity> and <activity-alias> tag,
+ * returns and empty list.
+ */
+ @NonNull
+ public List<Property> queryActivityProperty(@NonNull String propertyName) {
+ throw new UnsupportedOperationException(
+ "qeuryActivityProperty not implemented in subclass");
+ }
+
+ /**
+ * Returns the property definition for all <provider> tags.
+ * <p>If the property is not defined with any <provider> tag,
+ * returns and empty list.
+ */
+ @NonNull
+ public List<Property> queryProviderProperty(@NonNull String propertyName) {
+ throw new UnsupportedOperationException(
+ "qeuryProviderProperty not implemented in subclass");
+ }
+
+ /**
+ * Returns the property definition for all <receiver> tags.
+ * <p>If the property is not defined with any <receiver> tag,
+ * returns and empty list.
+ */
+ @NonNull
+ public List<Property> queryReceiverProperty(@NonNull String propertyName) {
+ throw new UnsupportedOperationException(
+ "qeuryReceiverProperty not implemented in subclass");
+ }
+
+ /**
+ * Returns the property definition for all <service> tags.
+ * <p>If the property is not defined with any <service> tag,
+ * returns and empty list.
+ */
+ @NonNull
+ public List<Property> queryServiceProperty(@NonNull String propertyName) {
+ throw new UnsupportedOperationException(
+ "qeuryServiceProperty not implemented in subclass");
+ }
+
+ /**
+ * Grants implicit visibility of the package that provides an authority to a querying UID.
+ *
+ * @throws SecurityException when called by a package other than the contacts provider
+ * @hide
+ */
+ public void grantImplicitAccess(int queryingUid, String visibleAuthority) {
+ try {
+ ActivityThread.getPackageManager().grantImplicitAccess(queryingUid, visibleAuthority);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // Some of the flags don't affect the query result, but let's be conservative and cache
+ // each combination of flags separately.
+
+ private static final class ApplicationInfoQuery {
+ final String packageName;
+ final int flags;
+ final int userId;
+
+ ApplicationInfoQuery(@Nullable String packageName, int flags, int userId) {
+ this.packageName = packageName;
+ this.flags = flags;
+ this.userId = userId;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "ApplicationInfoQuery(packageName=\"%s\", flags=%s, userId=%s)",
+ packageName, flags, userId);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = Objects.hashCode(packageName);
+ hash = hash * 13 + Objects.hashCode(flags);
+ hash = hash * 13 + Objects.hashCode(userId);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object rval) {
+ if (rval == null) {
+ return false;
+ }
+ ApplicationInfoQuery other;
+ try {
+ other = (ApplicationInfoQuery) rval;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+ return Objects.equals(packageName, other.packageName)
+ && flags == other.flags
+ && userId == other.userId;
+ }
+ }
+
+ private static ApplicationInfo getApplicationInfoAsUserUncached(
+ String packageName, int flags, int userId) {
+ try {
+ return ActivityThread.getPackageManager()
+ .getApplicationInfo(packageName, flags, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
+ sApplicationInfoCache =
+ new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
+ 16, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ "getApplicationInfo") {
+ @Override
+ protected ApplicationInfo recompute(ApplicationInfoQuery query) {
+ return getApplicationInfoAsUserUncached(
+ query.packageName, query.flags, query.userId);
+ }
+ @Override
+ protected ApplicationInfo maybeCheckConsistency(
+ ApplicationInfoQuery query, ApplicationInfo proposedResult) {
+ // Implementing this debug check for ApplicationInfo would require a
+ // complicated deep comparison, so just bypass it for now.
+ return proposedResult;
+ }
+ };
+
+ /** @hide */
+ public static ApplicationInfo getApplicationInfoAsUserCached(
+ String packageName, int flags, int userId) {
+ return sApplicationInfoCache.query(
+ new ApplicationInfoQuery(packageName, flags, userId));
+ }
+
+ /**
+ * Make getApplicationInfoAsUser() bypass the cache in this process.
+ *
+ * @hide
+ */
+ public static void disableApplicationInfoCache() {
+ sApplicationInfoCache.disableLocal();
+ }
+
+ private static final PropertyInvalidatedCache.AutoCorker sCacheAutoCorker =
+ new PropertyInvalidatedCache.AutoCorker(PermissionManager.CACHE_KEY_PACKAGE_INFO);
+
+ /**
+ * Invalidate caches of package and permission information system-wide.
+ *
+ * @hide
+ */
+ public static void invalidatePackageInfoCache() {
+ sCacheAutoCorker.autoCork();
+ }
+
+ // Some of the flags don't affect the query result, but let's be conservative and cache
+ // each combination of flags separately.
+
+ private static final class PackageInfoQuery {
+ final String packageName;
+ final int flags;
+ final int userId;
+
+ PackageInfoQuery(@Nullable String packageName, int flags, int userId) {
+ this.packageName = packageName;
+ this.flags = flags;
+ this.userId = userId;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "PackageInfoQuery(packageName=\"%s\", flags=%s, userId=%s)",
+ packageName, flags, userId);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = Objects.hashCode(packageName);
+ hash = hash * 13 + Objects.hashCode(flags);
+ hash = hash * 13 + Objects.hashCode(userId);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object rval) {
+ if (rval == null) {
+ return false;
+ }
+ PackageInfoQuery other;
+ try {
+ other = (PackageInfoQuery) rval;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+ return Objects.equals(packageName, other.packageName)
+ && flags == other.flags
+ && userId == other.userId;
+ }
+ }
+
+ private static PackageInfo getPackageInfoAsUserUncached(
+ String packageName, int flags, int userId) {
+ try {
+ return ActivityThread.getPackageManager().getPackageInfo(packageName, flags, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
+ sPackageInfoCache =
+ new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
+ 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ "getPackageInfo") {
+ @Override
+ protected PackageInfo recompute(PackageInfoQuery query) {
+ return getPackageInfoAsUserUncached(
+ query.packageName, query.flags, query.userId);
+ }
+ @Override
+ protected PackageInfo maybeCheckConsistency(
+ PackageInfoQuery query, PackageInfo proposedResult) {
+ // Implementing this debug check for PackageInfo would require a
+ // complicated deep comparison, so just bypass it for now.
+ return proposedResult;
+ }
+ };
+
+ /** @hide */
+ public static PackageInfo getPackageInfoAsUserCached(
+ String packageName, int flags, int userId) {
+ return sPackageInfoCache.query(new PackageInfoQuery(packageName, flags, userId));
+ }
+
+ /**
+ * Make getPackageInfoAsUser() bypass the cache in this process.
+ * @hide
+ */
+ public static void disablePackageInfoCache() {
+ sPackageInfoCache.disableLocal();
+ }
+
+ /**
+ * Inhibit package info cache invalidations when correct.
+ *
+ * @hide */
+ public static void corkPackageInfoCache() {
+ PropertyInvalidatedCache.corkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO);
+ }
+
+ /**
+ * Enable package info cache invalidations.
+ *
+ * @hide */
+ public static void uncorkPackageInfoCache() {
+ PropertyInvalidatedCache.uncorkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO);
+ }
+
+ /**
+ * Returns the token to be used by the subsequent calls to holdLock().
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
+ @TestApi
+ public IBinder getHoldLockToken() {
+ try {
+ return ActivityThread.getPackageManager().getHoldLockToken();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Holds the PM lock for the specified amount of milliseconds.
+ * Intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
+ * @hide
+ */
+ @TestApi
+ public void holdLock(IBinder token, int durationMs) {
+ try {
+ ActivityThread.getPackageManager().holdLock(token, durationMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set a list of apps to keep around as APKs even if no user has currently installed it.
+ * @param packageList List of package names to keep cached.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES)
+ @TestApi
+ public void setKeepUninstalledPackages(@NonNull List<String> packageList) {
+ try {
+ ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/content/pm/PackageManagerBenchmark.java b/android/content/pm/PackageManagerBenchmark.java
new file mode 100644
index 0000000..a82fab4
--- /dev/null
+++ b/android/content/pm/PackageManagerBenchmark.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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.content.Context;
+import android.os.UserHandle;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerBenchmark {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void createUserContextBenchmark() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ context.createContextAsUser(UserHandle.SYSTEM, /* flags */ 0);
+ }
+ }
+
+ @Test
+ public void getResourcesForApplication_byStarAsUser()
+ throws PackageManager.NameNotFoundException {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ context.getPackageManager().getResourcesForApplicationAsUser(context.getPackageName(),
+ UserHandle.USER_SYSTEM);
+ }
+ }
+
+ @Test
+ public void getResourcesApplication_byCreateContextAsUser()
+ throws PackageManager.NameNotFoundException {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ context.createContextAsUser(UserHandle.SYSTEM, /* flags */ 0).getPackageManager()
+ .getResourcesForApplication(context.getPackageName());
+ }
+ }
+}
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
new file mode 100644
index 0000000..95186ee
--- /dev/null
+++ b/android/content/pm/PackageManagerInternal.java
@@ -0,0 +1,1164 @@
+/*
+ * 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.AppIdInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.ComponentInfoFlags;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.pm.PackageList;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Package manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class PackageManagerInternal implements PackageSettingsSnapshotProvider {
+ @IntDef(prefix = "PACKAGE_", value = {
+ PACKAGE_SYSTEM,
+ PACKAGE_SETUP_WIZARD,
+ PACKAGE_INSTALLER,
+ PACKAGE_VERIFIER,
+ PACKAGE_BROWSER,
+ PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+ PACKAGE_PERMISSION_CONTROLLER,
+ PACKAGE_DOCUMENTER,
+ PACKAGE_CONFIGURATOR,
+ PACKAGE_INCIDENT_REPORT_APPROVER,
+ PACKAGE_APP_PREDICTOR,
+ PACKAGE_OVERLAY_CONFIG_SIGNATURE,
+ PACKAGE_WIFI,
+ PACKAGE_COMPANION,
+ PACKAGE_RETAIL_DEMO,
+ PACKAGE_RECENTS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface KnownPackage {}
+
+ public static final int PACKAGE_SYSTEM = 0;
+ public static final int PACKAGE_SETUP_WIZARD = 1;
+ 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;
+ public static final int PACKAGE_PERMISSION_CONTROLLER = 6;
+ public static final int PACKAGE_WELLBEING = 7;
+ public static final int PACKAGE_DOCUMENTER = 8;
+ public static final int PACKAGE_CONFIGURATOR = 9;
+ public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
+ public static final int PACKAGE_APP_PREDICTOR = 11;
+ public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 12;
+ public static final int PACKAGE_WIFI = 13;
+ public static final int PACKAGE_COMPANION = 14;
+ public static final int PACKAGE_RETAIL_DEMO = 15;
+ public static final int PACKAGE_RECENTS = 16;
+ // Integer value of the last known package ID. Increases as new ID is added to KnownPackage.
+ // Please note the numbers should be continuous.
+ public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS;
+
+ @IntDef(flag = true, prefix = "RESOLVE_", value = {
+ RESOLVE_NON_BROWSER_ONLY,
+ RESOLVE_NON_RESOLVER_ONLY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrivateResolveFlags {}
+
+ /**
+ * Internal {@link #resolveIntent(Intent, String, int, int, int, boolean, int)} flag:
+ * only match components that contain a generic web intent filter.
+ */
+ public static final int RESOLVE_NON_BROWSER_ONLY = 0x00000001;
+
+ /**
+ * Internal {@link #resolveIntent(Intent, String, int, int, int, boolean, int)} flag: do not
+ * match to the resolver.
+ */
+ public static final int RESOLVE_NON_RESOLVER_ONLY = 0x00000002;
+
+ @IntDef(value = {
+ INTEGRITY_VERIFICATION_ALLOW,
+ INTEGRITY_VERIFICATION_REJECT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IntegrityVerificationResult {}
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
+ * integrity component allows the install to proceed.
+ */
+ public static final int INTEGRITY_VERIFICATION_ALLOW = 1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
+ * integrity component does not allow install to proceed.
+ */
+ public static final int INTEGRITY_VERIFICATION_REJECT = 0;
+
+ /** Observer called whenever the list of packages changes */
+ public interface PackageListObserver {
+ /** A package was added to the system. */
+ void onPackageAdded(@NonNull String packageName, int uid);
+ /** A package was changed - either installed for a specific user or updated. */
+ default void onPackageChanged(@NonNull String packageName, int uid) {}
+ /** A package was removed from the system. */
+ void onPackageRemoved(@NonNull String packageName, int uid);
+ }
+
+ /**
+ * Called when the package for the default SMS handler changed
+ *
+ * @param packageName the new sms package
+ * @param userId user for which the change was made
+ */
+ public void onDefaultSmsAppChanged(String packageName, int userId) {}
+
+ /**
+ * Called when the package for the default sim call manager changed
+ *
+ * @param packageName the new sms package
+ * @param userId user for which the change was made
+ */
+ public void onDefaultSimCallManagerAppChanged(String packageName, int userId) {}
+
+ /**
+ * Sets a list of apps to keep in PM's internal data structures and as APKs even if no user has
+ * currently installed it. The apps are not preloaded.
+ * @param packageList List of package names to keep cached.
+ */
+ public abstract void setKeepUninstalledPackages(List<String> packageList);
+
+ /**
+ * Gets whether some of the permissions used by this package require a user
+ * review before any of the app components can run.
+ * @param packageName The package name for which to check.
+ * @param userId The user under which to check.
+ * @return True a permissions review is required.
+ */
+ public abstract boolean isPermissionsReviewRequired(String packageName, int userId);
+
+ /**
+ * Retrieve all of the information we know about a particular package/application.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#getPackageInfo(String, int)
+ */
+ public abstract PackageInfo getPackageInfo(String packageName,
+ @PackageInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Retrieve CE data directory inode number of an application.
+ * Return 0 if there's error.
+ */
+ public abstract long getCeDataInode(String packageName, int userId);
+
+ /**
+ * Return a List of all application packages that are installed on the
+ * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
+ * set, a list of all applications including those deleted with
+ * {@code DELETE_KEEP_DATA} (partially installed apps with data directory)
+ * will be returned.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user for whom the installed applications are to be
+ * listed
+ * @param callingUid The uid of the original caller app
+ * @return A List of ApplicationInfo objects, one for each installed
+ * application. In the unlikely case there are no installed
+ * packages, an empty list is returned. If flag
+ * {@code MATCH_UNINSTALLED_PACKAGES} is set, the application
+ * information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DELETE_KEEP_DATA} flag set).
+ */
+ public abstract List<ApplicationInfo> getInstalledApplications(
+ @ApplicationInfoFlags int flags, @UserIdInt int userId, int callingUid);
+
+ /**
+ * 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);
+
+ /**
+ * Internal api to query the suspended state of a package.
+ * @param packageName The package to check.
+ * @param userId The user id to check for.
+ * @return {@code true} if the package is suspended, {@code false} otherwise.
+ * @see PackageManager#isPackageSuspended(String)
+ */
+ public abstract boolean isPackageSuspended(String packageName, int userId);
+
+ /**
+ * Removes all package suspensions imposed by any non-system packages.
+ */
+ public abstract void removeAllNonSystemPackageSuspensions(int userId);
+
+ /**
+ * Removes all suspensions imposed on the given package by non-system packages.
+ */
+ public abstract void removeNonSystemPackageSuspensions(String packageName, int userId);
+
+ /**
+ * Removes all {@link PackageManager.DistractionRestriction restrictions} set on the given
+ * package
+ */
+ public abstract void removeDistractingPackageRestrictions(String packageName, int userId);
+
+ /**
+ * Removes all {@link PackageManager.DistractionRestriction restrictions} set on all the
+ * packages.
+ */
+ public abstract void removeAllDistractingPackageRestrictions(int userId);
+
+ /**
+ * Flushes package restrictions for the given user immediately to disk.
+ */
+ @WorkerThread
+ public abstract void flushPackageRestrictions(int userId);
+
+ /**
+ * Get the name of the package that suspended the given package. Packages can be suspended by
+ * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#SUSPEND_APPS}.
+ *
+ * @param suspendedPackage The package that has been suspended.
+ * @param userId The user for which to check.
+ * @return Name of the package that suspended the given package. Returns {@code null} if the
+ * given package is not currently suspended and the platform package name - i.e.
+ * {@code "android"} - if the package was suspended by a device admin.
+ */
+ public abstract String getSuspendingPackage(String suspendedPackage, int userId);
+
+ /**
+ * Get the information describing the dialog to be shown to the user when they try to launch a
+ * suspended application.
+ *
+ * @param suspendedPackage The package that has been suspended.
+ * @param suspendingPackage
+ * @param userId The user for which to check.
+ * @return A {@link SuspendDialogInfo} object describing the dialog to be shown.
+ */
+ @Nullable
+ public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
+ String suspendingPackage, int userId);
+
+ /**
+ * Gets any distraction flags set via
+ * {@link PackageManager#setDistractingPackageRestrictions(String[], int)}
+ *
+ * @param packageName
+ * @param userId
+ * @return A bitwise OR of any of the {@link PackageManager.DistractionRestriction}
+ */
+ public abstract @PackageManager.DistractionRestriction int getDistractingPackageRestrictions(
+ 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
+ */
+ public abstract int getPackageUid(String packageName, @PackageInfoFlags int flags, int userId);
+
+ /**
+ * Retrieve all of the information we know about a particular package/application.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#getApplicationInfo(String, int)
+ */
+ public abstract ApplicationInfo getApplicationInfo(String packageName,
+ @ApplicationInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Retrieve all of the information we know about a particular activity class.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#getActivityInfo(ComponentName, int)
+ */
+ public abstract ActivityInfo getActivityInfo(ComponentName component,
+ @ComponentInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Retrieve all activities that can be performed for the given intent.
+ * @param resolvedType the resolved type of the intent, which should be resolved via
+ * {@link Intent#resolveTypeIfNeeded(ContentResolver)} before passing to {@link PackageManager}
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#queryIntentActivities(Intent, int)
+ */
+ public abstract List<ResolveInfo> queryIntentActivities(
+ Intent intent, @Nullable String resolvedType, @ResolveInfoFlags int flags,
+ int filterCallingUid, int userId);
+
+
+ /**
+ * Retrieve all services that can be performed for the given intent.
+ * @see PackageManager#queryIntentServices(Intent, int)
+ */
+ public abstract List<ResolveInfo> queryIntentServices(
+ Intent intent, int flags, int callingUid, int userId);
+
+ /**
+ * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
+ */
+ public abstract ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
+ int userId);
+
+ /**
+ * @return The default home activity component name.
+ */
+ public abstract ComponentName getDefaultHomeActivity(int userId);
+
+ /**
+ * @return The SystemUI service component name.
+ */
+ public abstract ComponentName getSystemUiServiceComponent();
+
+ /**
+ * Called by DeviceOwnerManagerService to set the package names of device owner and profile
+ * owners.
+ */
+ public abstract void setDeviceAndProfileOwnerPackages(
+ int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners);
+
+ /**
+ * Called by Owners to set the package names protected by the device owner.
+ */
+ public abstract void setDeviceOwnerProtectedPackages(
+ String deviceOwnerPackageName, List<String> packageNames);
+
+ /**
+ * Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.
+ */
+ public abstract boolean isPackageDataProtected(int userId, String packageName);
+
+ /**
+ * Returns {@code true} if a given package's state is protected, e.g. it cannot be force
+ * stopped, suspended, disabled or hidden. Otherwise, returns {@code false}.
+ */
+ public abstract boolean isPackageStateProtected(String packageName, int userId);
+
+ /**
+ * Returns {@code true} if a given package is installed as ephemeral. Otherwise, returns
+ * {@code false}.
+ */
+ public abstract boolean isPackageEphemeral(int userId, String packageName);
+
+ /**
+ * Gets whether the package was ever launched.
+ * @param packageName The package name.
+ * @param userId The user for which to check.
+ * @return Whether was launched.
+ * @throws IllegalArgumentException if the package is not found
+ */
+ public abstract boolean wasPackageEverLaunched(String packageName, int userId);
+
+ /**
+ * Retrieve the official name associated with a uid. This name is
+ * guaranteed to never change, though it is possible for the underlying
+ * uid to be changed. That is, if you are storing information about
+ * uids in persistent storage, you should use the string returned
+ * by this function instead of the raw uid.
+ *
+ * @param uid The uid for which you would like to retrieve a name.
+ * @return Returns a unique name for the given uid, or null if the
+ * uid is not currently assigned.
+ */
+ public abstract String getNameForUid(int uid);
+
+ /**
+ * Request to perform the second phase of ephemeral resolution.
+ * @param responseObj The response of the first phase of ephemeral resolution
+ * @param origIntent The original intent that triggered ephemeral resolution
+ * @param resolvedType The resolved type of the intent
+ * @param callingPkg The app requesting the ephemeral application
+ * @param callingFeatureId The feature in the package
+ * @param isRequesterInstantApp Whether or not the app requesting the ephemeral application
+ * is an instant app
+ * @param verificationBundle Optional bundle to pass to the installer for additional
+ * verification
+ * @param userId The ID of the user that triggered ephemeral resolution
+ */
+ public abstract void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
+ Intent origIntent, String resolvedType, String callingPkg,
+ @Nullable String callingFeatureId, boolean isRequesterInstantApp,
+ Bundle verificationBundle, int userId);
+
+ /**
+ * Grants implicit access based on an interaction between two apps. This grants access to the
+ * from one application to the other's package metadata.
+ * <p>
+ * When an application explicitly tries to interact with another application [via an
+ * activity, service or provider that is either declared in the caller's
+ * manifest via the {@code <queries>} tag or has been exposed via the target apps manifest using
+ * the {@code visibleToInstantApp} attribute], the target application must be able to see
+ * metadata about the calling app. If the calling application uses an implicit intent [ie
+ * action VIEW, category BROWSABLE], it remains hidden from the launched app.
+ * <p>
+ * If an interaction is not explicit, the {@code direct} argument should be set to false as
+ * visibility should not be granted in some cases. This method handles that logic.
+ * <p>
+ * @param userId the user
+ * @param intent the intent that triggered the grant
+ * @param recipientAppId The app ID of the application that is being given access to {@code
+ * visibleUid}
+ * @param visibleUid The uid of the application that is becoming accessible to {@code
+ * recipientAppId}
+ * @param direct true if the access is being made due to direct interaction between visibleUid
+ * and recipientAppId.
+ */
+ public abstract void grantImplicitAccess(
+ @UserIdInt int userId, Intent intent,
+ @AppIdInt int recipientAppId, int visibleUid,
+ boolean direct);
+
+ public abstract boolean isInstantAppInstallerComponent(ComponentName component);
+ /**
+ * Prunes instant apps and state associated with uninstalled
+ * instant apps according to the current platform policy.
+ */
+ public abstract void pruneInstantApps();
+
+ /**
+ * Prunes the cache of the APKs in the given APEXes.
+ * @param apexPackages The list of APEX packages that may contain APK-in-APEX.
+ */
+ public abstract void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages);
+
+ /**
+ * @return The SetupWizard package name.
+ */
+ public abstract String getSetupWizardPackageName();
+
+ public interface ExternalSourcesPolicy {
+
+ int USER_TRUSTED = 0; // User has trusted the package to install apps
+ int USER_BLOCKED = 1; // User has blocked the package to install apps
+ int USER_DEFAULT = 2; // Default code to use when user response is unavailable
+
+ /**
+ * Checks the user preference for whether a package is trusted to request installs through
+ * package installer
+ *
+ * @param packageName The package to check for
+ * @param uid the uid in which the package is running
+ * @return {@link #USER_TRUSTED} if the user has trusted the package, {@link #USER_BLOCKED}
+ * if user has blocked requests from the package, {@link #USER_DEFAULT} if the user response
+ * is not yet available
+ */
+ int getPackageTrustedToInstallApps(String packageName, int uid);
+ }
+
+ public abstract void setExternalSourcesPolicy(ExternalSourcesPolicy policy);
+
+ /**
+ * Return true if the given package is a persistent app process.
+ */
+ public abstract boolean isPackagePersistent(String packageName);
+
+ /**
+ * Get all overlay packages for a user.
+ * @param userId The user for which to get the overlays.
+ * @return A list of overlay packages. An empty list is returned if the
+ * user has no installed overlay packages.
+ */
+ public abstract List<PackageInfo> getOverlayPackages(int userId);
+
+ /**
+ * Get the names of all target packages for a user.
+ * @param userId The user for which to get the package names.
+ * @return A list of target package names. This list includes the "android" package.
+ */
+ public abstract List<String> getTargetPackageNames(int userId);
+
+ /**
+ * Set which overlay to use for a package.
+ * @param userId The user for which to update the overlays.
+ * @param targetPackageName The package name of the package for which to update the overlays.
+ * @param overlayPaths The complete list of overlay paths that should be enabled for
+ * the target. Previously enabled overlays not specified in the list
+ * will be disabled. Pass in null or empty paths to disable all overlays.
+ * The order of the items is significant if several overlays modify the
+ * same resource.
+ * @param outUpdatedPackageNames An output list that contains the package names of packages
+ * affected by the update of enabled overlays.
+ * @return true if all packages names were known by the package manager, false otherwise
+ */
+ public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
+ @Nullable OverlayPaths overlayPaths, Set<String> outUpdatedPackageNames);
+
+ /**
+ * Resolves an activity intent, allowing instant apps to be resolved.
+ */
+ public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
+ int flags, @PrivateResolveFlags int privateResolveFlags, int userId,
+ boolean resolveForStart, int filterCallingUid);
+
+ /**
+ * Resolves a service intent, allowing instant apps to be resolved.
+ */
+ public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
+ int flags, int userId, int callingUid);
+
+ /**
+ * Resolves a content provider intent.
+ */
+ public abstract ProviderInfo resolveContentProvider(String name, int flags, int userId);
+
+ /**
+ * Resolves a content provider intent.
+ */
+ public abstract ProviderInfo resolveContentProvider(String name, int flags, int userId,
+ int callingUid);
+
+ /**
+ * Track the creator of a new isolated uid.
+ * @param isolatedUid The newly created isolated uid.
+ * @param ownerUid The uid of the app that created the isolated process.
+ */
+ public abstract void addIsolatedUid(int isolatedUid, int ownerUid);
+
+ /**
+ * Track removal of an isolated uid.
+ * @param isolatedUid isolated uid that is no longer being used.
+ */
+ public abstract void removeIsolatedUid(int isolatedUid);
+
+ /**
+ * Return the taget SDK version for the app with the given UID.
+ */
+ public abstract int getUidTargetSdkVersion(int uid);
+
+ /**
+ * Return the taget SDK version for the app with the given package name.
+ */
+ public abstract int getPackageTargetSdkVersion(String packageName);
+
+ /** 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)
+ * associated with an instant app. It may be kept after the instant app has been uninstalled.
+ */
+ public abstract boolean hasInstantApplicationMetadata(String packageName, int userId);
+
+ /**
+ * Updates a package last used time.
+ */
+ public abstract void notifyPackageUse(String packageName, int reason);
+
+ /**
+ * Returns a package object for the given package name.
+ */
+ public abstract @Nullable AndroidPackage getPackage(@NonNull String packageName);
+
+ public abstract @Nullable PackageSetting getPackageSetting(String packageName);
+
+ /**
+ * Returns a package for the given UID. If the UID is part of a shared user ID, one
+ * of the packages will be chosen to be returned.
+ */
+ public abstract @Nullable AndroidPackage getPackage(int uid);
+
+ /**
+ * Returns a list without a change observer.
+ *
+ * @see #getPackageList(PackageListObserver)
+ */
+ public @NonNull PackageList getPackageList() {
+ return getPackageList(null);
+ }
+
+ /**
+ * Returns the list of packages installed at the time of the method call.
+ * <p>The given observer is notified when the list of installed packages
+ * changes [eg. a package was installed or uninstalled]. It will not be
+ * notified if a package is updated.
+ * <p>The package list will not be updated automatically as packages are
+ * installed / uninstalled. Any changes must be handled within the observer.
+ */
+ public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer);
+
+ /**
+ * Removes the observer.
+ * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically
+ * remove the observer.
+ * <p>Does nothing if the observer isn't currently registered.
+ * <p>Observers are notified asynchronously and it's possible for an observer to be
+ * invoked after its been removed.
+ */
+ public abstract void removePackageListObserver(@NonNull PackageListObserver observer);
+
+ /**
+ * Returns a package object for the disabled system package name.
+ */
+ public abstract @Nullable PackageSetting getDisabledSystemPackage(@NonNull String packageName);
+
+ /**
+ * Returns the package name for the disabled system package.
+ *
+ * This is equivalent to
+ * {@link #getDisabledSystemPackage(String)}
+ * .{@link PackageSetting#pkg}
+ * .{@link AndroidPackage#getPackageName()}
+ */
+ public abstract @Nullable String getDisabledSystemPackageName(@NonNull String packageName);
+
+ /**
+ * Returns whether or not the component is the resolver activity.
+ */
+ public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
+
+
+ /**
+ * Returns a list of package names for a known package
+ */
+ public abstract @NonNull String[] getKnownPackageNames(
+ @KnownPackage int knownPackage, int userId);
+
+ /**
+ * Returns whether the package is an instant app.
+ */
+ public abstract boolean isInstantApp(String packageName, int userId);
+
+ /**
+ * Returns whether the package is an instant app.
+ */
+ public abstract @Nullable String getInstantAppPackageName(int uid);
+
+ /**
+ * Returns whether or not access to the application should be filtered.
+ * <p>
+ * Access may be limited based upon whether the calling or target applications
+ * are instant applications.
+ *
+ * @see #canAccessInstantApps
+ */
+ public abstract boolean filterAppAccess(
+ @NonNull AndroidPackage pkg, int callingUid, int userId);
+
+ /**
+ * Returns whether or not access to the application should be filtered.
+ *
+ * @see #filterAppAccess(AndroidPackage, int, int)
+ */
+ public abstract boolean filterAppAccess(
+ @NonNull String packageName, int callingUid, int userId);
+
+ /** Returns whether the given package was signed by the platform */
+ public abstract boolean isPlatformSigned(String pkg);
+
+ /**
+ * 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);
+
+ /**
+ * Returns {@code true} if the the signing information for {@code clientUid} is sufficient
+ * to gain access gated by {@code capability}. This can happen if the two UIDs have the
+ * same signing information, if the signing information {@code clientUid} indicates that
+ * it has the signing certificate for {@code serverUid} in its signing history (if it was
+ * previously signed by it), or if the signing certificate for {@code clientUid} is in the
+ * signing history for {@code serverUid} and with the {@code capability} specified.
+ */
+ public abstract boolean hasSignatureCapability(int serverUid, int clientUid,
+ @PackageParser.SigningDetails.CertCapabilities int capability);
+
+ /**
+ * Get appIds of all available apps which specified android:sharedUserId in the manifest.
+ *
+ * @return a SparseArray mapping from appId to it's sharedUserId.
+ */
+ public abstract SparseArray<String> getAppsWithSharedUserIds();
+
+ /**
+ * Get all packages which share the same userId as the specified package, or an empty array
+ * if the package does not have a shared userId.
+ */
+ @NonNull
+ public abstract String[] getSharedUserPackagesForPackage(@NonNull String packageName,
+ int userId);
+
+ /**
+ * Return the processes that have been declared for a uid.
+ *
+ * @param uid The uid to query.
+ *
+ * @return Returns null if there are no declared processes for the uid; otherwise,
+ * returns the set of processes it declared.
+ */
+ public abstract ArrayMap<String, ProcessInfo> getProcessesForUid(int uid);
+
+ /**
+ * Return the gids associated with a particular permission.
+ *
+ * @param permissionName The name of the permission to query.
+ * @param userId The user id the gids will be associated with.
+ *
+ * @return Returns null if there are no gids associated with the permission, otherwise an
+ * array if the gid ints.
+ */
+ public abstract int[] getPermissionGids(String permissionName, int userId);
+
+ /**
+ * Return if device is currently in a "core" boot environment, typically
+ * used to support full-disk encryption. Only apps marked with
+ * {@code coreApp} attribute are available.
+ */
+ public abstract boolean isOnlyCoreApps();
+
+ /**
+ * Make a best-effort attempt to provide the requested free disk space by
+ * deleting cached files.
+ *
+ * @throws IOException if the request was unable to be fulfilled.
+ */
+ public abstract void freeStorage(String volumeUuid, long bytes, int storageFlags)
+ throws IOException;
+
+ /** Returns {@code true} if the specified component is enabled and matches the given flags. */
+ public abstract boolean isEnabledAndMatches(@NonNull ParsedMainComponent component, int flags,
+ int userId);
+
+ /** Returns {@code true} if the given user requires extra badging for icons. */
+ public abstract boolean userNeedsBadging(int userId);
+
+ /**
+ * Perform the given action for each package.
+ * Note that packages lock will be held while performing the actions.
+ *
+ * If the caller does not need all packages, prefer the potentially non-locking
+ * {@link #withPackageSettingsSnapshot(Consumer)}.
+ *
+ * @param actionLocked action to be performed
+ */
+ public abstract void forEachPackage(Consumer<AndroidPackage> actionLocked);
+
+ /**
+ * Perform the given action for each {@link PackageSetting}.
+ * Note that packages lock will be held while performing the actions.
+ *
+ * If the caller does not need all packages, prefer the potentially non-locking
+ * {@link #withPackageSettingsSnapshot(Consumer)}.
+ *
+ * @param actionLocked action to be performed
+ */
+ public abstract void forEachPackageSetting(Consumer<PackageSetting> actionLocked);
+
+ /**
+ * Perform the given action for each installed package for a user.
+ * Note that packages lock will be held while performin the actions.
+ */
+ public abstract void forEachInstalledPackage(
+ @NonNull Consumer<AndroidPackage> actionLocked, @UserIdInt int userId);
+
+ /** Returns the list of enabled components */
+ public abstract ArraySet<String> getEnabledComponents(String packageName, int userId);
+
+ /** Returns the list of disabled components */
+ public abstract ArraySet<String> getDisabledComponents(String packageName, int userId);
+
+ /** Returns whether the given package is enabled for the given user */
+ public abstract @PackageManager.EnabledState int getApplicationEnabledState(
+ String packageName, int userId);
+
+ /**
+ * Return the enabled setting for a package component (activity, receiver, service, provider).
+ */
+ public abstract @PackageManager.EnabledState int getComponentEnabledSetting(
+ @NonNull ComponentName componentName, int callingUid, int userId);
+
+ /**
+ * Extra field name for the token of a request to enable rollback for a
+ * package.
+ */
+ public static final String EXTRA_ENABLE_ROLLBACK_TOKEN =
+ "android.content.pm.extra.ENABLE_ROLLBACK_TOKEN";
+
+ /**
+ * Extra field name for the session id of a request to enable rollback
+ * for a package.
+ */
+ public static final String EXTRA_ENABLE_ROLLBACK_SESSION_ID =
+ "android.content.pm.extra.ENABLE_ROLLBACK_SESSION_ID";
+
+ /**
+ * Used as the {@code enableRollbackCode} argument for
+ * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
+ * enabling rollback succeeded.
+ */
+ public static final int ENABLE_ROLLBACK_SUCCEEDED = 1;
+
+ /**
+ * Used as the {@code enableRollbackCode} argument for
+ * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
+ * enabling rollback failed.
+ */
+ public static final int ENABLE_ROLLBACK_FAILED = -1;
+
+ /**
+ * Allows the rollback manager listening to the
+ * {@link Intent#ACTION_PACKAGE_ENABLE_ROLLBACK enable rollback broadcast}
+ * to respond to the package manager. The response must include the
+ * {@code enableRollbackCode} which is one of
+ * {@link PackageManager#ENABLE_ROLLBACK_SUCCEEDED} or
+ * {@link PackageManager#ENABLE_ROLLBACK_FAILED}.
+ *
+ * @param token pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_ENABLE_ROLLBACK_TOKEN} Intent extra.
+ * @param enableRollbackCode the status code result of enabling rollback
+ * @throws SecurityException if the caller does not have the
+ * PACKAGE_ROLLBACK_AGENT permission.
+ */
+ public abstract void setEnableRollbackCode(int token, int enableRollbackCode);
+
+ /**
+ * Ask the package manager to compile layouts in the given package.
+ */
+ public abstract boolean compileLayouts(String packageName);
+
+ /*
+ * Inform the package manager that the pending package install identified by
+ * {@code token} can be completed.
+ */
+ public abstract void finishPackageInstall(int token, boolean didLaunch);
+
+ /**
+ * Remove the default browser stored in the legacy package settings.
+ *
+ * @param userId the user id
+ *
+ * @return the package name of the default browser, or {@code null} if none
+ */
+ @Nullable
+ public abstract String removeLegacyDefaultBrowserPackageName(int userId);
+
+ /**
+ * Returns {@code true} if given {@code packageName} is an apex package.
+ */
+ public abstract boolean isApexPackage(String packageName);
+
+ /**
+ * Returns list of {@code packageName} of apks inside the given apex.
+ * @param apexPackageName Package name of the apk container of apex
+ */
+ public abstract List<String> getApksInApex(String apexPackageName);
+
+ /**
+ * Uninstalls given {@code packageName}.
+ *
+ * @param packageName apex package to uninstall.
+ * @param versionCode version of a package to uninstall.
+ * @param userId user to uninstall apex package for. Must be
+ * {@link android.os.UserHandle#USER_ALL}, otherwise failure will be reported.
+ * @param intentSender a {@link IntentSender} to send result of an uninstall to.
+ * @param flags flags about the uninstall.
+ */
+ public abstract void uninstallApex(String packageName, long versionCode, int userId,
+ IntentSender intentSender, int flags);
+
+ /**
+ * Update fingerprint of build that updated the runtime permissions for a user.
+ *
+ * @param userId The user to update
+ */
+ public abstract void updateRuntimePermissionsFingerprint(@UserIdInt int userId);
+
+ /**
+ * Migrates legacy obb data to its new location.
+ */
+ public abstract void migrateLegacyObbData();
+
+ /**
+ * Writes all package manager settings to disk. If {@code async} is {@code true}, the
+ * settings are written at some point in the future. Otherwise, the call blocks until
+ * the settings have been written.
+ */
+ public abstract void writeSettings(boolean async);
+
+ /**
+ * Writes all permission settings for the given set of users to disk. If {@code async}
+ * is {@code true}, the settings are written at some point in the future. Otherwise,
+ * the call blocks until the settings have been written.
+ */
+ public abstract void writePermissionSettings(@NonNull @UserIdInt int[] userIds, boolean async);
+
+ /**
+ * Returns {@code true} if the caller is the installer of record for the given package.
+ * Otherwise, {@code false}.
+ */
+ public abstract boolean isCallerInstallerOfRecord(
+ @NonNull AndroidPackage pkg, int callingUid);
+
+ /** Returns whether or not permissions need to be upgraded for the given user */
+ public abstract boolean isPermissionUpgradeNeeded(@UserIdInt int userId);
+
+ /**
+ * Allows the integrity component to respond to the
+ * {@link Intent#ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification
+ * broadcast} to respond to the package manager. The response must include
+ * the {@code verificationCode} which is one of
+ * {@link #INTEGRITY_VERIFICATION_ALLOW} and {@link #INTEGRITY_VERIFICATION_REJECT}.
+ *
+ * @param verificationId pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationResult either {@link #INTEGRITY_VERIFICATION_ALLOW}
+ * or {@link #INTEGRITY_VERIFICATION_REJECT}.
+ */
+ public abstract void setIntegrityVerificationResult(int verificationId,
+ @IntegrityVerificationResult int verificationResult);
+
+ /**
+ * Returns MIME types contained in {@code mimeGroup} from {@code packageName} package
+ */
+ public abstract List<String> getMimeGroup(String packageName, String mimeGroup);
+
+ /**
+ * Toggles visibility logging to help in debugging the app enumeration feature.
+ * @param packageName the package name that should begin logging
+ * @param enabled true if visibility blocks should be logged
+ */
+ public abstract void setVisibilityLogging(String packageName, boolean enabled);
+
+ /**
+ * Returns if a package name is a valid system package.
+ */
+ public abstract boolean isSystemPackage(@NonNull String packageName);
+
+ /**
+ * Unblocks uninstall for all packages for the user.
+ */
+ public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
+
+ /**
+ * Unsuspends all packages suspended by the given package for the user.
+ */
+ public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId);
+
+ /**
+ * Returns {@code true} if the package is suspending any packages for the user.
+ */
+ public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
+
+ /**
+ * Register to listen for loading progress of an installed package.
+ * The listener is automatically unregistered when the app is fully loaded.
+ * @param packageName The name of the installed package
+ * @param callback To loading reporting progress
+ * @param userId The user under which to check.
+ * @return Whether the registration was successful. It can fail if the package has not been
+ * installed yet.
+ */
+ public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName,
+ @NonNull InstalledLoadingProgressCallback callback, int userId);
+
+ /**
+ * Returns the string representation of a known package. For example,
+ * {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard.
+ *
+ * @param knownPackage The known package.
+ * @return The string representation.
+ */
+ public static @NonNull String knownPackageToString(@KnownPackage int knownPackage) {
+ switch (knownPackage) {
+ case PACKAGE_SYSTEM:
+ return "System";
+ case PACKAGE_SETUP_WIZARD:
+ return "Setup Wizard";
+ case PACKAGE_INSTALLER:
+ return "Installer";
+ case PACKAGE_VERIFIER:
+ return "Verifier";
+ case PACKAGE_BROWSER:
+ return "Browser";
+ case PACKAGE_SYSTEM_TEXT_CLASSIFIER:
+ return "System Text Classifier";
+ case PACKAGE_PERMISSION_CONTROLLER:
+ return "Permission Controller";
+ case PACKAGE_WELLBEING:
+ return "Wellbeing";
+ case PACKAGE_DOCUMENTER:
+ return "Documenter";
+ case PACKAGE_CONFIGURATOR:
+ return "Configurator";
+ case PACKAGE_INCIDENT_REPORT_APPROVER:
+ return "Incident Report Approver";
+ case PACKAGE_APP_PREDICTOR:
+ return "App Predictor";
+ case PACKAGE_WIFI:
+ return "Wi-Fi";
+ case PACKAGE_COMPANION:
+ return "Companion";
+ case PACKAGE_RETAIL_DEMO:
+ return "Retail Demo";
+ case PACKAGE_OVERLAY_CONFIG_SIGNATURE:
+ return "Overlay Config Signature";
+ case PACKAGE_RECENTS:
+ return "Recents";
+ }
+ return "Unknown";
+ }
+
+ /**
+ * Callback to listen for loading progress of a package installed on Incremental File System.
+ */
+ public abstract static class InstalledLoadingProgressCallback {
+ final LoadingProgressCallbackBinder mBinder = new LoadingProgressCallbackBinder();
+ final Executor mExecutor;
+ /**
+ * Default constructor that should always be called on subclass instantiation
+ * @param handler To dispatch callback events through. If null, the main thread
+ * handler will be used.
+ */
+ public InstalledLoadingProgressCallback(@Nullable Handler handler) {
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ mExecutor = new HandlerExecutor(handler);
+ }
+
+ /**
+ * Binder used by Package Manager Service to register as a callback
+ * @return the binder object of IPackageLoadingProgressCallback
+ */
+ public final @NonNull IBinder getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * Report loading progress of an installed package.
+ *
+ * @param progress Loading progress between [0, 1] for the registered package.
+ */
+ public abstract void onLoadingProgressChanged(float progress);
+
+ private class LoadingProgressCallbackBinder extends
+ android.content.pm.IPackageLoadingProgressCallback.Stub {
+ @Override
+ public void onPackageLoadingProgressChanged(float progress) {
+ mExecutor.execute(PooledLambda.obtainRunnable(
+ InstalledLoadingProgressCallback::onLoadingProgressChanged,
+ InstalledLoadingProgressCallback.this,
+ progress).recycleOnUse());
+ }
+ }
+ }
+
+ /**
+ * Retrieve all of the information we know about a particular activity class including its
+ * package states.
+ *
+ * @param packageName a specific package
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @param userId The user for whom the package is installed
+ * @return IncrementalStatesInfo that contains information about package states.
+ */
+ public abstract IncrementalStatesInfo getIncrementalStatesInfo(String packageName,
+ int filterCallingUid, int userId);
+
+ /**
+ * Requesting the checksums for APKs within a package.
+ * See {@link PackageManager#requestChecksums} for details.
+ *
+ * @param executor to use for digest calculations.
+ * @param handler to use for postponed calculations.
+ */
+ public abstract void requestChecksums(@NonNull String packageName, boolean includeSplits,
+ @Checksum.TypeMask int optional, @Checksum.TypeMask int required,
+ @Nullable List trustedInstallers,
+ @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId,
+ @NonNull Executor executor, @NonNull Handler handler);
+
+ /**
+ * Returns true if the given {@code packageName} and {@code userId} is frozen.
+ *
+ * @param packageName a specific package
+ * @param callingUid The uid of the caller
+ * @param userId The user for whom the package is installed
+ * @return {@code true} If the package is current frozen (due to install/update etc.)
+ */
+ public abstract boolean isPackageFrozen(
+ @NonNull String packageName, int callingUid, int userId);
+
+ /**
+ * Deletes the OAT artifacts of a package.
+ * @param packageName a specific package
+ * @return the number of freed bytes or -1 if there was an error in the process.
+ */
+ public abstract long deleteOatArtifactsOfPackage(String packageName);
+}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
new file mode 100644
index 0000000..4ff2624
--- /dev/null
+++ b/android/content/pm/PackageParser.java
@@ -0,0 +1,9102 @@
+/*
+ * Copyright (C) 2007 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.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.apex.ApexInfo;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
+import android.app.ResourcesManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.overlay.OverlayPaths;
+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;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.permission.PermissionManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Base64;
+import android.util.DisplayMetrics;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.PackageUtils;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.apk.ApkSignatureVerifier;
+import android.view.Gravity;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ClassLoaderFactory;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Parser for package files (APKs) on disk. This supports apps packaged either
+ * as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
+ * APKs in a single directory.
+ * <p>
+ * Apps packaged as multiple APKs always consist of a single "base" APK (with a
+ * {@code null} split name) and zero or more "split" APKs (with unique split
+ * names). Any subset of those split APKs are a valid install, as long as the
+ * following constraints are met:
+ * <ul>
+ * <li>All APKs must have the exact same package name, version code, and signing
+ * certificates.
+ * <li>All APKs must have unique split names.
+ * <li>All installations must contain a single base APK.
+ * </ul>
+ *
+ * @deprecated This class is mostly unused and no new changes should be added to it. Use
+ * {@link android.content.pm.parsing.ParsingPackageUtils} and related parsing v2 infrastructure in
+ * the core/services parsing subpackages. Or for a quick parse of a provided APK, use
+ * {@link PackageManager#getPackageArchiveInfo(String, int)}.
+ *
+ * @hide
+ */
+@Deprecated
+public class PackageParser {
+
+ public static final boolean DEBUG_JAR = false;
+ public static final boolean DEBUG_PARSER = false;
+ public static final boolean DEBUG_BACKUP = false;
+ public static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
+ public static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
+
+ private static final String PROPERTY_CHILD_PACKAGES_ENABLED =
+ "persist.sys.child_packages_enabled";
+
+ public static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE &&
+ SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false);
+
+ public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
+ private static final int DEFAULT_MIN_SDK_VERSION = 1;
+ private static final int DEFAULT_TARGET_SDK_VERSION = 0;
+
+ // TODO: switch outError users to PackageParserException
+ // TODO: refactor "codePath" to "apkPath"
+
+ /** File name in an APK for the Android manifest. */
+ public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+
+ /** Path prefix for apps on expanded storage */
+ public static final String MNT_EXPAND = "/mnt/expand/";
+
+ public static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";
+ public static final String TAG_APPLICATION = "application";
+ public static final String TAG_COMPATIBLE_SCREENS = "compatible-screens";
+ public static final String TAG_EAT_COMMENT = "eat-comment";
+ public static final String TAG_FEATURE_GROUP = "feature-group";
+ public static final String TAG_INSTRUMENTATION = "instrumentation";
+ public static final String TAG_KEY_SETS = "key-sets";
+ public static final String TAG_MANIFEST = "manifest";
+ public static final String TAG_ORIGINAL_PACKAGE = "original-package";
+ public static final String TAG_OVERLAY = "overlay";
+ public static final String TAG_PACKAGE = "package";
+ public static final String TAG_PACKAGE_VERIFIER = "package-verifier";
+ public static final String TAG_ATTRIBUTION = "attribution";
+ public static final String TAG_PERMISSION = "permission";
+ public static final String TAG_PERMISSION_GROUP = "permission-group";
+ public static final String TAG_PERMISSION_TREE = "permission-tree";
+ public static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";
+ public static final String TAG_QUERIES = "queries";
+ public static final String TAG_RESTRICT_UPDATE = "restrict-update";
+ public static final String TAG_SUPPORT_SCREENS = "supports-screens";
+ public static final String TAG_SUPPORTS_INPUT = "supports-input";
+ public static final String TAG_USES_CONFIGURATION = "uses-configuration";
+ public static final String TAG_USES_FEATURE = "uses-feature";
+ public static final String TAG_USES_GL_TEXTURE = "uses-gl-texture";
+ public static final String TAG_USES_PERMISSION = "uses-permission";
+ public static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23";
+ public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";
+ public static final String TAG_USES_SDK = "uses-sdk";
+ public static final String TAG_USES_SPLIT = "uses-split";
+ public static final String TAG_PROFILEABLE = "profileable";
+
+ public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
+ public static final String METADATA_SUPPORTS_SIZE_CHANGES = "android.supports_size_changes";
+ public static final String METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY =
+ "android.activity_window_layout_affinity";
+
+ /**
+ * Bit mask of all the valid bits that can be set in recreateOnConfigChanges.
+ * @hide
+ */
+ private static final int RECREATE_ON_CONFIG_CHANGES_MASK =
+ ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
+
+ // These are the tags supported by child packages
+ public static final Set<String> CHILD_PACKAGE_TAGS = new ArraySet<>();
+ static {
+ CHILD_PACKAGE_TAGS.add(TAG_APPLICATION);
+ CHILD_PACKAGE_TAGS.add(TAG_COMPATIBLE_SCREENS);
+ CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT);
+ CHILD_PACKAGE_TAGS.add(TAG_FEATURE_GROUP);
+ CHILD_PACKAGE_TAGS.add(TAG_INSTRUMENTATION);
+ CHILD_PACKAGE_TAGS.add(TAG_SUPPORT_SCREENS);
+ CHILD_PACKAGE_TAGS.add(TAG_SUPPORTS_INPUT);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_CONFIGURATION);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_FEATURE);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_GL_TEXTURE);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_23);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_M);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_SDK);
+ }
+
+ public static final boolean LOG_UNSAFE_BROADCASTS = false;
+
+ // Set of broadcast actions that are safe for manifest receivers
+ public static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
+ static {
+ SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
+ }
+
+ /** @hide */
+ public static final String APK_FILE_EXTENSION = ".apk";
+ /** @hide */
+ public static final String APEX_FILE_EXTENSION = ".apex";
+
+ /** @hide */
+ public static class NewPermissionInfo {
+ @UnsupportedAppUsage
+ public final String name;
+ @UnsupportedAppUsage
+ public final int sdkVersion;
+ public final int fileVersion;
+
+ public NewPermissionInfo(String name, int sdkVersion, int fileVersion) {
+ this.name = name;
+ this.sdkVersion = sdkVersion;
+ this.fileVersion = fileVersion;
+ }
+ }
+
+ /**
+ * List of new permissions that have been added since 1.0.
+ * NOTE: These must be declared in SDK version order, with permissions
+ * added to older SDKs appearing before those added to newer SDKs.
+ * If sdkVersion is 0, then this is not a permission that we want to
+ * automatically add to older apps, but we do want to allow it to be
+ * granted during a platform update.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] =
+ new PackageParser.NewPermissionInfo[] {
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ android.os.Build.VERSION_CODES.DONUT, 0),
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE,
+ android.os.Build.VERSION_CODES.DONUT, 0)
+ };
+
+ /**
+ * @deprecated callers should move to explicitly passing around source path.
+ */
+ @Deprecated
+ public String mArchiveSourcePath;
+
+ public String[] mSeparateProcesses;
+ private boolean mOnlyCoreApps;
+ private DisplayMetrics mMetrics;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Callback mCallback;
+ private File mCacheDir;
+
+ public static final int SDK_VERSION = Build.VERSION.SDK_INT;
+ public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+
+ public int mParseError = PackageManager.INSTALL_SUCCEEDED;
+
+ public static boolean sCompatibilityModeEnabled = true;
+ public static boolean sUseRoundIcon = false;
+
+ public static final int PARSE_DEFAULT_INSTALL_LOCATION =
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1;
+
+ static class ParsePackageItemArgs {
+ final Package owner;
+ final String[] outError;
+ final int nameRes;
+ final int labelRes;
+ final int iconRes;
+ final int roundIconRes;
+ final int logoRes;
+ final int bannerRes;
+
+ String tag;
+ TypedArray sa;
+
+ ParsePackageItemArgs(Package _owner, String[] _outError,
+ int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes,
+ int _bannerRes) {
+ owner = _owner;
+ outError = _outError;
+ nameRes = _nameRes;
+ labelRes = _labelRes;
+ iconRes = _iconRes;
+ logoRes = _logoRes;
+ bannerRes = _bannerRes;
+ roundIconRes = _roundIconRes;
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static class ParseComponentArgs extends ParsePackageItemArgs {
+ final String[] sepProcesses;
+ final int processRes;
+ final int descriptionRes;
+ final int enabledRes;
+ int flags;
+
+ public ParseComponentArgs(Package _owner, String[] _outError,
+ int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes,
+ int _bannerRes,
+ String[] _sepProcesses, int _processRes,
+ int _descriptionRes, int _enabledRes) {
+ super(_owner, _outError, _nameRes, _labelRes, _iconRes, _roundIconRes, _logoRes,
+ _bannerRes);
+ sepProcesses = _sepProcesses;
+ processRes = _processRes;
+ descriptionRes = _descriptionRes;
+ enabledRes = _enabledRes;
+ }
+ }
+
+ /**
+ * Lightweight parsed details about a single package.
+ */
+ public static class PackageLite {
+ @UnsupportedAppUsage
+ public final String packageName;
+ public final int versionCode;
+ public final int versionCodeMajor;
+ @UnsupportedAppUsage
+ public final int installLocation;
+ public final VerifierInfo[] verifiers;
+
+ /** Names of any split APKs, ordered by parsed splitName */
+ public final String[] splitNames;
+
+ /** Names of any split APKs that are features. Ordered by splitName */
+ public final boolean[] isFeatureSplits;
+
+ /** Dependencies of any split APKs, ordered by parsed splitName */
+ public final String[] usesSplitNames;
+ public final String[] configForSplit;
+
+ /**
+ * Path where this package was found on disk. For monolithic packages
+ * this is path to single base APK file; for cluster packages this is
+ * path to the cluster directory.
+ */
+ public final String codePath;
+
+ /** Path of base APK */
+ public final String baseCodePath;
+ /** Paths of any split APKs, ordered by parsed splitName */
+ public final String[] splitCodePaths;
+
+ /** Revision code of base APK */
+ public final int baseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ public final int[] splitRevisionCodes;
+
+ public final boolean coreApp;
+ public final boolean debuggable;
+ public final boolean multiArch;
+ public final boolean use32bitAbi;
+ public final boolean extractNativeLibs;
+ public final boolean isolatedSplits;
+
+ // This does not represent the actual manifest structure since the 'profilable' tag
+ // could be used with attributes other than 'shell'. Extend if necessary.
+ public final boolean profilableByShell;
+ public final boolean isSplitRequired;
+ public final boolean useEmbeddedDex;
+
+ public PackageLite(String codePath, String baseCodePath, ApkLite baseApk,
+ String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
+ String[] configForSplit, String[] splitCodePaths, int[] splitRevisionCodes) {
+ this.packageName = baseApk.packageName;
+ this.versionCode = baseApk.versionCode;
+ this.versionCodeMajor = baseApk.versionCodeMajor;
+ this.installLocation = baseApk.installLocation;
+ this.verifiers = baseApk.verifiers;
+ this.splitNames = splitNames;
+ this.isFeatureSplits = isFeatureSplits;
+ this.usesSplitNames = usesSplitNames;
+ this.configForSplit = configForSplit;
+ // The following paths may be different from the path in ApkLite because we
+ // move or rename the APK files. Use parameters to indicate the correct paths.
+ this.codePath = codePath;
+ this.baseCodePath = baseCodePath;
+ this.splitCodePaths = splitCodePaths;
+ this.baseRevisionCode = baseApk.revisionCode;
+ this.splitRevisionCodes = splitRevisionCodes;
+ this.coreApp = baseApk.coreApp;
+ this.debuggable = baseApk.debuggable;
+ this.multiArch = baseApk.multiArch;
+ this.use32bitAbi = baseApk.use32bitAbi;
+ this.extractNativeLibs = baseApk.extractNativeLibs;
+ this.isolatedSplits = baseApk.isolatedSplits;
+ this.useEmbeddedDex = baseApk.useEmbeddedDex;
+ this.isSplitRequired = baseApk.isSplitRequired;
+ this.profilableByShell = baseApk.profilableByShell;
+ }
+
+ public List<String> getAllCodePaths() {
+ ArrayList<String> paths = new ArrayList<>();
+ paths.add(baseCodePath);
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ Collections.addAll(paths, splitCodePaths);
+ }
+ return paths;
+ }
+
+ public long getLongVersionCode() {
+ return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode);
+ }
+ }
+
+ /**
+ * Lightweight parsed details about a single APK file.
+ */
+ public static class ApkLite {
+ public final String codePath;
+ public final String packageName;
+ public final String splitName;
+ public boolean isFeatureSplit;
+ public final String configForSplit;
+ public final String usesSplitName;
+ public final int versionCode;
+ public final int versionCodeMajor;
+ public final int revisionCode;
+ public final int installLocation;
+ public final int minSdkVersion;
+ public final int targetSdkVersion;
+ public final VerifierInfo[] verifiers;
+ public final SigningDetails signingDetails;
+ public final boolean coreApp;
+ public final boolean debuggable;
+ // This does not represent the actual manifest structure since the 'profilable' tag
+ // could be used with attributes other than 'shell'. Extend if necessary.
+ public final boolean profilableByShell;
+ public final boolean multiArch;
+ public final boolean use32bitAbi;
+ public final boolean extractNativeLibs;
+ public final boolean isolatedSplits;
+ public final boolean isSplitRequired;
+ public final boolean useEmbeddedDex;
+ public final String targetPackageName;
+ public final boolean overlayIsStatic;
+ public final int overlayPriority;
+ public final int rollbackDataPolicy;
+
+ public ApkLite(String codePath, String packageName, String splitName,
+ boolean isFeatureSplit,
+ String configForSplit, String usesSplitName, boolean isSplitRequired,
+ int versionCode, int versionCodeMajor,
+ int revisionCode, int installLocation, List<VerifierInfo> verifiers,
+ SigningDetails signingDetails, boolean coreApp,
+ boolean debuggable, boolean profilableByShell, boolean multiArch,
+ boolean use32bitAbi, boolean useEmbeddedDex, boolean extractNativeLibs,
+ boolean isolatedSplits, String targetPackageName, boolean overlayIsStatic,
+ int overlayPriority, int minSdkVersion, int targetSdkVersion,
+ int rollbackDataPolicy) {
+ this.codePath = codePath;
+ this.packageName = packageName;
+ this.splitName = splitName;
+ this.isFeatureSplit = isFeatureSplit;
+ this.configForSplit = configForSplit;
+ this.usesSplitName = usesSplitName;
+ this.versionCode = versionCode;
+ this.versionCodeMajor = versionCodeMajor;
+ this.revisionCode = revisionCode;
+ this.installLocation = installLocation;
+ this.signingDetails = signingDetails;
+ this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
+ this.coreApp = coreApp;
+ this.debuggable = debuggable;
+ this.profilableByShell = profilableByShell;
+ this.multiArch = multiArch;
+ this.use32bitAbi = use32bitAbi;
+ this.useEmbeddedDex = useEmbeddedDex;
+ this.extractNativeLibs = extractNativeLibs;
+ this.isolatedSplits = isolatedSplits;
+ this.isSplitRequired = isSplitRequired;
+ this.targetPackageName = targetPackageName;
+ this.overlayIsStatic = overlayIsStatic;
+ this.overlayPriority = overlayPriority;
+ this.minSdkVersion = minSdkVersion;
+ this.targetSdkVersion = targetSdkVersion;
+ this.rollbackDataPolicy = rollbackDataPolicy;
+ }
+
+ public long getLongVersionCode() {
+ return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode);
+ }
+ }
+
+ /**
+ * Cached parse state for new components.
+ *
+ * Allows reuse of the same parse argument records to avoid GC pressure. Lifetime is carefully
+ * scoped to the parsing of a single application element.
+ */
+ private static class CachedComponentArgs {
+ ParseComponentArgs mActivityArgs;
+ ParseComponentArgs mActivityAliasArgs;
+ ParseComponentArgs mServiceArgs;
+ ParseComponentArgs mProviderArgs;
+ }
+
+ /**
+ * Cached state for parsing instrumentation to avoid GC pressure.
+ *
+ * Must be manually reset to null for each new manifest.
+ */
+ private ParsePackageItemArgs mParseInstrumentationArgs;
+
+ /** If set to true, we will only allow package files that exactly match
+ * the DTD. Otherwise, we try to get as much from the package as we
+ * can without failing. This should normally be set to false, to
+ * support extensions to the DTD in future versions. */
+ public static final boolean RIGID_PARSER = false;
+
+ private static final String TAG = "PackageParser";
+
+ @UnsupportedAppUsage
+ public PackageParser() {
+ mMetrics = new DisplayMetrics();
+ mMetrics.setToDefaults();
+ }
+
+ @UnsupportedAppUsage
+ public void setSeparateProcesses(String[] procs) {
+ mSeparateProcesses = procs;
+ }
+
+ /**
+ * Flag indicating this parser should only consider apps with
+ * {@code coreApp} manifest attribute to be valid apps. This is useful when
+ * creating a minimalist boot environment.
+ */
+ public void setOnlyCoreApps(boolean onlyCoreApps) {
+ mOnlyCoreApps = onlyCoreApps;
+ }
+
+ public void setDisplayMetrics(DisplayMetrics metrics) {
+ mMetrics = metrics;
+ }
+
+ /**
+ * Sets the cache directory for this package parser.
+ */
+ public void setCacheDir(File cacheDir) {
+ mCacheDir = cacheDir;
+ }
+
+ /**
+ * Callback interface for retrieving information that may be needed while parsing
+ * a package.
+ */
+ public interface Callback {
+ boolean hasFeature(String feature);
+ }
+
+ /**
+ * Standard implementation of {@link Callback} on top of the public {@link PackageManager}
+ * class.
+ */
+ public static final class CallbackImpl implements Callback {
+ private final PackageManager mPm;
+
+ public CallbackImpl(PackageManager pm) {
+ mPm = pm;
+ }
+
+ @Override public boolean hasFeature(String feature) {
+ return mPm.hasSystemFeature(feature);
+ }
+ }
+
+ /**
+ * Set the {@link Callback} that can be used while parsing.
+ */
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ public static final boolean isApkFile(File file) {
+ return isApkPath(file.getName());
+ }
+
+ public static boolean isApkPath(String path) {
+ return path.endsWith(APK_FILE_EXTENSION);
+ }
+
+ /**
+ * Returns true if the package is installed and not hidden, or if the caller
+ * explicitly wanted all uninstalled and hidden packages as well.
+ * @param appInfo The applicationInfo of the app being checked.
+ */
+ private static boolean checkUseInstalledOrHidden(int flags, PackageUserState state,
+ ApplicationInfo appInfo) {
+ // Returns false if the package is hidden system app until installed.
+ if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
+ && !state.installed
+ && appInfo != null && appInfo.hiddenUntilInstalled) {
+ return false;
+ }
+
+ // If available for the target user, or trying to match uninstalled packages and it's
+ // a system app.
+ return state.isAvailable(flags)
+ || (appInfo != null && appInfo.isSystemApp()
+ && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
+ || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
+ }
+
+ public static boolean isAvailable(PackageUserState state) {
+ return checkUseInstalledOrHidden(0, state, null);
+ }
+
+ /**
+ * Generate and return the {@link PackageInfo} for a parsed package.
+ *
+ * @param p the parsed package.
+ * @param flags indicating which optional information is included.
+ */
+ @UnsupportedAppUsage
+ public static PackageInfo generatePackageInfo(PackageParser.Package p,
+ int[] gids, int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state) {
+
+ return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
+ grantedPermissions, state, UserHandle.getCallingUserId());
+ }
+
+ @UnsupportedAppUsage
+ public static PackageInfo generatePackageInfo(PackageParser.Package p,
+ int[] gids, int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state, int userId) {
+
+ return generatePackageInfo(p, null, gids, flags, firstInstallTime, lastUpdateTime,
+ grantedPermissions, state, userId);
+ }
+
+ /**
+ * PackageInfo generator specifically for apex files.
+ *
+ * @param pkg Package to generate info from. Should be derived from an apex.
+ * @param apexInfo Apex info relating to the package.
+ * @return PackageInfo
+ * @throws PackageParserException
+ */
+ public static PackageInfo generatePackageInfo(
+ PackageParser.Package pkg, ApexInfo apexInfo, int flags) {
+ return generatePackageInfo(pkg, apexInfo, EmptyArray.INT, flags, 0, 0,
+ Collections.emptySet(), new PackageUserState(), UserHandle.getCallingUserId());
+ }
+
+ private static PackageInfo generatePackageInfo(PackageParser.Package p, ApexInfo apexInfo,
+ int gids[], int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state, int userId) {
+ if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
+ return null;
+ }
+ PackageInfo pi = new PackageInfo();
+ pi.packageName = p.packageName;
+ pi.splitNames = p.splitNames;
+ pi.versionCode = p.mVersionCode;
+ pi.versionCodeMajor = p.mVersionCodeMajor;
+ pi.baseRevisionCode = p.baseRevisionCode;
+ pi.splitRevisionCodes = p.splitRevisionCodes;
+ pi.versionName = p.mVersionName;
+ pi.sharedUserId = p.mSharedUserId;
+ pi.sharedUserLabel = p.mSharedUserLabel;
+ pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
+ pi.installLocation = p.installLocation;
+ pi.isStub = p.isStub;
+ pi.coreApp = p.coreApp;
+ if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
+ || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ pi.requiredForAllUsers = p.mRequiredForAllUsers;
+ }
+ pi.restrictedAccountType = p.mRestrictedAccountType;
+ pi.requiredAccountType = p.mRequiredAccountType;
+ pi.overlayTarget = p.mOverlayTarget;
+ pi.targetOverlayableName = p.mOverlayTargetName;
+ pi.overlayCategory = p.mOverlayCategory;
+ pi.overlayPriority = p.mOverlayPriority;
+ pi.mOverlayIsStatic = p.mOverlayIsStatic;
+ pi.compileSdkVersion = p.mCompileSdkVersion;
+ pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
+ pi.firstInstallTime = firstInstallTime;
+ pi.lastUpdateTime = lastUpdateTime;
+ if ((flags&PackageManager.GET_GIDS) != 0) {
+ pi.gids = gids;
+ }
+ if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
+ int N = p.configPreferences != null ? p.configPreferences.size() : 0;
+ if (N > 0) {
+ pi.configPreferences = new ConfigurationInfo[N];
+ p.configPreferences.toArray(pi.configPreferences);
+ }
+ N = p.reqFeatures != null ? p.reqFeatures.size() : 0;
+ if (N > 0) {
+ pi.reqFeatures = new FeatureInfo[N];
+ p.reqFeatures.toArray(pi.reqFeatures);
+ }
+ N = p.featureGroups != null ? p.featureGroups.size() : 0;
+ if (N > 0) {
+ pi.featureGroups = new FeatureGroupInfo[N];
+ p.featureGroups.toArray(pi.featureGroups);
+ }
+ }
+ if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+ final int N = p.activities.size();
+ if (N > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Activity a = p.activities.get(i);
+ if (state.isMatch(a.info, flags)) {
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
+ continue;
+ }
+ res[num++] = generateActivityInfo(a, flags, state, userId);
+ }
+ }
+ pi.activities = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+ final int N = p.receivers.size();
+ if (N > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Activity a = p.receivers.get(i);
+ if (state.isMatch(a.info, flags)) {
+ res[num++] = generateActivityInfo(a, flags, state, userId);
+ }
+ }
+ pi.receivers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_SERVICES) != 0) {
+ final int N = p.services.size();
+ if (N > 0) {
+ int num = 0;
+ final ServiceInfo[] res = new ServiceInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Service s = p.services.get(i);
+ if (state.isMatch(s.info, flags)) {
+ res[num++] = generateServiceInfo(s, flags, state, userId);
+ }
+ }
+ pi.services = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+ final int N = p.providers.size();
+ if (N > 0) {
+ int num = 0;
+ final ProviderInfo[] res = new ProviderInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Provider pr = p.providers.get(i);
+ if (state.isMatch(pr.info, flags)) {
+ res[num++] = generateProviderInfo(pr, flags, state, userId);
+ }
+ }
+ pi.providers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
+ int N = p.instrumentation.size();
+ if (N > 0) {
+ pi.instrumentation = new InstrumentationInfo[N];
+ for (int i=0; i<N; i++) {
+ pi.instrumentation[i] = generateInstrumentationInfo(
+ p.instrumentation.get(i), flags);
+ }
+ }
+ }
+ if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
+ int N = p.permissions.size();
+ if (N > 0) {
+ pi.permissions = new PermissionInfo[N];
+ for (int i=0; i<N; i++) {
+ pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
+ }
+ }
+ N = p.requestedPermissions.size();
+ if (N > 0) {
+ pi.requestedPermissions = new String[N];
+ pi.requestedPermissionsFlags = new int[N];
+ for (int i=0; i<N; i++) {
+ final String perm = p.requestedPermissions.get(i);
+ pi.requestedPermissions[i] = perm;
+ // The notion of required permissions is deprecated but for compatibility.
+ pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+ if (grantedPermissions != null && grantedPermissions.contains(perm)) {
+ pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
+ }
+ }
+ }
+ }
+
+ if (apexInfo != null) {
+ File apexFile = new File(apexInfo.modulePath);
+
+ pi.applicationInfo.sourceDir = apexFile.getPath();
+ pi.applicationInfo.publicSourceDir = apexFile.getPath();
+ if (apexInfo.isFactory) {
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ } else {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
+ }
+ if (apexInfo.isActive) {
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
+ } else {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+ }
+ pi.isApex = true;
+ }
+
+ // deprecated method of getting signing certificates
+ if ((flags & PackageManager.GET_SIGNATURES) != 0) {
+ if (p.mSigningDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ pi.signatures = new Signature[1];
+ pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
+ } else if (p.mSigningDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = p.mSigningDetails.signatures.length;
+ pi.signatures = new Signature[numberOfSigs];
+ System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
+ }
+ }
+
+ // replacement for GET_SIGNATURES
+ if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ if (p.mSigningDetails != SigningDetails.UNKNOWN) {
+ // only return a valid SigningInfo if there is signing information to report
+ pi.signingInfo = new SigningInfo(p.mSigningDetails);
+ } else {
+ pi.signingInfo = null;
+ }
+ }
+ return pi;
+ }
+
+ public static final int PARSE_MUST_BE_APK = 1 << 0;
+ public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
+ public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
+ public static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
+ public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
+ public static final int PARSE_ENFORCE_CODE = 1 << 6;
+ public static final int PARSE_CHATTY = 1 << 31;
+
+ @IntDef(flag = true, prefix = { "PARSE_" }, value = {
+ PARSE_CHATTY,
+ PARSE_COLLECT_CERTIFICATES,
+ PARSE_ENFORCE_CODE,
+ PARSE_EXTERNAL_STORAGE,
+ PARSE_IGNORE_PROCESSES,
+ PARSE_IS_SYSTEM_DIR,
+ PARSE_MUST_BE_APK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ParseFlags {}
+
+ public static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
+
+ /**
+ * Used to sort a set of APKs based on their split names, always placing the
+ * base APK (with {@code null} split name) first.
+ */
+ private static class SplitNameComparator implements Comparator<String> {
+ @Override
+ public int compare(String lhs, String rhs) {
+ if (lhs == null) {
+ return -1;
+ } else if (rhs == null) {
+ return 1;
+ } else {
+ return lhs.compareTo(rhs);
+ }
+ }
+ }
+
+ /**
+ * Parse only lightweight details about the package at the given location.
+ * Automatically detects if the package is a monolithic style (single APK
+ * file) or cluster style (directory of APKs).
+ * <p>
+ * This performs checking on cluster style packages, such as
+ * requiring identical package name and version codes, a single base APK,
+ * and unique split names.
+ *
+ * @see PackageParser#parsePackage(File, int)
+ */
+ @UnsupportedAppUsage
+ public static PackageLite parsePackageLite(File packageFile, int flags)
+ throws PackageParserException {
+ if (packageFile.isDirectory()) {
+ return parseClusterPackageLite(packageFile, flags);
+ } else {
+ return parseMonolithicPackageLite(packageFile, flags);
+ }
+ }
+
+ private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
+ throws PackageParserException {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ final ApkLite baseApk = parseApkLite(packageFile, flags);
+ final String packagePath = packageFile.getAbsolutePath();
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ return new PackageLite(packagePath, baseApk.codePath, baseApk, null, null, null, null, null,
+ null);
+ }
+
+ static PackageLite parseClusterPackageLite(File packageDir, int flags)
+ throws PackageParserException {
+ final File[] files = packageDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "No packages found in split");
+ }
+ // Apk directory is directly nested under the current directory
+ if (files.length == 1 && files[0].isDirectory()) {
+ return parseClusterPackageLite(files[0], flags);
+ }
+
+ String packageName = null;
+ int versionCode = 0;
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
+ for (File file : files) {
+ if (isApkFile(file)) {
+ final ApkLite lite = parseApkLite(file, flags);
+
+ // Assert that all package names and version codes are
+ // consistent with the first one we encounter.
+ if (packageName == null) {
+ packageName = lite.packageName;
+ versionCode = lite.versionCode;
+ } else {
+ if (!packageName.equals(lite.packageName)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent package " + lite.packageName + " in " + file
+ + "; expected " + packageName);
+ }
+ if (versionCode != lite.versionCode) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent version " + lite.versionCode + " in " + file
+ + "; expected " + versionCode);
+ }
+ }
+
+ // Assert that each split is defined only once
+ if (apks.put(lite.splitName, lite) != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Split name " + lite.splitName
+ + " defined more than once; most recent was " + file);
+ }
+ }
+ }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+ final ApkLite baseApk = apks.remove(null);
+ if (baseApk == null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Missing base APK in " + packageDir);
+ }
+
+ // Always apply deterministic ordering based on splitName
+ final int size = apks.size();
+
+ String[] splitNames = null;
+ boolean[] isFeatureSplits = null;
+ String[] usesSplitNames = null;
+ String[] configForSplits = null;
+ String[] splitCodePaths = null;
+ int[] splitRevisionCodes = null;
+ String[] splitClassLoaderNames = null;
+ if (size > 0) {
+ splitNames = new String[size];
+ isFeatureSplits = new boolean[size];
+ usesSplitNames = new String[size];
+ configForSplits = new String[size];
+ splitCodePaths = new String[size];
+ splitRevisionCodes = new int[size];
+
+ splitNames = apks.keySet().toArray(splitNames);
+ Arrays.sort(splitNames, sSplitNameComparator);
+
+ for (int i = 0; i < size; i++) {
+ final ApkLite apk = apks.get(splitNames[i]);
+ usesSplitNames[i] = apk.usesSplitName;
+ isFeatureSplits[i] = apk.isFeatureSplit;
+ configForSplits[i] = apk.configForSplit;
+ splitCodePaths[i] = apk.codePath;
+ splitRevisionCodes[i] = apk.revisionCode;
+ }
+ }
+
+ final String codePath = packageDir.getAbsolutePath();
+ return new PackageLite(codePath, baseApk.codePath, baseApk, splitNames, isFeatureSplits,
+ usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes);
+ }
+
+ /**
+ * Parse the package at the given location. Automatically detects if the
+ * package is a monolithic style (single APK file) or cluster style
+ * (directory of APKs).
+ * <p>
+ * This performs checking on cluster style packages, such as
+ * requiring identical package name and version codes, a single base APK,
+ * and unique split names.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in {@link #collectCertificates(Package, boolean)}.
+ *
+ * If {@code useCaches} is true, the package parser might return a cached
+ * result from a previous parse of the same {@code packageFile} with the same
+ * {@code flags}. Note that this method does not check whether {@code packageFile}
+ * has changed since the last parse, it's up to callers to do so.
+ *
+ * @see #parsePackageLite(File, int)
+ */
+ @UnsupportedAppUsage
+ public Package parsePackage(File packageFile, int flags, boolean useCaches)
+ throws PackageParserException {
+ if (packageFile.isDirectory()) {
+ return parseClusterPackage(packageFile, flags);
+ } else {
+ return parseMonolithicPackage(packageFile, flags);
+ }
+ }
+
+ /**
+ * Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}.
+ */
+ @UnsupportedAppUsage
+ public Package parsePackage(File packageFile, int flags) throws PackageParserException {
+ return parsePackage(packageFile, flags, false /* useCaches */);
+ }
+
+ /**
+ * Parse all APKs contained in the given directory, treating them as a
+ * single package. This also performs checking, such as requiring
+ * identical package name and version codes, a single base APK, and unique
+ * split names.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in
+ * {@link #collectCertificates(Package, boolean)} .
+ */
+ private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
+ final PackageLite lite = parseClusterPackageLite(packageDir, 0);
+ if (mOnlyCoreApps && !lite.coreApp) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Not a coreApp: " + packageDir);
+ }
+
+ // Build the split dependency tree.
+ SparseArray<int[]> splitDependencies = null;
+ final SplitAssetLoader assetLoader;
+ if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+ try {
+ splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
+ assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+ } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
+ }
+ } else {
+ assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ }
+
+ try {
+ final AssetManager assets = assetLoader.getBaseAssetManager();
+ final File baseApk = new File(lite.baseCodePath);
+ final Package pkg = parseBaseApk(baseApk, assets, flags);
+ if (pkg == null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Failed to parse base APK: " + baseApk);
+ }
+
+ if (!ArrayUtils.isEmpty(lite.splitNames)) {
+ final int num = lite.splitNames.length;
+ pkg.splitNames = lite.splitNames;
+ pkg.splitCodePaths = lite.splitCodePaths;
+ pkg.splitRevisionCodes = lite.splitRevisionCodes;
+ pkg.splitFlags = new int[num];
+ pkg.splitPrivateFlags = new int[num];
+ pkg.applicationInfo.splitNames = pkg.splitNames;
+ pkg.applicationInfo.splitDependencies = splitDependencies;
+ pkg.applicationInfo.splitClassLoaderNames = new String[num];
+
+ for (int i = 0; i < num; i++) {
+ final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+ parseSplitApk(pkg, i, splitAssets, flags);
+ }
+ }
+
+ pkg.setCodePath(lite.codePath);
+ pkg.setUse32bitAbi(lite.use32bitAbi);
+ return pkg;
+ } finally {
+ IoUtils.closeQuietly(assetLoader);
+ }
+ }
+
+ /**
+ * Parse the given APK file, treating it as as a single monolithic package.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in
+ * {@link #collectCertificates(Package, boolean)}.
+ */
+ @UnsupportedAppUsage
+ public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
+ final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
+ if (mOnlyCoreApps) {
+ if (!lite.coreApp) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Not a coreApp: " + apkFile);
+ }
+ }
+
+ final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ try {
+ final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
+ pkg.setCodePath(apkFile.getCanonicalPath());
+ pkg.setUse32bitAbi(lite.use32bitAbi);
+ return pkg;
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to get path: " + apkFile, e);
+ } finally {
+ IoUtils.closeQuietly(assetLoader);
+ }
+ }
+
+ private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ String volumeUuid = null;
+ if (apkPath.startsWith(MNT_EXPAND)) {
+ final int end = apkPath.indexOf('/', MNT_EXPAND.length());
+ volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
+ }
+
+ mParseError = PackageManager.INSTALL_SUCCEEDED;
+ mArchiveSourcePath = apkFile.getAbsolutePath();
+
+ if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+
+ XmlResourceParser parser = null;
+ try {
+ 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);
+ if (pkg == null) {
+ throw new PackageParserException(mParseError,
+ apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
+ }
+
+ pkg.setVolumeUuid(volumeUuid);
+ pkg.setApplicationVolumeUuid(volumeUuid);
+ pkg.setBaseCodePath(apkPath);
+ pkg.setSigningDetails(SigningDetails.UNKNOWN);
+
+ return pkg;
+
+ } catch (PackageParserException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to read manifest from " + apkPath, e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ }
+
+ private void parseSplitApk(Package pkg, int splitIndex, AssetManager assets, int flags)
+ throws PackageParserException {
+ final String apkPath = pkg.splitCodePaths[splitIndex];
+
+ mParseError = PackageManager.INSTALL_SUCCEEDED;
+ mArchiveSourcePath = apkPath;
+
+ if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
+
+ final Resources res;
+ XmlResourceParser parser = null;
+ try {
+ // 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);
+ if (pkg == null) {
+ throw new PackageParserException(mParseError,
+ apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
+ }
+
+ } catch (PackageParserException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to read manifest from " + apkPath, e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ }
+
+ /**
+ * Parse the manifest of a <em>split APK</em>.
+ * <p>
+ * Note that split APKs have many more restrictions on what they're capable
+ * of doing, so many valid features of a base APK have been carefully
+ * omitted here.
+ */
+ private Package parseSplitApk(Package pkg, Resources res, XmlResourceParser parser, int flags,
+ int splitIndex, String[] outError) throws XmlPullParserException, IOException,
+ PackageParserException {
+ AttributeSet attrs = parser;
+
+ // We parsed manifest tag earlier; just skip past it
+ parsePackageSplitNames(parser, attrs);
+
+ mParseInstrumentationArgs = null;
+
+ int type;
+
+ boolean foundApp = false;
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_APPLICATION)) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ outError[0] = "<manifest> has more than one <application>";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ } else {
+ Slog.w(TAG, "<manifest> has more than one <application>");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+
+ foundApp = true;
+ if (!parseSplitApplication(pkg, res, parser, flags, splitIndex, outError)) {
+ return null;
+ }
+
+ } else if (RIGID_PARSER) {
+ outError[0] = "Bad element under <manifest>: "
+ + parser.getName();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+
+ } else {
+ Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+
+ if (!foundApp) {
+ outError[0] = "<manifest> does not contain an <application>";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY;
+ }
+
+ return pkg;
+ }
+
+ /** Parses the public keys from the set of signatures. */
+ public static ArraySet<PublicKey> toSigningKeys(Signature[] signatures)
+ throws CertificateException {
+ ArraySet<PublicKey> keys = new ArraySet<>(signatures.length);
+ for (int i = 0; i < signatures.length; i++) {
+ keys.add(signatures[i].getPublicKey());
+ }
+ return keys;
+ }
+
+ /**
+ * Collect certificates from all the APKs described in the given package,
+ * populating {@link Package#mSigningDetails}. Also asserts that all APK
+ * contents are signed correctly and consistently.
+ */
+ @UnsupportedAppUsage
+ public static void collectCertificates(Package pkg, boolean skipVerify)
+ throws PackageParserException {
+ 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);
+ childPkg.mSigningDetails = pkg.mSigningDetails;
+ }
+ }
+
+ 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), skipVerify);
+
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify);
+ }
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+ pkg.applicationInfo.targetSdkVersion);
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ // must use v2 signing scheme
+ minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
+ }
+ SigningDetails verified;
+ if (skipVerify) {
+ // systemDir APKs are already trusted, save time by not verifying; since the signature
+ // is not verified and some system apps can have their V2+ signatures stripped allow
+ // pulling the certs from the jar signature.
+ verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
+ apkPath, SigningDetails.SignatureSchemeVersion.JAR);
+ } else {
+ verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
+ }
+
+ // Verify that entries are signed consistently with the first pkg
+ // we encountered. Note that for splits, certificates may have
+ // already been populated during an earlier parse of a base APK.
+ if (pkg.mSigningDetails == SigningDetails.UNKNOWN) {
+ pkg.mSigningDetails = verified;
+ } else {
+ if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) {
+ throw new PackageParserException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ apkPath + " has mismatched certificates");
+ }
+ }
+ }
+
+ private static AssetManager newConfiguredAssetManager() {
+ AssetManager assetManager = new AssetManager();
+ assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ return assetManager;
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param apkFile path to a single APK
+ * @param flags optional parse flags, such as
+ * {@link #PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ApkLite parseApkLite(File apkFile, int flags)
+ throws PackageParserException {
+ return parseApkLiteInner(apkFile, null, null, flags);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param fd already open file descriptor of an apk file
+ * @param debugPathName arbitrary text name for this file, for debug output
+ * @param flags optional parse flags, such as
+ * {@link #PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags)
+ throws PackageParserException {
+ return parseApkLiteInner(null, fd, debugPathName, flags);
+ }
+
+ private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
+ int flags) throws PackageParserException {
+ final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
+
+ XmlResourceParser parser = null;
+ ApkAssets apkAssets = null;
+ try {
+ try {
+ apkAssets = fd != null
+ ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
+ : ApkAssets.loadFromPath(apkPath);
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Failed to parse " + apkPath);
+ }
+
+ 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, skipVerify);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ signingDetails = tempPkg.mSigningDetails;
+ } else {
+ signingDetails = SigningDetails.UNKNOWN;
+ }
+
+ final AttributeSet attrs = parser;
+ return parseApkLite(apkPath, parser, attrs, signingDetails);
+
+ } catch (XmlPullParserException | IOException | RuntimeException e) {
+ Slog.w(TAG, "Failed to parse " + apkPath, e);
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to parse " + apkPath, e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
+ }
+ }
+ // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
+ }
+ }
+
+ public static String validateName(String name, boolean requireSeparator,
+ boolean requireFilename) {
+ final int N = name.length();
+ boolean hasSep = false;
+ boolean front = true;
+ for (int i=0; i<N; i++) {
+ final char c = name.charAt(i);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ front = false;
+ continue;
+ }
+ if (!front) {
+ if ((c >= '0' && c <= '9') || c == '_') {
+ continue;
+ }
+ }
+ if (c == '.') {
+ hasSep = true;
+ front = true;
+ continue;
+ }
+ return "bad character '" + c + "'";
+ }
+ if (requireFilename && !FileUtils.isValidExtFilename(name)) {
+ return "Invalid filename";
+ }
+ return hasSep || !requireSeparator
+ ? null : "must have at least one '.' separator";
+ }
+
+ /**
+ * @deprecated Use {@link android.content.pm.parsing.ApkLiteParseUtils#parsePackageSplitNames}
+ */
+ @Deprecated
+ public static Pair<String, String> parsePackageSplitNames(XmlPullParser parser,
+ AttributeSet attrs) throws IOException, XmlPullParserException,
+ PackageParserException {
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No start tag found");
+ }
+ if (!parser.getName().equals(TAG_MANIFEST)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No <manifest> tag");
+ }
+
+ final String packageName = attrs.getAttributeValue(null, "package");
+ if (!"android".equals(packageName)) {
+ final String error = validateName(packageName, true, true);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest package: " + error);
+ }
+ }
+
+ String splitName = attrs.getAttributeValue(null, "split");
+ if (splitName != null) {
+ if (splitName.length() == 0) {
+ splitName = null;
+ } else {
+ final String error = validateName(splitName, false, false);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest split: " + error);
+ }
+ }
+ }
+
+ return Pair.create(packageName.intern(),
+ (splitName != null) ? splitName.intern() : splitName);
+ }
+
+ private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
+ SigningDetails signingDetails)
+ throws IOException, XmlPullParserException, PackageParserException {
+ final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
+
+ int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
+ int versionCode = 0;
+ int versionCodeMajor = 0;
+ int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
+ int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
+ int revisionCode = 0;
+ boolean coreApp = false;
+ boolean debuggable = false;
+ boolean profilableByShell = false;
+ boolean multiArch = false;
+ boolean use32bitAbi = false;
+ boolean extractNativeLibs = true;
+ boolean isolatedSplits = false;
+ boolean isFeatureSplit = false;
+ boolean isSplitRequired = false;
+ boolean useEmbeddedDex = false;
+ String configForSplit = null;
+ String usesSplitName = null;
+ String targetPackage = null;
+ boolean overlayIsStatic = false;
+ int overlayPriority = 0;
+ int rollbackDataPolicy = 0;
+
+ String requiredSystemPropertyName = null;
+ String requiredSystemPropertyValue = null;
+
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ final String attr = attrs.getAttributeName(i);
+ if (attr.equals("installLocation")) {
+ installLocation = attrs.getAttributeIntValue(i,
+ PARSE_DEFAULT_INSTALL_LOCATION);
+ } else if (attr.equals("versionCode")) {
+ versionCode = attrs.getAttributeIntValue(i, 0);
+ } else if (attr.equals("versionCodeMajor")) {
+ versionCodeMajor = attrs.getAttributeIntValue(i, 0);
+ } else if (attr.equals("revisionCode")) {
+ revisionCode = attrs.getAttributeIntValue(i, 0);
+ } else if (attr.equals("coreApp")) {
+ coreApp = attrs.getAttributeBooleanValue(i, false);
+ } else if (attr.equals("isolatedSplits")) {
+ isolatedSplits = attrs.getAttributeBooleanValue(i, false);
+ } else if (attr.equals("configForSplit")) {
+ configForSplit = attrs.getAttributeValue(i);
+ } else if (attr.equals("isFeatureSplit")) {
+ isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
+ } else if (attr.equals("isSplitRequired")) {
+ isSplitRequired = attrs.getAttributeBooleanValue(i, false);
+ }
+ }
+
+ // Only search the tree when the tag is the direct child of <manifest> tag
+ int type;
+ final int searchDepth = parser.getDepth() + 1;
+
+ final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() != searchDepth) {
+ continue;
+ }
+
+ if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+ final VerifierInfo verifier = parseVerifier(attrs);
+ if (verifier != null) {
+ verifiers.add(verifier);
+ }
+ } else if (TAG_APPLICATION.equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ if ("debuggable".equals(attr)) {
+ debuggable = attrs.getAttributeBooleanValue(i, false);
+ }
+ if ("multiArch".equals(attr)) {
+ multiArch = attrs.getAttributeBooleanValue(i, false);
+ }
+ if ("use32bitAbi".equals(attr)) {
+ use32bitAbi = attrs.getAttributeBooleanValue(i, false);
+ }
+ if ("extractNativeLibs".equals(attr)) {
+ extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
+ }
+ if ("useEmbeddedDex".equals(attr)) {
+ useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
+ }
+ if (attr.equals("rollbackDataPolicy")) {
+ rollbackDataPolicy = attrs.getAttributeIntValue(i, 0);
+ }
+ }
+ } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ if ("requiredSystemPropertyName".equals(attr)) {
+ requiredSystemPropertyName = attrs.getAttributeValue(i);
+ } else if ("requiredSystemPropertyValue".equals(attr)) {
+ requiredSystemPropertyValue = attrs.getAttributeValue(i);
+ } else if ("targetPackage".equals(attr)) {
+ targetPackage = attrs.getAttributeValue(i);;
+ } else if ("isStatic".equals(attr)) {
+ overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
+ } else if ("priority".equals(attr)) {
+ overlayPriority = attrs.getAttributeIntValue(i, 0);
+ }
+ }
+ } else if (TAG_USES_SPLIT.equals(parser.getName())) {
+ if (usesSplitName != null) {
+ Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
+ continue;
+ }
+
+ usesSplitName = attrs.getAttributeValue(ANDROID_RESOURCES, "name");
+ if (usesSplitName == null) {
+ throw new PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<uses-split> tag requires 'android:name' attribute");
+ }
+ } else if (TAG_USES_SDK.equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ if ("targetSdkVersion".equals(attr)) {
+ targetSdkVersion = attrs.getAttributeIntValue(i,
+ DEFAULT_TARGET_SDK_VERSION);
+ }
+ if ("minSdkVersion".equals(attr)) {
+ minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION);
+ }
+ }
+ }
+ }
+
+ // Check to see if overlay should be excluded based on system property condition
+ if (!checkRequiredSystemProperties(requiredSystemPropertyName,
+ requiredSystemPropertyValue)) {
+ Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ + codePath + ": overlay ignored due to required system property: "
+ + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
+ targetPackage = null;
+ overlayIsStatic = false;
+ overlayPriority = 0;
+ }
+
+ return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+ configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor,
+ revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable,
+ profilableByShell, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs,
+ isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion,
+ targetSdkVersion, rollbackDataPolicy);
+ }
+
+ /**
+ * Parses a child package and adds it to the parent if successful. If you add
+ * new tags that need to be supported by child packages make sure to add them
+ * to {@link #CHILD_PACKAGE_TAGS}.
+ *
+ * @param parentPkg The parent that contains the child
+ * @param res Resources against which to resolve values
+ * @param parser Parser of the manifest
+ * @param flags Flags about how to parse
+ * @param outError Human readable error if parsing fails
+ * @return True of parsing succeeded.
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ private boolean parseBaseApkChild(Package parentPkg, Resources res, XmlResourceParser parser,
+ int flags, String[] outError) throws XmlPullParserException, IOException {
+ // Make sure we have a valid child package name
+ String childPackageName = parser.getAttributeValue(null, "package");
+ if (validateName(childPackageName, true, false) != null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+ return false;
+ }
+
+ // Child packages must be unique
+ if (childPackageName.equals(parentPkg.packageName)) {
+ String message = "Child package name cannot be equal to parent package name: "
+ + parentPkg.packageName;
+ Slog.w(TAG, message);
+ outError[0] = message;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ // Child packages must be unique
+ if (parentPkg.hasChildPackage(childPackageName)) {
+ String message = "Duplicate child package:" + childPackageName;
+ Slog.w(TAG, message);
+ outError[0] = message;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ // Go ahead and parse the child
+ Package childPkg = new Package(childPackageName);
+
+ // Child package inherits parent version code/name/target SDK
+ childPkg.mVersionCode = parentPkg.mVersionCode;
+ childPkg.baseRevisionCode = parentPkg.baseRevisionCode;
+ childPkg.mVersionName = parentPkg.mVersionName;
+ childPkg.applicationInfo.targetSdkVersion = parentPkg.applicationInfo.targetSdkVersion;
+ childPkg.applicationInfo.minSdkVersion = parentPkg.applicationInfo.minSdkVersion;
+
+ childPkg = parseBaseApkCommon(childPkg, CHILD_PACKAGE_TAGS, res, parser, flags, outError);
+ if (childPkg == null) {
+ // If we got null then error was set during child parsing
+ return false;
+ }
+
+ // Set the parent-child relation
+ if (parentPkg.childPackages == null) {
+ parentPkg.childPackages = new ArrayList<>();
+ }
+ parentPkg.childPackages.add(childPkg);
+ childPkg.parentPackage = parentPkg;
+
+ return true;
+ }
+
+ /**
+ * Parse the manifest of a <em>base APK</em>. When adding new features you
+ * need to consider whether they should be supported by split APKs and child
+ * packages.
+ *
+ * @param apkPath The package apk file path
+ * @param res The resources from which to resolve values
+ * @param parser The manifest parser
+ * @param flags Flags how to parse
+ * @param outError Human readable error message
+ * @return Parsed package or null on error.
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
+ String[] outError) throws XmlPullParserException, IOException {
+ final String splitName;
+ final String pkgName;
+
+ try {
+ Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
+ pkgName = packageSplit.first;
+ splitName = packageSplit.second;
+
+ if (!TextUtils.isEmpty(splitName)) {
+ outError[0] = "Expected base APK, but found split " + splitName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+ return null;
+ }
+ } catch (PackageParserException e) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+ return null;
+ }
+
+ final Package pkg = new Package(pkgName);
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifest);
+
+ pkg.mVersionCode = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
+ pkg.mVersionCodeMajor = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);
+ pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());
+ pkg.baseRevisionCode = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
+ pkg.mVersionName = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_versionName, 0);
+ if (pkg.mVersionName != null) {
+ pkg.mVersionName = pkg.mVersionName.intern();
+ }
+
+ pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+
+ final boolean isolatedSplits = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false);
+ if (isolatedSplits) {
+ pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+ }
+
+ pkg.mCompileSdkVersion = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
+ pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
+ pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
+ if (pkg.mCompileSdkVersionCodename != null) {
+ pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
+ }
+ pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
+
+ sa.recycle();
+
+ return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
+ }
+
+ /**
+ * This is the common parsing routing for handling parent and child
+ * packages in a base APK. The difference between parent and child
+ * parsing is that some tags are not supported by child packages as
+ * well as some manifest attributes are ignored. The implementation
+ * assumes the calling code has already handled the manifest tag if needed
+ * (this applies to the parent only).
+ *
+ * @param pkg The package which to populate
+ * @param acceptedTags Which tags to handle, null to handle all
+ * @param res Resources against which to resolve values
+ * @param parser Parser of the manifest
+ * @param flags Flags about how to parse
+ * @param outError Human readable error if parsing fails
+ * @return The package if parsing succeeded or null.
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
+ XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
+ IOException {
+ mParseInstrumentationArgs = null;
+
+ int type;
+ boolean foundApp = false;
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifest);
+
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
+ if (str != null && str.length() > 0) {
+ String nameError = validateName(str, true, true);
+ if (nameError != null && !"android".equals(pkg.packageName)) {
+ outError[0] = "<manifest> specifies bad sharedUserId name \""
+ + str + "\": " + nameError;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
+ return null;
+ }
+ pkg.mSharedUserId = str.intern();
+ pkg.mSharedUserLabel = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
+ }
+
+ pkg.installLocation = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_installLocation,
+ PARSE_DEFAULT_INSTALL_LOCATION);
+ pkg.applicationInfo.installLocation = pkg.installLocation;
+
+ final int targetSandboxVersion = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_targetSandboxVersion,
+ PARSE_DEFAULT_TARGET_SANDBOX);
+ pkg.applicationInfo.targetSandboxVersion = targetSandboxVersion;
+
+ /* Set the global "on SD card" flag */
+ if ((flags & PARSE_EXTERNAL_STORAGE) != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+ }
+
+ // Resource boolean are -1, so 1 means we don't know the value.
+ int supportsSmallScreens = 1;
+ int supportsNormalScreens = 1;
+ int supportsLargeScreens = 1;
+ int supportsXLargeScreens = 1;
+ int resizeable = 1;
+ int anyDensity = 1;
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+
+ if (acceptedTags != null && !acceptedTags.contains(tagName)) {
+ Slog.w(TAG, "Skipping unsupported element under <manifest>: "
+ + tagName + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ if (tagName.equals(TAG_APPLICATION)) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ outError[0] = "<manifest> has more than one <application>";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ } else {
+ Slog.w(TAG, "<manifest> has more than one <application>");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+
+ foundApp = true;
+ if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_OVERLAY)) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay);
+ pkg.mOverlayTarget = sa.getString(
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
+ pkg.mOverlayTargetName = sa.getString(
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetName);
+ pkg.mOverlayCategory = sa.getString(
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay_category);
+ pkg.mOverlayPriority = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
+ 0);
+ pkg.mOverlayIsStatic = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
+ false);
+ final String propName = sa.getString(
+ com.android.internal.R.styleable
+ .AndroidManifestResourceOverlay_requiredSystemPropertyName);
+ final String propValue = sa.getString(
+ com.android.internal.R.styleable
+ .AndroidManifestResourceOverlay_requiredSystemPropertyValue);
+ sa.recycle();
+
+ if (pkg.mOverlayTarget == null) {
+ outError[0] = "<overlay> does not specify a target package";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) {
+ outError[0] = "<overlay> priority must be between 0 and 9999";
+ mParseError =
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ // check to see if overlay should be excluded based on system property condition
+ if (!checkRequiredSystemProperties(propName, propValue)) {
+ Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
+ + pkg.baseCodePath+ ": overlay ignored due to required system property: "
+ + propName + " with value: " + propValue);
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_SKIPPED;
+ return null;
+ }
+
+ pkg.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY;
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_KEY_SETS)) {
+ if (!parseKeySets(pkg, res, parser, outError)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_PERMISSION_GROUP)) {
+ if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_PERMISSION)) {
+ if (!parsePermission(pkg, res, parser, outError)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_PERMISSION_TREE)) {
+ if (!parsePermissionTree(pkg, res, parser, outError)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_USES_PERMISSION)) {
+ if (!parseUsesPermission(pkg, res, parser)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
+ || tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
+ if (!parseUsesPermission(pkg, res, parser)) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_USES_CONFIGURATION)) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration);
+ cPref.reqTouchScreen = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen,
+ Configuration.TOUCHSCREEN_UNDEFINED);
+ cPref.reqKeyboardType = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType,
+ Configuration.KEYBOARD_UNDEFINED);
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard,
+ false)) {
+ cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
+ }
+ cPref.reqNavigation = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqNavigation,
+ Configuration.NAVIGATION_UNDEFINED);
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav,
+ false)) {
+ cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
+ }
+ sa.recycle();
+ pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref);
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_USES_FEATURE)) {
+ FeatureInfo fi = parseUsesFeature(res, parser);
+ pkg.reqFeatures = ArrayUtils.add(pkg.reqFeatures, fi);
+
+ if (fi.name == null) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ cPref.reqGlEsVersion = fi.reqGlEsVersion;
+ pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_FEATURE_GROUP)) {
+ FeatureGroupInfo group = new FeatureGroupInfo();
+ ArrayList<FeatureInfo> features = null;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final String innerTagName = parser.getName();
+ if (innerTagName.equals("uses-feature")) {
+ FeatureInfo featureInfo = parseUsesFeature(res, parser);
+ // FeatureGroups are stricter and mandate that
+ // any <uses-feature> declared are mandatory.
+ featureInfo.flags |= FeatureInfo.FLAG_REQUIRED;
+ features = ArrayUtils.add(features, featureInfo);
+ } else {
+ Slog.w(TAG, "Unknown element under <feature-group>: " + innerTagName +
+ " at " + mArchiveSourcePath + " " +
+ parser.getPositionDescription());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ if (features != null) {
+ group.features = new FeatureInfo[features.size()];
+ group.features = features.toArray(group.features);
+ }
+ pkg.featureGroups = ArrayUtils.add(pkg.featureGroups, group);
+
+ } else if (tagName.equals(TAG_USES_SDK)) {
+ if (SDK_VERSION > 0) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesSdk);
+
+ int minVers = 1;
+ String minCode = null;
+ int targetVers = 0;
+ String targetCode = null;
+
+ TypedValue val = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ if (val != null) {
+ if (val.type == TypedValue.TYPE_STRING && val.string != null) {
+ minCode = val.string.toString();
+ } else {
+ // If it's not a string, it's an integer.
+ minVers = val.data;
+ }
+ }
+
+ val = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+ if (val != null) {
+ if (val.type == TypedValue.TYPE_STRING && val.string != null) {
+ targetCode = val.string.toString();
+ if (minCode == null) {
+ minCode = targetCode;
+ }
+ } else {
+ // If it's not a string, it's an integer.
+ targetVers = val.data;
+ }
+ } else {
+ targetVers = minVers;
+ targetCode = minCode;
+ }
+
+ sa.recycle();
+
+ final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
+ SDK_VERSION, SDK_CODENAMES, outError);
+ if (minSdkVersion < 0) {
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+
+ final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
+ targetCode, SDK_CODENAMES, outError);
+ if (targetSdkVersion < 0) {
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+
+ pkg.applicationInfo.minSdkVersion = minSdkVersion;
+ pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens);
+
+ pkg.applicationInfo.requiresSmallestWidthDp = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_requiresSmallestWidthDp,
+ 0);
+ pkg.applicationInfo.compatibleWidthLimitDp = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_compatibleWidthLimitDp,
+ 0);
+ pkg.applicationInfo.largestWidthLimitDp = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_largestWidthLimitDp,
+ 0);
+
+ // This is a trick to get a boolean and still able to detect
+ // if a value was actually set.
+ supportsSmallScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_smallScreens,
+ supportsSmallScreens);
+ supportsNormalScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_normalScreens,
+ supportsNormalScreens);
+ supportsLargeScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens,
+ supportsLargeScreens);
+ supportsXLargeScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_xlargeScreens,
+ supportsXLargeScreens);
+ resizeable = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_resizeable,
+ resizeable);
+ anyDensity = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_anyDensity,
+ anyDensity);
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);
+
+ sa.recycle();
+
+ if (name != null) {
+ if (pkg.protectedBroadcasts == null) {
+ pkg.protectedBroadcasts = new ArrayList<String>();
+ }
+ if (!pkg.protectedBroadcasts.contains(name)) {
+ pkg.protectedBroadcasts.add(name.intern());
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_INSTRUMENTATION)) {
+ if (parseInstrumentation(pkg, res, parser, outError) == null) {
+ return null;
+ }
+ } else if (tagName.equals(TAG_ORIGINAL_PACKAGE)) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+
+ String orig =sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
+ if (!pkg.packageName.equals(orig)) {
+ if (pkg.mOriginalPackages == null) {
+ pkg.mOriginalPackages = new ArrayList<String>();
+ pkg.mRealPackage = pkg.packageName;
+ }
+ pkg.mOriginalPackages.add(orig);
+ }
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_ADOPT_PERMISSIONS)) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+
+ String name = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
+
+ sa.recycle();
+
+ if (name != null) {
+ if (pkg.mAdoptPermissions == null) {
+ pkg.mAdoptPermissions = new ArrayList<String>();
+ }
+ pkg.mAdoptPermissions.add(name);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals(TAG_USES_GL_TEXTURE)) {
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+
+ } else if (tagName.equals(TAG_COMPATIBLE_SCREENS)) {
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else if (tagName.equals(TAG_SUPPORTS_INPUT)) {//
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+
+ } else if (tagName.equals(TAG_EAT_COMMENT)) {
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+
+ } else if (tagName.equals(TAG_PACKAGE)) {
+ if (!MULTI_PACKAGE_APK_ENABLED) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ if (!parseBaseApkChild(pkg, res, parser, flags, outError)) {
+ // If parsing a child failed the error is already set
+ return null;
+ }
+
+ } else if (tagName.equals(TAG_RESTRICT_UPDATE)) {
+ if ((flags & PARSE_IS_SYSTEM_DIR) != 0) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestRestrictUpdate);
+ final String hash = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestRestrictUpdate_hash, 0);
+ sa.recycle();
+
+ pkg.restrictUpdateHash = null;
+ if (hash != null) {
+ final int hashLength = hash.length();
+ final byte[] hashBytes = new byte[hashLength / 2];
+ for (int i = 0; i < hashLength; i += 2){
+ hashBytes[i/2] = (byte) ((Character.digit(hash.charAt(i), 16) << 4)
+ + Character.digit(hash.charAt(i + 1), 16));
+ }
+ pkg.restrictUpdateHash = hashBytes;
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (RIGID_PARSER) {
+ outError[0] = "Bad element under <manifest>: "
+ + parser.getName();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+
+ } else {
+ Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+
+ if (!foundApp && pkg.instrumentation.size() == 0) {
+ outError[0] = "<manifest> does not contain an <application> or <instrumentation>";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY;
+ }
+
+ final int NP = PackageParser.NEW_PERMISSIONS.length;
+ StringBuilder newPermsMsg = null;
+ for (int ip=0; ip<NP; ip++) {
+ final PackageParser.NewPermissionInfo npi
+ = PackageParser.NEW_PERMISSIONS[ip];
+ if (pkg.applicationInfo.targetSdkVersion >= npi.sdkVersion) {
+ break;
+ }
+ if (!pkg.requestedPermissions.contains(npi.name)) {
+ if (newPermsMsg == null) {
+ newPermsMsg = new StringBuilder(128);
+ newPermsMsg.append(pkg.packageName);
+ newPermsMsg.append(": compat added ");
+ } else {
+ newPermsMsg.append(' ');
+ }
+ newPermsMsg.append(npi.name);
+ pkg.requestedPermissions.add(npi.name);
+ pkg.implicitPermissions.add(npi.name);
+ }
+ }
+ if (newPermsMsg != null) {
+ Slog.i(TAG, newPermsMsg.toString());
+ }
+
+ final List<PermissionManager.SplitPermissionInfo> splitPermissions =
+ ActivityThread.currentApplication().getSystemService(PermissionManager.class)
+ .getSplitPermissions();
+
+ final int listSize = splitPermissions.size();
+ for (int is = 0; is < listSize; is++) {
+ final PermissionManager.SplitPermissionInfo spi = splitPermissions.get(is);
+ if (pkg.applicationInfo.targetSdkVersion >= spi.getTargetSdk()
+ || !pkg.requestedPermissions.contains(spi.getSplitPermission())) {
+ continue;
+ }
+ final List<String> newPerms = spi.getNewPermissions();
+ for (int in = 0; in < newPerms.size(); in++) {
+ final String perm = newPerms.get(in);
+ if (!pkg.requestedPermissions.contains(perm)) {
+ pkg.requestedPermissions.add(perm);
+ pkg.implicitPermissions.add(perm);
+ }
+ }
+ }
+
+ if (supportsSmallScreens < 0 || (supportsSmallScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.DONUT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS;
+ }
+ if (supportsNormalScreens != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS;
+ }
+ if (supportsLargeScreens < 0 || (supportsLargeScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.DONUT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
+ }
+ if (supportsXLargeScreens < 0 || (supportsXLargeScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.GINGERBREAD)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS;
+ }
+ if (resizeable < 0 || (resizeable > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.DONUT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS;
+ }
+ if (anyDensity < 0 || (anyDensity > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.DONUT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
+ }
+
+ // At this point we can check if an application is not supporting densities and hence
+ // cannot be windowed / resized. Note that an SDK version of 0 is common for
+ // pre-Doughnut applications.
+ if (pkg.applicationInfo.usesCompatibilityMode()) {
+ adjustPackageToBeUnresizeableAndUnpipable(pkg);
+ }
+
+ return pkg;
+ }
+
+ /**
+ * Returns {@code true} if both the property name and value are empty or if the given system
+ * property is set to the specified value. Properties can be one or more, and if properties are
+ * more than one, they must be separated by comma, and count of names and values must be equal,
+ * and also every given system property must be set to the corresponding value.
+ * In all other cases, returns {@code false}
+ */
+ public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames,
+ @Nullable String rawPropValues) {
+ if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) {
+ if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) {
+ // malformed condition - incomplete
+ Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames
+ + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue to be specified.");
+ return false;
+ }
+ // no valid condition set - so no exclusion criteria, overlay will be included.
+ return true;
+ }
+
+ final String[] propNames = rawPropNames.split(",");
+ final String[] propValues = rawPropValues.split(",");
+
+ if (propNames.length != propValues.length) {
+ Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames
+ + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue lists to have the same size.");
+ return false;
+ }
+ for (int i = 0; i < propNames.length; i++) {
+ // Check property value: make sure it is both set and equal to expected value
+ final String currValue = SystemProperties.get(propNames[i]);
+ if (!TextUtils.equals(currValue, propValues[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This is a pre-density application which will get scaled - instead of being pixel perfect.
+ * This type of application is not resizable.
+ *
+ * @param pkg The package which needs to be marked as unresizable.
+ */
+ private void adjustPackageToBeUnresizeableAndUnpipable(Package pkg) {
+ for (Activity a : pkg.activities) {
+ a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.info.flags &= ~FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+ }
+ }
+
+ /**
+
+ /**
+ * Matches a given {@code targetCode} against a set of release codeNames. Target codes can
+ * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form
+ * {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}).
+ */
+ private static boolean matchTargetCode(@NonNull String[] codeNames,
+ @NonNull String targetCode) {
+ final String targetCodeName;
+ final int targetCodeIdx = targetCode.indexOf('.');
+ if (targetCodeIdx == -1) {
+ targetCodeName = targetCode;
+ } else {
+ targetCodeName = targetCode.substring(0, targetCodeIdx);
+ }
+ return ArrayUtils.contains(codeNames, targetCodeName);
+ }
+
+ /**
+ * Computes the targetSdkVersion to use at runtime. If the package is not
+ * compatible with this platform, populates {@code outError[0]} with an
+ * error message.
+ * <p>
+ * If {@code targetCode} is not specified, e.g. the value is {@code null},
+ * then the {@code targetVers} will be returned unmodified.
+ * <p>
+ * Otherwise, the behavior varies based on whether the current platform
+ * is a pre-release version, e.g. the {@code platformSdkCodenames} array
+ * has length > 0:
+ * <ul>
+ * <li>If this is a pre-release platform and the value specified by
+ * {@code targetCode} is contained within the array of allowed pre-release
+ * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
+ * <li>If this is a released platform, this method will return -1 to
+ * indicate that the package is not compatible with this platform.
+ * </ul>
+ *
+ * @param targetVers targetSdkVersion number, if specified in the
+ * application manifest, or 0 otherwise
+ * @param targetCode targetSdkVersion code, if specified in the application
+ * manifest, or {@code null} otherwise
+ * @param platformSdkCodenames array of allowed pre-release SDK codenames
+ * for this platform
+ * @param outError output array to populate with error, if applicable
+ * @return the targetSdkVersion to use at runtime, or -1 if the package is
+ * not compatible with this platform
+ * @hide Exposed for unit testing only.
+ */
+ public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
+ @Nullable String targetCode, @NonNull String[] platformSdkCodenames,
+ @NonNull String[] outError) {
+ // If it's a release SDK, return the version number unmodified.
+ if (targetCode == null) {
+ return targetVers;
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, it
+ // definitely targets this SDK.
+ if (matchTargetCode(platformSdkCodenames, targetCode)) {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
+ // Otherwise, we're looking at an incompatible pre-release SDK.
+ if (platformSdkCodenames.length > 0) {
+ outError[0] = "Requires development platform " + targetCode
+ + " (current platform is any of "
+ + Arrays.toString(platformSdkCodenames) + ")";
+ } else {
+ outError[0] = "Requires development platform " + targetCode
+ + " but this is a release platform.";
+ }
+ return -1;
+ }
+
+ /**
+ * Computes the minSdkVersion to use at runtime. If the package is not
+ * compatible with this platform, populates {@code outError[0]} with an
+ * error message.
+ * <p>
+ * If {@code minCode} is not specified, e.g. the value is {@code null},
+ * then behavior varies based on the {@code platformSdkVersion}:
+ * <ul>
+ * <li>If the platform SDK version is greater than or equal to the
+ * {@code minVers}, returns the {@code mniVers} unmodified.
+ * <li>Otherwise, returns -1 to indicate that the package is not
+ * compatible with this platform.
+ * </ul>
+ * <p>
+ * Otherwise, the behavior varies based on whether the current platform
+ * is a pre-release version, e.g. the {@code platformSdkCodenames} array
+ * has length > 0:
+ * <ul>
+ * <li>If this is a pre-release platform and the value specified by
+ * {@code targetCode} is contained within the array of allowed pre-release
+ * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
+ * <li>If this is a released platform, this method will return -1 to
+ * indicate that the package is not compatible with this platform.
+ * </ul>
+ *
+ * @param minVers minSdkVersion number, if specified in the application
+ * manifest, or 1 otherwise
+ * @param minCode minSdkVersion code, if specified in the application
+ * manifest, or {@code null} otherwise
+ * @param platformSdkVersion platform SDK version number, typically
+ * Build.VERSION.SDK_INT
+ * @param platformSdkCodenames array of allowed prerelease SDK codenames
+ * for this platform
+ * @param outError output array to populate with error, if applicable
+ * @return the minSdkVersion to use at runtime, or -1 if the package is not
+ * compatible with this platform
+ * @hide Exposed for unit testing only.
+ */
+ public static int computeMinSdkVersion(@IntRange(from = 1) int minVers,
+ @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
+ @NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
+ // If it's a release SDK, make sure we meet the minimum SDK requirement.
+ if (minCode == null) {
+ if (minVers <= platformSdkVersion) {
+ return minVers;
+ }
+
+ // We don't meet the minimum SDK requirement.
+ outError[0] = "Requires newer sdk version #" + minVers
+ + " (current version is #" + platformSdkVersion + ")";
+ return -1;
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, we
+ // definitely meet the minimum SDK requirement.
+ if (matchTargetCode(platformSdkCodenames, minCode)) {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
+ // Otherwise, we're looking at an incompatible pre-release SDK.
+ if (platformSdkCodenames.length > 0) {
+ outError[0] = "Requires development platform " + minCode
+ + " (current platform is any of "
+ + Arrays.toString(platformSdkCodenames) + ")";
+ } else {
+ outError[0] = "Requires development platform " + minCode
+ + " but this is a release platform.";
+ }
+ return -1;
+ }
+
+ private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) {
+ FeatureInfo fi = new FeatureInfo();
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesFeature);
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ fi.name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
+ fi.version = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_version, 0);
+ if (fi.name == null) {
+ fi.reqGlEsVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
+ FeatureInfo.GL_ES_VERSION_UNDEFINED);
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_required, true)) {
+ fi.flags |= FeatureInfo.FLAG_REQUIRED;
+ }
+ sa.recycle();
+ return fi;
+ }
+
+ private boolean parseUsesStaticLibrary(Package pkg, Resources res, XmlResourceParser parser,
+ String[] outError) throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary);
+
+ // Note: don't allow this value to be a reference to a resource that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+ final int version = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
+ String certSha256Digest = sa.getNonResourceString(com.android.internal.R.styleable
+ .AndroidManifestUsesStaticLibrary_certDigest);
+ sa.recycle();
+
+ // Since an APK providing a static shared lib can only provide the lib - fail if malformed
+ if (lname == null || version < 0 || certSha256Digest == null) {
+ outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
+ + version + " certDigest" + certSha256Digest;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ // Can depend only on one version of the same library
+ if (pkg.usesStaticLibraries != null && pkg.usesStaticLibraries.contains(lname)) {
+ outError[0] = "Depending on multiple versions of static library " + lname;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ lname = lname.intern();
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+
+ // Fot apps targeting O-MR1 we require explicit enumeration of all certs.
+ String[] additionalCertSha256Digests = EmptyArray.STRING;
+ if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
+ additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError);
+ if (additionalCertSha256Digests == null) {
+ return false;
+ }
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
+ certSha256Digests[0] = certSha256Digest;
+ System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+ 1, additionalCertSha256Digests.length);
+
+ pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
+ pkg.usesStaticLibrariesVersions = ArrayUtils.appendLong(
+ pkg.usesStaticLibrariesVersions, version, true);
+ pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
+ pkg.usesStaticLibrariesCertDigests, certSha256Digests, true);
+
+ return true;
+ }
+
+ private String[] parseAdditionalCertificates(Resources resources, XmlResourceParser parser,
+ String[] outError) throws XmlPullParserException, IOException {
+ String[] certSha256Digests = EmptyArray.STRING;
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final String nodeName = parser.getName();
+ if (nodeName.equals("additional-certificate")) {
+ final TypedArray sa = resources.obtainAttributes(parser, com.android.internal.
+ R.styleable.AndroidManifestAdditionalCertificate);
+ String certSha256Digest = sa.getNonResourceString(com.android.internal.
+ R.styleable.AndroidManifestAdditionalCertificate_certDigest);
+ sa.recycle();
+
+ if (TextUtils.isEmpty(certSha256Digest)) {
+ outError[0] = "Bad additional-certificate declaration with empty"
+ + " certDigest:" + certSha256Digest;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ sa.recycle();
+ return null;
+ }
+
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+ certSha256Digests = ArrayUtils.appendElement(String.class,
+ certSha256Digests, certSha256Digest);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ return certSha256Digests;
+ }
+
+ private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesPermission);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
+
+ int maxSdkVersion = 0;
+ TypedValue val = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
+ if (val != null) {
+ if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
+ maxSdkVersion = val.data;
+ }
+ }
+
+ final String requiredFeature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
+
+ final String requiredNotfeature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredNotFeature, 0);
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
+
+ if (name == null) {
+ return true;
+ }
+
+ if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+ return true;
+ }
+
+ // Only allow requesting this permission if the platform supports the given feature.
+ if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(requiredFeature)) {
+ return true;
+ }
+
+ // Only allow requesting this permission if the platform doesn't support the given feature.
+ if (requiredNotfeature != null && mCallback != null
+ && mCallback.hasFeature(requiredNotfeature)) {
+ return true;
+ }
+
+ int index = pkg.requestedPermissions.indexOf(name);
+ if (index == -1) {
+ pkg.requestedPermissions.add(name.intern());
+ } else {
+ Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ + name + " in package: " + pkg.packageName + " at: "
+ + parser.getPositionDescription());
+ }
+
+ return true;
+ }
+
+ public static String buildClassName(String pkg, CharSequence clsSeq,
+ String[] outError) {
+ if (clsSeq == null || clsSeq.length() <= 0) {
+ outError[0] = "Empty class name in package " + pkg;
+ return null;
+ }
+ String cls = clsSeq.toString();
+ char c = cls.charAt(0);
+ if (c == '.') {
+ return pkg + cls;
+ }
+ if (cls.indexOf('.') < 0) {
+ StringBuilder b = new StringBuilder(pkg);
+ b.append('.');
+ b.append(cls);
+ return b.toString();
+ }
+ return cls;
+ }
+
+ private static String buildCompoundName(String pkg,
+ CharSequence procSeq, String type, String[] outError) {
+ String proc = procSeq.toString();
+ char c = proc.charAt(0);
+ if (pkg != null && c == ':') {
+ if (proc.length() < 2) {
+ outError[0] = "Bad " + type + " name " + proc + " in package " + pkg
+ + ": must be at least two characters";
+ return null;
+ }
+ String subName = proc.substring(1);
+ String nameError = validateName(subName, false, false);
+ if (nameError != null) {
+ outError[0] = "Invalid " + type + " name " + proc + " in package "
+ + pkg + ": " + nameError;
+ return null;
+ }
+ return pkg + proc;
+ }
+ String nameError = validateName(proc, true, false);
+ if (nameError != null && !"system".equals(proc)) {
+ outError[0] = "Invalid " + type + " name " + proc + " in package "
+ + pkg + ": " + nameError;
+ return null;
+ }
+ return proc;
+ }
+
+ public static String buildProcessName(String pkg, String defProc,
+ CharSequence procSeq, int flags, String[] separateProcesses,
+ String[] outError) {
+ if ((flags&PARSE_IGNORE_PROCESSES) != 0 && !"system".equals(procSeq)) {
+ return defProc != null ? defProc : pkg;
+ }
+ if (separateProcesses != null) {
+ for (int i=separateProcesses.length-1; i>=0; i--) {
+ String sp = separateProcesses[i];
+ if (sp.equals(pkg) || sp.equals(defProc) || sp.equals(procSeq)) {
+ return pkg;
+ }
+ }
+ }
+ if (procSeq == null || procSeq.length() <= 0) {
+ return defProc;
+ }
+ return TextUtils.safeIntern(buildCompoundName(pkg, procSeq, "process", outError));
+ }
+
+ public static String buildTaskAffinityName(String pkg, String defProc,
+ CharSequence procSeq, String[] outError) {
+ if (procSeq == null) {
+ return defProc;
+ }
+ if (procSeq.length() <= 0) {
+ return null;
+ }
+ return buildCompoundName(pkg, procSeq, "taskAffinity", outError);
+ }
+
+ private boolean parseKeySets(Package owner, Resources res,
+ XmlResourceParser parser, String[] outError)
+ throws XmlPullParserException, IOException {
+ // we've encountered the 'key-sets' tag
+ // all the keys and keysets that we want must be defined here
+ // so we're going to iterate over the parser and pull out the things we want
+ int outerDepth = parser.getDepth();
+ int currentKeySetDepth = -1;
+ int type;
+ String currentKeySet = null;
+ ArrayMap<String, PublicKey> publicKeys = new ArrayMap<String, PublicKey>();
+ ArraySet<String> upgradeKeySets = new ArraySet<String>();
+ ArrayMap<String, ArraySet<String>> definedKeySets = new ArrayMap<String, ArraySet<String>>();
+ ArraySet<String> improperKeySets = new ArraySet<String>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG) {
+ if (parser.getDepth() == currentKeySetDepth) {
+ currentKeySet = null;
+ currentKeySetDepth = -1;
+ }
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("key-set")) {
+ if (currentKeySet != null) {
+ outError[0] = "Improperly nested 'key-set' tag at "
+ + parser.getPositionDescription();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ final TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestKeySet);
+ final String keysetName = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestKeySet_name);
+ definedKeySets.put(keysetName, new ArraySet<String>());
+ currentKeySet = keysetName;
+ currentKeySetDepth = parser.getDepth();
+ sa.recycle();
+ } else if (tagName.equals("public-key")) {
+ if (currentKeySet == null) {
+ outError[0] = "Improperly nested 'key-set' tag at "
+ + parser.getPositionDescription();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ final TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestPublicKey);
+ final String publicKeyName = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPublicKey_name);
+ final String encodedKey = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPublicKey_value);
+ if (encodedKey == null && publicKeys.get(publicKeyName) == null) {
+ outError[0] = "'public-key' " + publicKeyName + " must define a public-key value"
+ + " on first use at " + parser.getPositionDescription();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ sa.recycle();
+ return false;
+ } else if (encodedKey != null) {
+ PublicKey currentKey = parsePublicKey(encodedKey);
+ if (currentKey == null) {
+ Slog.w(TAG, "No recognized valid key in 'public-key' tag at "
+ + parser.getPositionDescription() + " key-set " + currentKeySet
+ + " will not be added to the package's defined key-sets.");
+ sa.recycle();
+ improperKeySets.add(currentKeySet);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ if (publicKeys.get(publicKeyName) == null
+ || publicKeys.get(publicKeyName).equals(currentKey)) {
+
+ /* public-key first definition, or matches old definition */
+ publicKeys.put(publicKeyName, currentKey);
+ } else {
+ outError[0] = "Value of 'public-key' " + publicKeyName
+ + " conflicts with previously defined value at "
+ + parser.getPositionDescription();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ sa.recycle();
+ return false;
+ }
+ }
+ definedKeySets.get(currentKeySet).add(publicKeyName);
+ sa.recycle();
+ XmlUtils.skipCurrentTag(parser);
+ } else if (tagName.equals("upgrade-key-set")) {
+ final TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUpgradeKeySet);
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUpgradeKeySet_name);
+ upgradeKeySets.add(name);
+ sa.recycle();
+ XmlUtils.skipCurrentTag(parser);
+ } else if (RIGID_PARSER) {
+ outError[0] = "Bad element under <key-sets>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ } else {
+ Slog.w(TAG, "Unknown element under <key-sets>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+ Set<String> publicKeyNames = publicKeys.keySet();
+ if (publicKeyNames.removeAll(definedKeySets.keySet())) {
+ outError[0] = "Package" + owner.packageName + " AndroidManifext.xml "
+ + "'key-set' and 'public-key' names must be distinct.";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ owner.mKeySetMapping = new ArrayMap<String, ArraySet<PublicKey>>();
+ for (ArrayMap.Entry<String, ArraySet<String>> e: definedKeySets.entrySet()) {
+ final String keySetName = e.getKey();
+ if (e.getValue().size() == 0) {
+ Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.xml "
+ + "'key-set' " + keySetName + " has no valid associated 'public-key'."
+ + " Not including in package's defined key-sets.");
+ continue;
+ } else if (improperKeySets.contains(keySetName)) {
+ Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.xml "
+ + "'key-set' " + keySetName + " contained improper 'public-key'"
+ + " tags. Not including in package's defined key-sets.");
+ continue;
+ }
+ owner.mKeySetMapping.put(keySetName, new ArraySet<PublicKey>());
+ for (String s : e.getValue()) {
+ owner.mKeySetMapping.get(keySetName).add(publicKeys.get(s));
+ }
+ }
+ if (owner.mKeySetMapping.keySet().containsAll(upgradeKeySets)) {
+ owner.mUpgradeKeySets = upgradeKeySets;
+ } else {
+ outError[0] ="Package" + owner.packageName + " AndroidManifext.xml "
+ + "does not define all 'upgrade-key-set's .";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ return true;
+ }
+
+ private boolean parsePermissionGroup(Package owner, int flags, Resources res,
+ XmlResourceParser parser, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup);
+
+ int requestDetailResourceId = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_requestDetail, 0);
+ int backgroundRequestResourceId = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_backgroundRequest,
+ 0);
+ int backgroundRequestDetailResourceId = sa.getResourceId(
+ com.android.internal.R.styleable
+ .AndroidManifestPermissionGroup_backgroundRequestDetail, 0);
+
+ PermissionGroup perm = new PermissionGroup(owner, requestDetailResourceId,
+ backgroundRequestResourceId, backgroundRequestDetailResourceId);
+
+ if (!parsePackageItemInfo(owner, perm.info, outError,
+ "<permission-group>", sa, true /*nameRequired*/,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_label,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_banner)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ perm.info.descriptionRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_description,
+ 0);
+ perm.info.requestRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_request, 0);
+ perm.info.flags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0);
+ perm.info.priority = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_priority, 0);
+
+ sa.recycle();
+
+ if (!parseAllMetaData(res, parser, "<permission-group>", perm,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.permissionGroups.add(perm);
+
+ return true;
+ }
+
+ private boolean parsePermission(Package owner, Resources res,
+ XmlResourceParser parser, String[] outError)
+ throws XmlPullParserException, IOException {
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestPermission);
+
+ String backgroundPermission = null;
+ if (sa.hasValue(
+ com.android.internal.R.styleable.AndroidManifestPermission_backgroundPermission)) {
+ if ("android".equals(owner.packageName)) {
+ backgroundPermission = sa.getNonResourceString(
+ com.android.internal.R.styleable
+ .AndroidManifestPermission_backgroundPermission);
+ } else {
+ Slog.w(TAG, owner.packageName + " defines a background permission. Only the "
+ + "'android' package can do that.");
+ }
+ }
+
+ Permission perm = new Permission(owner, backgroundPermission);
+ if (!parsePackageItemInfo(owner, perm.info, outError,
+ "<permission>", sa, true /*nameRequired*/,
+ com.android.internal.R.styleable.AndroidManifestPermission_name,
+ com.android.internal.R.styleable.AndroidManifestPermission_label,
+ com.android.internal.R.styleable.AndroidManifestPermission_icon,
+ com.android.internal.R.styleable.AndroidManifestPermission_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestPermission_logo,
+ com.android.internal.R.styleable.AndroidManifestPermission_banner)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ perm.info.group = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPermission_permissionGroup);
+ if (perm.info.group != null) {
+ perm.info.group = perm.info.group.intern();
+ }
+
+ perm.info.descriptionRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermission_description,
+ 0);
+
+ perm.info.requestRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermission_request, 0);
+
+ perm.info.protectionLevel = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel,
+ PermissionInfo.PROTECTION_NORMAL);
+
+ perm.info.flags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermission_permissionFlags, 0);
+
+ // For now only platform runtime permissions can be restricted
+ if (!perm.info.isRuntime() || !"android".equals(perm.info.packageName)) {
+ perm.info.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
+ perm.info.flags &= ~PermissionInfo.FLAG_SOFT_RESTRICTED;
+ } else {
+ // The platform does not get to specify conflicting permissions
+ if ((perm.info.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0
+ && (perm.info.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) {
+ throw new IllegalStateException("Permission cannot be both soft and hard"
+ + " restricted: " + perm.info.name);
+ }
+ }
+
+ sa.recycle();
+
+ if (perm.info.protectionLevel == -1) {
+ outError[0] = "<permission> does not specify protectionLevel";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ perm.info.protectionLevel = PermissionInfo.fixProtectionLevel(perm.info.protectionLevel);
+
+ 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-instant flag but is "
+ + "not based on signature type";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ }
+
+ if (!parseAllMetaData(res, parser, "<permission>", perm, outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.permissions.add(perm);
+
+ return true;
+ }
+
+ private boolean parsePermissionTree(Package owner, Resources res,
+ XmlResourceParser parser, String[] outError)
+ throws XmlPullParserException, IOException {
+ Permission perm = new Permission(owner, (String) null);
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree);
+
+ if (!parsePackageItemInfo(owner, perm.info, outError,
+ "<permission-tree>", sa, true /*nameRequired*/,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_name,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_label,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_icon,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_logo,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_banner)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ sa.recycle();
+
+ int index = perm.info.name.indexOf('.');
+ if (index > 0) {
+ index = perm.info.name.indexOf('.', index+1);
+ }
+ if (index < 0) {
+ outError[0] = "<permission-tree> name has less than three segments: "
+ + perm.info.name;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ perm.info.descriptionRes = 0;
+ perm.info.requestRes = 0;
+ perm.info.protectionLevel = PermissionInfo.PROTECTION_NORMAL;
+ perm.tree = true;
+
+ if (!parseAllMetaData(res, parser, "<permission-tree>", perm,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.permissions.add(perm);
+
+ return true;
+ }
+
+ private Instrumentation parseInstrumentation(Package owner, Resources res,
+ XmlResourceParser parser, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation);
+
+ if (mParseInstrumentationArgs == null) {
+ mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_name,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_label,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_icon,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_logo,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_banner);
+ mParseInstrumentationArgs.tag = "<instrumentation>";
+ }
+
+ mParseInstrumentationArgs.sa = sa;
+
+ Instrumentation a = new Instrumentation(mParseInstrumentationArgs,
+ new InstrumentationInfo());
+ if (outError[0] != null) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ String str;
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_targetPackage);
+ a.info.targetPackage = str != null ? str.intern() : null;
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_targetProcesses);
+ a.info.targetProcesses = str != null ? str.intern() : null;
+
+ a.info.handleProfiling = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_handleProfiling,
+ false);
+
+ a.info.functionalTest = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_functionalTest,
+ false);
+
+ sa.recycle();
+
+ if (a.info.targetPackage == null) {
+ outError[0] = "<instrumentation> does not specify targetPackage";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ if (!parseAllMetaData(res, parser, "<instrumentation>", a,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ owner.instrumentation.add(a);
+
+ return a;
+ }
+
+ /**
+ * Parse the {@code application} XML tree at the current parse location in a
+ * <em>base APK</em> manifest.
+ * <p>
+ * When adding new features, carefully consider if they should also be
+ * supported by split APKs.
+ */
+ @UnsupportedAppUsage
+ private boolean parseBaseApplication(Package owner, Resources res,
+ XmlResourceParser parser, int flags, String[] outError)
+ throws XmlPullParserException, IOException {
+ final ApplicationInfo ai = owner.applicationInfo;
+ final String pkgName = owner.applicationInfo.packageName;
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestApplication);
+
+ ai.iconRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
+ ai.roundIconRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_roundIcon, 0);
+
+ if (!parsePackageItemInfo(owner, ai, outError,
+ "<application>", sa, false /*nameRequired*/,
+ com.android.internal.R.styleable.AndroidManifestApplication_name,
+ com.android.internal.R.styleable.AndroidManifestApplication_label,
+ com.android.internal.R.styleable.AndroidManifestApplication_icon,
+ com.android.internal.R.styleable.AndroidManifestApplication_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestApplication_logo,
+ com.android.internal.R.styleable.AndroidManifestApplication_banner)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ if (ai.name != null) {
+ ai.className = ai.name;
+ }
+
+ String manageSpaceActivity = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity,
+ Configuration.NATIVE_CONFIG_VERSION);
+ if (manageSpaceActivity != null) {
+ ai.manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity,
+ outError);
+ }
+
+ boolean allowBackup = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true);
+ if (allowBackup) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+
+ // backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
+ // and restoreAnyVersion are only relevant if backup is possible for the
+ // given application.
+ String backupAgent = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_backupAgent,
+ Configuration.NATIVE_CONFIG_VERSION);
+ if (backupAgent != null) {
+ ai.backupAgentName = buildClassName(pkgName, backupAgent, outError);
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "android:backupAgent = " + ai.backupAgentName
+ + " from " + pkgName + "+" + backupAgent);
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_killAfterRestore,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_KILL_AFTER_RESTORE;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_restoreAnyVersion,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_fullBackupOnly,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_backupInForeground,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND;
+ }
+ }
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestApplication_fullBackupContent);
+ if (v != null && (ai.fullBackupContent = v.resourceId) == 0) {
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "fullBackupContent specified as boolean=" +
+ (v.data == 0 ? "false" : "true"));
+ }
+ // "false" => -1, "true" => 0
+ ai.fullBackupContent = (v.data == 0 ? -1 : 0);
+ }
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "fullBackupContent=" + ai.fullBackupContent + " for " + pkgName);
+ }
+ }
+
+ ai.theme = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
+ ai.descriptionRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_description, 0);
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_persistent,
+ false)) {
+ // Check if persistence is based on a feature being present
+ final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable
+ .AndroidManifestApplication_persistentWhenFeatureAvailable);
+ if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
+ ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
+ }
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers,
+ false)) {
+ owner.mRequiredForAllUsers = true;
+ }
+
+ String restrictedAccountType = sa.getString(com.android.internal.R.styleable
+ .AndroidManifestApplication_restrictedAccountType);
+ if (restrictedAccountType != null && restrictedAccountType.length() > 0) {
+ owner.mRestrictedAccountType = restrictedAccountType;
+ }
+
+ String requiredAccountType = sa.getString(com.android.internal.R.styleable
+ .AndroidManifestApplication_requiredAccountType);
+ if (requiredAccountType != null && requiredAccountType.length() > 0) {
+ owner.mRequiredAccountType = requiredAccountType;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_vmSafeMode,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE;
+ }
+
+ owner.baseHardwareAccelerated = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
+ owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
+ if (owner.baseHardwareAccelerated) {
+ ai.flags |= ApplicationInfo.FLAG_HARDWARE_ACCELERATED;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_HAS_CODE;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowTaskReparenting,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowClearUserData,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA;
+ }
+
+ // The parent package controls installation, hence specify test only installs.
+ if (owner.parentPackage == null) {
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_testOnly,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_TEST_ONLY;
+ }
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_largeHeap,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_LARGE_HEAP;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_usesCleartextTraffic,
+ owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P)) {
+ ai.flags |= ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_supportsRtl,
+ false /* default is no RTL support*/)) {
+ ai.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_multiArch,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_MULTIARCH;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_extractNativeLibs,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS;
+ }
+
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_useEmbeddedDex,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USE_EMBEDDED_DEX;
+ }
+
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
+ }
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_directBootAware,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+ }
+
+ if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestApplication_resizeableActivity, true)) {
+ ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
+ } else {
+ ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+ }
+ } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
+ ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable
+ .AndroidManifestApplication_allowClearUserDataOnFailedRestore,
+ true)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE;
+ }
+
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture,
+ owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE;
+ }
+
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
+ owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
+ }
+
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING;
+ }
+
+ ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
+ ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0);
+
+ ai.networkSecurityConfigRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
+ 0);
+ ai.category = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestApplication_appCategory,
+ ApplicationInfo.CATEGORY_UNDEFINED);
+
+ String str;
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_permission, 0);
+ ai.permission = (str != null && str.length() > 0) ? str.intern() : null;
+
+ if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity,
+ Configuration.NATIVE_CONFIG_VERSION);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity);
+ }
+ ai.taskAffinity = buildTaskAffinityName(ai.packageName, ai.packageName,
+ str, outError);
+ String factory = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_appComponentFactory);
+ if (factory != null) {
+ ai.appComponentFactory = buildClassName(ai.packageName, factory, outError);
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_usesNonSdkApi, false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hasFragileUserData,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA;
+ }
+
+ if (outError[0] == null) {
+ CharSequence pname;
+ if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ pname = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_process,
+ Configuration.NATIVE_CONFIG_VERSION);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ pname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_process);
+ }
+ ai.processName = buildProcessName(ai.packageName, null, pname,
+ flags, mSeparateProcesses, outError);
+
+ ai.enabled = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_isGame, false)) {
+ ai.flags |= ApplicationInfo.FLAG_IS_GAME;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+
+ // A heavy-weight application can not be in a custom process.
+ // We can do direct compare because we intern all strings.
+ if (ai.processName != null && !ai.processName.equals(ai.packageName)) {
+ outError[0] = "cantSaveState applications can not use custom processes";
+ }
+ }
+ }
+
+ ai.uiOptions = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestApplication_uiOptions, 0);
+
+ ai.classLoaderName = sa.getString(
+ com.android.internal.R.styleable.AndroidManifestApplication_classLoader);
+ if (ai.classLoaderName != null
+ && !ClassLoaderFactory.isValidClassLoaderName(ai.classLoaderName)) {
+ outError[0] = "Invalid class loader name: " + ai.classLoaderName;
+ }
+
+ ai.zygotePreloadName = sa.getString(
+ com.android.internal.R.styleable.AndroidManifestApplication_zygotePreloadName);
+
+ sa.recycle();
+
+ if (outError[0] != null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ final int innerDepth = parser.getDepth();
+ // IMPORTANT: These must only be cached for a single <application> to avoid components
+ // 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) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("activity")) {
+ Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
+ owner.baseHardwareAccelerated);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ hasActivityOrder |= (a.order != 0);
+ owner.activities.add(a);
+
+ } else if (tagName.equals("receiver")) {
+ Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
+ true, false);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ hasReceiverOrder |= (a.order != 0);
+ owner.receivers.add(a);
+
+ } else if (tagName.equals("service")) {
+ Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
+ if (s == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ hasServiceOrder |= (s.order != 0);
+ owner.services.add(s);
+
+ } else if (tagName.equals("provider")) {
+ Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
+ if (p == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.providers.add(p);
+
+ } else if (tagName.equals("activity-alias")) {
+ Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ hasActivityOrder |= (a.order != 0);
+ owner.activities.add(a);
+
+ } else if (parser.getName().equals("meta-data")) {
+ // note: application meta-data is stored off to the side, so it can
+ // remain null in the primary copy (we like to avoid extra copies because
+ // it can be large)
+ if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,
+ outError)) == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ } else if (tagName.equals("static-library")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ final String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary_name);
+ final int version = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary_version, -1);
+ final int versionMajor = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary_versionMajor,
+ 0);
+
+ sa.recycle();
+
+ // Since the app canot run without a static lib - fail if malformed
+ if (lname == null || version < 0) {
+ outError[0] = "Bad static-library declaration name: " + lname
+ + " version: " + version;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ if (owner.mSharedUserId != null) {
+ outError[0] = "sharedUserId not allowed in static shared library";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ if (owner.staticSharedLibName != null) {
+ outError[0] = "Multiple static-shared libs for package " + pkgName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ owner.staticSharedLibName = lname.intern();
+ if (version >= 0) {
+ owner.staticSharedLibVersion =
+ PackageInfo.composeLongVersionCode(versionMajor, version);
+ } else {
+ owner.staticSharedLibVersion = version;
+ }
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY;
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("library")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestLibrary_name);
+
+ sa.recycle();
+
+ if (lname != null) {
+ lname = lname.intern();
+ if (!ArrayUtils.contains(owner.libraryNames, lname)) {
+ owner.libraryNames = ArrayUtils.add(
+ owner.libraryNames, lname);
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("uses-static-library")) {
+ if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
+ return false;
+ }
+
+ } else if (tagName.equals("uses-library")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+ boolean req = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_required,
+ true);
+
+ sa.recycle();
+
+ if (lname != null) {
+ lname = lname.intern();
+ if (req) {
+ owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname);
+ } else {
+ owner.usesOptionalLibraries = ArrayUtils.add(
+ owner.usesOptionalLibraries, lname);
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("uses-package")) {
+ // Dependencies for app installers; we don't currently try to
+ // enforce this.
+ XmlUtils.skipCurrentTag(parser);
+ } else if (tagName.equals("profileable")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestProfileable);
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProfileable_shell, false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL;
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <application>: " + tagName
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under <application>: " + tagName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ }
+ }
+
+ if (TextUtils.isEmpty(owner.staticSharedLibName)) {
+ // Add a hidden app detail activity to normal apps which forwards user to App Details
+ // page.
+ Activity a = generateAppDetailsHiddenActivity(owner, flags, outError,
+ owner.baseHardwareAccelerated);
+ owner.activities.add(a);
+ }
+
+ 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);
+ setMinAspectRatio(owner);
+ setSupportsSizeChanges(owner);
+
+ if (hasDomainURLs(owner)) {
+ owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+ } else {
+ owner.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
+ */
+ private static boolean hasDomainURLs(Package pkg) {
+ if (pkg == null || pkg.activities == null) return false;
+ final ArrayList<Activity> activities = pkg.activities;
+ final int countActivities = activities.size();
+ for (int n=0; n<countActivities; n++) {
+ Activity activity = activities.get(n);
+ ArrayList<ActivityIntentInfo> filters = activity.intents;
+ if (filters == null) continue;
+ final int countFilters = filters.size();
+ for (int m=0; m<countFilters; m++) {
+ ActivityIntentInfo aii = filters.get(m);
+ if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
+ if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
+ if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Parse the {@code application} XML tree at the current parse location in a
+ * <em>split APK</em> manifest.
+ * <p>
+ * Note that split APKs have many more restrictions on what they're capable
+ * of doing, so many valid features of a base APK have been carefully
+ * omitted here.
+ */
+ private boolean parseSplitApplication(Package owner, Resources res, XmlResourceParser parser,
+ int flags, int splitIndex, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestApplication);
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) {
+ owner.splitFlags[splitIndex] |= ApplicationInfo.FLAG_HAS_CODE;
+ }
+
+ final String classLoaderName = sa.getString(
+ com.android.internal.R.styleable.AndroidManifestApplication_classLoader);
+ if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
+ owner.applicationInfo.splitClassLoaderNames[splitIndex] = classLoaderName;
+ } else {
+ outError[0] = "Invalid class loader name: " + classLoaderName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ final int innerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ ComponentInfo parsedComponent = null;
+
+ // IMPORTANT: These must only be cached for a single <application> to avoid components
+ // getting added to the wrong package.
+ final CachedComponentArgs cachedArgs = new CachedComponentArgs();
+ String tagName = parser.getName();
+ if (tagName.equals("activity")) {
+ Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
+ owner.baseHardwareAccelerated);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.activities.add(a);
+ parsedComponent = a.info;
+
+ } else if (tagName.equals("receiver")) {
+ Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
+ true, false);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.receivers.add(a);
+ parsedComponent = a.info;
+
+ } else if (tagName.equals("service")) {
+ Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
+ if (s == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.services.add(s);
+ parsedComponent = s.info;
+
+ } else if (tagName.equals("provider")) {
+ Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
+ if (p == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.providers.add(p);
+ parsedComponent = p.info;
+
+ } else if (tagName.equals("activity-alias")) {
+ Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.activities.add(a);
+ parsedComponent = a.info;
+
+ } else if (parser.getName().equals("meta-data")) {
+ // note: application meta-data is stored off to the side, so it can
+ // remain null in the primary copy (we like to avoid extra copies because
+ // it can be large)
+ if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,
+ outError)) == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ } else if (tagName.equals("uses-static-library")) {
+ if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
+ return false;
+ }
+
+ } else if (tagName.equals("uses-library")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+ boolean req = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_required,
+ true);
+
+ sa.recycle();
+
+ if (lname != null) {
+ lname = lname.intern();
+ if (req) {
+ // Upgrade to treat as stronger constraint
+ owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname);
+ owner.usesOptionalLibraries = ArrayUtils.remove(
+ owner.usesOptionalLibraries, lname);
+ } else {
+ // Ignore if someone already defined as required
+ if (!ArrayUtils.contains(owner.usesLibraries, lname)) {
+ owner.usesOptionalLibraries = ArrayUtils.add(
+ owner.usesOptionalLibraries, lname);
+ }
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("uses-package")) {
+ // Dependencies for app installers; we don't currently try to
+ // enforce this.
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <application>: " + tagName
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under <application>: " + tagName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ }
+
+ if (parsedComponent != null && parsedComponent.splitName == null) {
+ // If the loaded component did not specify a split, inherit the split name
+ // based on the split it is defined in.
+ // This is used to later load the correct split when starting this
+ // component.
+ parsedComponent.splitName = owner.splitNames[splitIndex];
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
+ String[] outError, String tag, TypedArray sa, boolean nameRequired,
+ int nameRes, int labelRes, int iconRes, int roundIconRes, int logoRes, int bannerRes) {
+ // This case can only happen in unit tests where we sometimes need to create fakes
+ // of various package parser data structures.
+ if (sa == null) {
+ outError[0] = tag + " does not contain any attributes";
+ return false;
+ }
+
+ String name = sa.getNonConfigurationString(nameRes, 0);
+ if (name == null) {
+ if (nameRequired) {
+ outError[0] = tag + " does not specify android:name";
+ return false;
+ }
+ } else {
+ String outInfoName
+ = buildClassName(owner.applicationInfo.packageName, name, outError);
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) {
+ outError[0] = tag + " invalid android:name";
+ return false;
+ }
+ outInfo.name = outInfoName;
+ if (outInfoName == null) {
+ return false;
+ }
+ }
+
+ int roundIconVal = sUseRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0;
+ if (roundIconVal != 0) {
+ outInfo.icon = roundIconVal;
+ outInfo.nonLocalizedLabel = null;
+ } else {
+ int iconVal = sa.getResourceId(iconRes, 0);
+ if (iconVal != 0) {
+ outInfo.icon = iconVal;
+ outInfo.nonLocalizedLabel = null;
+ }
+ }
+
+ int logoVal = sa.getResourceId(logoRes, 0);
+ if (logoVal != 0) {
+ outInfo.logo = logoVal;
+ }
+
+ int bannerVal = sa.getResourceId(bannerRes, 0);
+ if (bannerVal != 0) {
+ outInfo.banner = bannerVal;
+ }
+
+ TypedValue v = sa.peekValue(labelRes);
+ if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
+ outInfo.nonLocalizedLabel = v.coerceToString();
+ }
+
+ outInfo.packageName = owner.packageName;
+
+ return true;
+ }
+
+ /**
+ * Generate activity object that forwards user to App Details page automatically.
+ * This activity should be invisible to user and user should not know or see it.
+ */
+ private @NonNull PackageParser.Activity generateAppDetailsHiddenActivity(
+ PackageParser.Package owner, int flags, String[] outError,
+ boolean hardwareAccelerated) {
+
+ // Build custom App Details activity info instead of parsing it from xml
+ Activity a = new Activity(owner, PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME,
+ new ActivityInfo());
+ a.owner = owner;
+ a.setPackageName(owner.packageName);
+
+ a.info.theme = android.R.style.Theme_NoDisplay;
+ a.info.exported = true;
+ a.info.name = PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME;
+ a.info.processName = owner.applicationInfo.processName;
+ a.info.uiOptions = a.info.applicationInfo.uiOptions;
+ a.info.taskAffinity = buildTaskAffinityName(owner.packageName, owner.packageName,
+ ":app_details", outError);
+ a.info.enabled = true;
+ a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ a.info.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
+ a.info.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
+ a.info.configChanges = getActivityConfigChanges(0, 0);
+ a.info.softInputMode = 0;
+ a.info.persistableMode = ActivityInfo.PERSIST_NEVER;
+ a.info.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ a.info.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+ a.info.lockTaskLaunchMode = 0;
+ a.info.directBootAware = false;
+ a.info.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
+ a.info.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+ if (hardwareAccelerated) {
+ a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
+ }
+ return a;
+ }
+
+ private Activity parseActivity(Package owner, Resources res,
+ XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
+ boolean receiver, boolean hardwareAccelerated)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);
+
+ if (cachedArgs.mActivityArgs == null) {
+ cachedArgs.mActivityArgs = new ParseComponentArgs(owner, outError,
+ R.styleable.AndroidManifestActivity_name,
+ R.styleable.AndroidManifestActivity_label,
+ R.styleable.AndroidManifestActivity_icon,
+ R.styleable.AndroidManifestActivity_roundIcon,
+ R.styleable.AndroidManifestActivity_logo,
+ R.styleable.AndroidManifestActivity_banner,
+ mSeparateProcesses,
+ R.styleable.AndroidManifestActivity_process,
+ R.styleable.AndroidManifestActivity_description,
+ R.styleable.AndroidManifestActivity_enabled);
+ }
+
+ cachedArgs.mActivityArgs.tag = receiver ? "<receiver>" : "<activity>";
+ cachedArgs.mActivityArgs.sa = sa;
+ cachedArgs.mActivityArgs.flags = flags;
+
+ Activity a = new Activity(cachedArgs.mActivityArgs, new ActivityInfo());
+ if (outError[0] != null) {
+ sa.recycle();
+ return null;
+ }
+
+ boolean setExported = sa.hasValue(R.styleable.AndroidManifestActivity_exported);
+ if (setExported) {
+ a.info.exported = sa.getBoolean(R.styleable.AndroidManifestActivity_exported, false);
+ }
+
+ a.info.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0);
+
+ a.info.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions,
+ a.info.applicationInfo.uiOptions);
+
+ String parentName = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestActivity_parentActivityName,
+ Configuration.NATIVE_CONFIG_VERSION);
+ if (parentName != null) {
+ String parentClassName = buildClassName(a.info.packageName, parentName, outError);
+ if (outError[0] == null) {
+ a.info.parentActivityName = parentClassName;
+ } else {
+ Log.e(TAG, "Activity " + a.info.name + " specified invalid parentActivityName " +
+ parentName);
+ outError[0] = null;
+ }
+ }
+
+ String str;
+ str = sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_permission, 0);
+ if (str == null) {
+ a.info.permission = owner.applicationInfo.permission;
+ } else {
+ a.info.permission = str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestActivity_taskAffinity,
+ Configuration.NATIVE_CONFIG_VERSION);
+ a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
+ owner.applicationInfo.taskAffinity, str, outError);
+
+ a.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0);
+
+ a.info.flags = 0;
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestActivity_multiprocess, false)) {
+ a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnTaskLaunch, false)) {
+ a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_clearTaskOnLaunch, false)) {
+ a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_noHistory, false)) {
+ a.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysRetainTaskState, false)) {
+ a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_stateNotNeeded, false)) {
+ a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_excludeFromRecents, false)) {
+ a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowTaskReparenting,
+ (owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) {
+ a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, false)) {
+ a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false)
+ || sa.getBoolean(R.styleable.AndroidManifestActivity_showForAllUsers, false)) {
+ a.info.flags |= ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_immersive, false)) {
+ a.info.flags |= ActivityInfo.FLAG_IMMERSIVE;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_systemUserOnly, false)) {
+ a.info.flags |= ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+ }
+
+ if (!receiver) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_hardwareAccelerated,
+ hardwareAccelerated)) {
+ a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
+ }
+
+ a.info.launchMode = sa.getInt(
+ R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE);
+ a.info.documentLaunchMode = sa.getInt(
+ R.styleable.AndroidManifestActivity_documentLaunchMode,
+ ActivityInfo.DOCUMENT_LAUNCH_NONE);
+ a.info.maxRecents = sa.getInt(
+ R.styleable.AndroidManifestActivity_maxRecents,
+ ActivityTaskManager.getDefaultAppRecentsLimitStatic());
+ a.info.configChanges = getActivityConfigChanges(
+ sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0),
+ sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0));
+ a.info.softInputMode = sa.getInt(
+ R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
+
+ a.info.persistableMode = sa.getInteger(
+ R.styleable.AndroidManifestActivity_persistableMode,
+ ActivityInfo.PERSIST_ROOT_ONLY);
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowEmbedded, false)) {
+ a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_autoRemoveFromRecents, false)) {
+ a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_relinquishTaskIdentity, false)) {
+ a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_resumeWhilePausing, false)) {
+ a.info.flags |= ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+ }
+
+ a.info.screenOrientation = sa.getInt(
+ R.styleable.AndroidManifestActivity_screenOrientation,
+ SCREEN_ORIENTATION_UNSPECIFIED);
+
+ setActivityResizeMode(a.info, sa, owner);
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture,
+ false)) {
+ a.info.flags |= FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) {
+ a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
+ }
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_maxAspectRatio)
+ && sa.getType(R.styleable.AndroidManifestActivity_maxAspectRatio)
+ == TypedValue.TYPE_FLOAT) {
+ a.setMaxAspectRatio(sa.getFloat(R.styleable.AndroidManifestActivity_maxAspectRatio,
+ 0 /*default*/));
+ }
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio)
+ && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio)
+ == TypedValue.TYPE_FLOAT) {
+ a.setMinAspectRatio(sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio,
+ 0 /*default*/));
+ }
+
+ a.info.lockTaskLaunchMode =
+ sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
+
+ a.info.directBootAware = sa.getBoolean(
+ R.styleable.AndroidManifestActivity_directBootAware,
+ false);
+
+ a.info.requestedVrComponent =
+ sa.getString(R.styleable.AndroidManifestActivity_enableVrMode);
+
+ a.info.rotationAnimation =
+ sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, ROTATION_ANIMATION_UNSPECIFIED);
+
+ a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode,
+ ActivityInfo.COLOR_MODE_DEFAULT);
+
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, false)) {
+ a.info.flags |= ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_showWhenLocked, false)) {
+ a.info.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_turnScreenOn, false)) {
+ a.info.flags |= ActivityInfo.FLAG_TURN_SCREEN_ON;
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_inheritShowWhenLocked, false)) {
+ a.info.privateFlags |= ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED;
+ }
+ } else {
+ a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ a.info.configChanges = 0;
+
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) {
+ a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
+ }
+
+ a.info.directBootAware = sa.getBoolean(
+ R.styleable.AndroidManifestActivity_directBootAware,
+ false);
+ }
+
+ if (a.info.directBootAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
+ }
+
+ // can't make this final; we may set it later via meta-data
+ boolean visibleToEphemeral =
+ sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
+ if (visibleToEphemeral) {
+ a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ owner.visibleToInstantApps = true;
+ }
+
+ sa.recycle();
+
+ if (receiver && (owner.applicationInfo.privateFlags
+ &ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
+ // A heavy-weight application can not have receives in its main process
+ // We can do direct compare because we intern all strings.
+ if (a.info.processName == owner.packageName) {
+ outError[0] = "Heavy-weight applications can not have receivers in main process";
+ }
+ }
+
+ if (outError[0] != null) {
+ return null;
+ }
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getName().equals("intent-filter")) {
+ ActivityIntentInfo intent = new ActivityIntentInfo(a);
+ if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/,
+ intent, outError)) {
+ return null;
+ }
+ if (intent.countActions() == 0) {
+ Slog.w(TAG, "No actions in intent filter at "
+ + 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
+ final int visibility = visibleToEphemeral
+ ? IntentFilter.VISIBILITY_EXPLICIT
+ : !receiver && isImplicitlyExposedIntent(intent)
+ ? IntentFilter.VISIBILITY_IMPLICIT
+ : IntentFilter.VISIBILITY_NONE;
+ intent.setVisibilityToInstantApp(visibility);
+ if (intent.isVisibleToInstantApp()) {
+ a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ }
+ if (intent.isImplicitlyVisibleToInstantApp()) {
+ a.info.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
+ }
+ if (LOG_UNSAFE_BROADCASTS && receiver
+ && (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O)) {
+ for (int i = 0; i < intent.countActions(); i++) {
+ final String action = intent.getAction(i);
+ if (action == null || !action.startsWith("android.")) continue;
+ if (!SAFE_BROADCASTS.contains(action)) {
+ Slog.w(TAG, "Broadcast " + action + " may never be delivered to "
+ + owner.packageName + " as requested at: "
+ + parser.getPositionDescription());
+ }
+ }
+ }
+ } else if (!receiver && parser.getName().equals("preferred")) {
+ ActivityIntentInfo intent = new ActivityIntentInfo(a);
+ if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/,
+ intent, outError)) {
+ return null;
+ }
+ if (intent.countActions() == 0) {
+ Slog.w(TAG, "No actions in preferred at "
+ + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ } else {
+ if (owner.preferredActivityFilters == null) {
+ owner.preferredActivityFilters = new ArrayList<ActivityIntentInfo>();
+ }
+ owner.preferredActivityFilters.add(intent);
+ }
+ // adjust activity flags when we implicitly expose it via a browsable filter
+ final int visibility = visibleToEphemeral
+ ? IntentFilter.VISIBILITY_EXPLICIT
+ : !receiver && isImplicitlyExposedIntent(intent)
+ ? IntentFilter.VISIBILITY_IMPLICIT
+ : IntentFilter.VISIBILITY_NONE;
+ intent.setVisibilityToInstantApp(visibility);
+ if (intent.isVisibleToInstantApp()) {
+ a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ }
+ if (intent.isImplicitlyVisibleToInstantApp()) {
+ a.info.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
+ }
+ } else if (parser.getName().equals("meta-data")) {
+ if ((a.metaData = parseMetaData(res, parser, a.metaData,
+ outError)) == null) {
+ return null;
+ }
+ } else if (!receiver && parser.getName().equals("layout")) {
+ parseLayout(res, parser, a);
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ if (receiver) {
+ Slog.w(TAG, "Unknown element under <receiver>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ } else {
+ Slog.w(TAG, "Unknown element under <activity>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ if (receiver) {
+ outError[0] = "Bad element under <receiver>: " + parser.getName();
+ } else {
+ outError[0] = "Bad element under <activity>: " + parser.getName();
+ }
+ return null;
+ }
+ }
+ }
+
+ resolveWindowLayout(a);
+
+ if (!setExported) {
+ a.info.exported = a.intents.size() > 0;
+ }
+
+ return a;
+ }
+
+ private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) {
+ final boolean appExplicitDefault = (owner.applicationInfo.privateFlags
+ & (PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE
+ | PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE)) != 0;
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
+ || appExplicitDefault) {
+ // Activity or app explicitly set if it is resizeable or not;
+ final boolean appResizeable = (owner.applicationInfo.privateFlags
+ & PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE) != 0;
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
+ appResizeable)) {
+ aInfo.resizeMode = RESIZE_MODE_RESIZEABLE;
+ } else {
+ aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ }
+ return;
+ }
+
+ if ((owner.applicationInfo.privateFlags
+ & PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) != 0) {
+ // The activity or app didn't explicitly set the resizing option, however we want to
+ // make it resize due to the sdk version it is targeting.
+ aInfo.resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+ return;
+ }
+
+ // resize preference isn't set and target sdk version doesn't support resizing apps by
+ // default. For the app to be resizeable if it isn't fixed orientation or immersive.
+ if (aInfo.isFixedOrientationPortrait()) {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+ } else if (aInfo.isFixedOrientationLandscape()) {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+ } else if (aInfo.isFixedOrientation()) {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ } else {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+ }
+ }
+
+ /**
+ * Sets every the max aspect ratio of every child activity that doesn't already have an aspect
+ * ratio set.
+ */
+ private void setMaxAspectRatio(Package owner) {
+ // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
+ // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
+ float maxAspectRatio = owner.applicationInfo.targetSdkVersion < O
+ ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+
+ if (owner.applicationInfo.maxAspectRatio != 0) {
+ // Use the application max aspect ration as default if set.
+ maxAspectRatio = owner.applicationInfo.maxAspectRatio;
+ } else if (owner.mAppMetaData != null
+ && owner.mAppMetaData.containsKey(METADATA_MAX_ASPECT_RATIO)) {
+ maxAspectRatio = owner.mAppMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
+ }
+
+ for (Activity activity : owner.activities) {
+ // If the max aspect ratio for the activity has already been set, skip.
+ if (activity.hasMaxAspectRatio()) {
+ continue;
+ }
+
+ // By default we prefer to use a values defined on the activity directly than values
+ // defined on the application. We do not check the styled attributes on the activity
+ // as it would have already been set when we processed the activity. We wait to process
+ // the meta data here since this method is called at the end of processing the
+ // application and all meta data is guaranteed.
+ final float activityAspectRatio = activity.metaData != null
+ ? activity.metaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
+ : maxAspectRatio;
+
+ activity.setMaxAspectRatio(activityAspectRatio);
+ }
+ }
+
+ /**
+ * Sets every the min aspect ratio of every child activity that doesn't already have an aspect
+ * ratio set.
+ */
+ private void setMinAspectRatio(Package owner) {
+ // Use the application max aspect ration as default if set.
+ final float minAspectRatio = owner.applicationInfo.minAspectRatio;
+
+ for (Activity activity : owner.activities) {
+ if (activity.hasMinAspectRatio()) {
+ continue;
+ }
+ activity.setMinAspectRatio(minAspectRatio);
+ }
+ }
+
+ private void setSupportsSizeChanges(Package owner) {
+ final boolean supportsSizeChanges = owner.mAppMetaData != null
+ && owner.mAppMetaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false);
+
+ for (Activity activity : owner.activities) {
+ if (supportsSizeChanges || (activity.metaData != null
+ && activity.metaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+ activity.info.supportsSizeChanges = true;
+ }
+ }
+ }
+
+ /**
+ * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
+ * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
+ * AndroidManifest.xml.
+ * @hide Exposed for unit testing only.
+ */
+ public static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) {
+ return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK);
+ }
+
+ private void parseLayout(Resources res, AttributeSet attrs, Activity a) {
+ TypedArray sw = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestLayout);
+ int width = -1;
+ float widthFraction = -1f;
+ int height = -1;
+ float heightFraction = -1f;
+ final int widthType = sw.getType(
+ com.android.internal.R.styleable.AndroidManifestLayout_defaultWidth);
+ if (widthType == TypedValue.TYPE_FRACTION) {
+ widthFraction = sw.getFraction(
+ com.android.internal.R.styleable.AndroidManifestLayout_defaultWidth,
+ 1, 1, -1);
+ } else if (widthType == TypedValue.TYPE_DIMENSION) {
+ width = sw.getDimensionPixelSize(
+ com.android.internal.R.styleable.AndroidManifestLayout_defaultWidth,
+ -1);
+ }
+ final int heightType = sw.getType(
+ com.android.internal.R.styleable.AndroidManifestLayout_defaultHeight);
+ if (heightType == TypedValue.TYPE_FRACTION) {
+ heightFraction = sw.getFraction(
+ com.android.internal.R.styleable.AndroidManifestLayout_defaultHeight,
+ 1, 1, -1);
+ } else if (heightType == TypedValue.TYPE_DIMENSION) {
+ height = sw.getDimensionPixelSize(
+ com.android.internal.R.styleable.AndroidManifestLayout_defaultHeight,
+ -1);
+ }
+ int gravity = sw.getInt(
+ com.android.internal.R.styleable.AndroidManifestLayout_gravity,
+ Gravity.CENTER);
+ int minWidth = sw.getDimensionPixelSize(
+ com.android.internal.R.styleable.AndroidManifestLayout_minWidth,
+ -1);
+ int minHeight = sw.getDimensionPixelSize(
+ com.android.internal.R.styleable.AndroidManifestLayout_minHeight,
+ -1);
+ sw.recycle();
+ a.info.windowLayout = new ActivityInfo.WindowLayout(width, widthFraction,
+ height, heightFraction, gravity, minWidth, minHeight);
+ }
+
+ /**
+ * Resolves values in {@link ActivityInfo.WindowLayout}.
+ *
+ * <p>{@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in
+ * Android R and some variants of pre-R.
+ */
+ private void resolveWindowLayout(Activity activity) {
+ // There isn't a metadata for us to fall back. Whatever is in layout is correct.
+ if (activity.metaData == null
+ || !activity.metaData.containsKey(METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
+ return;
+ }
+
+ final ActivityInfo aInfo = activity.info;
+ // Layout already specifies a value. We should just use that one.
+ if (aInfo.windowLayout != null && aInfo.windowLayout.windowLayoutAffinity != null) {
+ return;
+ }
+
+ String windowLayoutAffinity = activity.metaData.getString(
+ METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
+ if (aInfo.windowLayout == null) {
+ aInfo.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
+ -1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
+ Gravity.NO_GRAVITY, -1 /* minWidth */, -1 /* minHeight */);
+ }
+ aInfo.windowLayout.windowLayoutAffinity = windowLayoutAffinity;
+ }
+
+ private Activity parseActivityAlias(Package owner, Resources res,
+ XmlResourceParser parser, int flags, String[] outError,
+ CachedComponentArgs cachedArgs)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias);
+
+ String targetActivity = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity,
+ Configuration.NATIVE_CONFIG_VERSION);
+ if (targetActivity == null) {
+ outError[0] = "<activity-alias> does not specify android:targetActivity";
+ sa.recycle();
+ return null;
+ }
+
+ targetActivity = buildClassName(owner.applicationInfo.packageName,
+ targetActivity, outError);
+ if (targetActivity == null) {
+ sa.recycle();
+ return null;
+ }
+
+ if (cachedArgs.mActivityAliasArgs == null) {
+ cachedArgs.mActivityAliasArgs = new ParseComponentArgs(owner, outError,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_name,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_label,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_logo,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_banner,
+ mSeparateProcesses,
+ 0,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_description,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled);
+ cachedArgs.mActivityAliasArgs.tag = "<activity-alias>";
+ }
+
+ cachedArgs.mActivityAliasArgs.sa = sa;
+ cachedArgs.mActivityAliasArgs.flags = flags;
+
+ Activity target = null;
+
+ final int NA = owner.activities.size();
+ for (int i=0; i<NA; i++) {
+ Activity t = owner.activities.get(i);
+ if (targetActivity.equals(t.info.name)) {
+ target = t;
+ break;
+ }
+ }
+
+ if (target == null) {
+ outError[0] = "<activity-alias> target activity " + targetActivity
+ + " not found in manifest";
+ sa.recycle();
+ return null;
+ }
+
+ ActivityInfo info = new ActivityInfo();
+ info.targetActivity = targetActivity;
+ info.configChanges = target.info.configChanges;
+ info.flags = target.info.flags;
+ info.privateFlags = target.info.privateFlags;
+ info.icon = target.info.icon;
+ info.logo = target.info.logo;
+ info.banner = target.info.banner;
+ info.labelRes = target.info.labelRes;
+ info.nonLocalizedLabel = target.info.nonLocalizedLabel;
+ info.launchMode = target.info.launchMode;
+ info.lockTaskLaunchMode = target.info.lockTaskLaunchMode;
+ info.processName = target.info.processName;
+ if (info.descriptionRes == 0) {
+ info.descriptionRes = target.info.descriptionRes;
+ }
+ info.screenOrientation = target.info.screenOrientation;
+ info.taskAffinity = target.info.taskAffinity;
+ info.theme = target.info.theme;
+ info.softInputMode = target.info.softInputMode;
+ info.uiOptions = target.info.uiOptions;
+ info.parentActivityName = target.info.parentActivityName;
+ info.maxRecents = target.info.maxRecents;
+ info.windowLayout = target.info.windowLayout;
+ info.resizeMode = target.info.resizeMode;
+ info.setMaxAspectRatio(target.info.getMaxAspectRatio());
+ info.setMinAspectRatio(target.info.getManifestMinAspectRatio());
+ info.supportsSizeChanges = target.info.supportsSizeChanges;
+ info.requestedVrComponent = target.info.requestedVrComponent;
+
+ info.directBootAware = target.info.directBootAware;
+
+ Activity a = new Activity(cachedArgs.mActivityAliasArgs, info);
+ if (outError[0] != null) {
+ sa.recycle();
+ return null;
+ }
+
+ final boolean setExported = sa.hasValue(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_exported);
+ if (setExported) {
+ a.info.exported = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_exported, false);
+ }
+
+ String str;
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_permission, 0);
+ if (str != null) {
+ a.info.permission = str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ String parentName = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_parentActivityName,
+ Configuration.NATIVE_CONFIG_VERSION);
+ if (parentName != null) {
+ String parentClassName = buildClassName(a.info.packageName, parentName, outError);
+ if (outError[0] == null) {
+ a.info.parentActivityName = parentClassName;
+ } else {
+ Log.e(TAG, "Activity alias " + a.info.name +
+ " specified invalid parentActivityName " + parentName);
+ outError[0] = null;
+ }
+ }
+
+ // TODO add visibleToInstantApps attribute to activity alias
+ final boolean visibleToEphemeral =
+ ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0);
+
+ sa.recycle();
+
+ if (outError[0] != null) {
+ return null;
+ }
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getName().equals("intent-filter")) {
+ ActivityIntentInfo intent = new ActivityIntentInfo(a);
+ if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/,
+ intent, outError)) {
+ return null;
+ }
+ if (intent.countActions() == 0) {
+ Slog.w(TAG, "No actions in intent filter at "
+ + 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
+ final int visibility = visibleToEphemeral
+ ? IntentFilter.VISIBILITY_EXPLICIT
+ : isImplicitlyExposedIntent(intent)
+ ? IntentFilter.VISIBILITY_IMPLICIT
+ : IntentFilter.VISIBILITY_NONE;
+ intent.setVisibilityToInstantApp(visibility);
+ if (intent.isVisibleToInstantApp()) {
+ a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ }
+ if (intent.isImplicitlyVisibleToInstantApp()) {
+ a.info.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
+ }
+ } else if (parser.getName().equals("meta-data")) {
+ if ((a.metaData=parseMetaData(res, parser, a.metaData,
+ outError)) == null) {
+ return null;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <activity-alias>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under <activity-alias>: " + parser.getName();
+ return null;
+ }
+ }
+ }
+
+ if (!setExported) {
+ a.info.exported = a.intents.size() > 0;
+ }
+
+ return a;
+ }
+
+ private Provider parseProvider(Package owner, Resources res,
+ XmlResourceParser parser, int flags, String[] outError,
+ CachedComponentArgs cachedArgs)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestProvider);
+
+ if (cachedArgs.mProviderArgs == null) {
+ cachedArgs.mProviderArgs = new ParseComponentArgs(owner, outError,
+ com.android.internal.R.styleable.AndroidManifestProvider_name,
+ com.android.internal.R.styleable.AndroidManifestProvider_label,
+ com.android.internal.R.styleable.AndroidManifestProvider_icon,
+ com.android.internal.R.styleable.AndroidManifestProvider_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestProvider_logo,
+ com.android.internal.R.styleable.AndroidManifestProvider_banner,
+ mSeparateProcesses,
+ com.android.internal.R.styleable.AndroidManifestProvider_process,
+ com.android.internal.R.styleable.AndroidManifestProvider_description,
+ com.android.internal.R.styleable.AndroidManifestProvider_enabled);
+ cachedArgs.mProviderArgs.tag = "<provider>";
+ }
+
+ cachedArgs.mProviderArgs.sa = sa;
+ cachedArgs.mProviderArgs.flags = flags;
+
+ Provider p = new Provider(cachedArgs.mProviderArgs, new ProviderInfo());
+ if (outError[0] != null) {
+ sa.recycle();
+ return null;
+ }
+
+ boolean providerExportedDefault = false;
+
+ if (owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // For compatibility, applications targeting API level 16 or lower
+ // should have their content providers exported by default, unless they
+ // specify otherwise.
+ providerExportedDefault = true;
+ }
+
+ p.info.exported = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_exported,
+ providerExportedDefault);
+
+ String cpname = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_authorities, 0);
+
+ p.info.isSyncable = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_syncable,
+ false);
+
+ String permission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_permission, 0);
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_readPermission, 0);
+ if (str == null) {
+ str = permission;
+ }
+ if (str == null) {
+ p.info.readPermission = owner.applicationInfo.permission;
+ } else {
+ p.info.readPermission =
+ str.length() > 0 ? str.toString().intern() : null;
+ }
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_writePermission, 0);
+ if (str == null) {
+ str = permission;
+ }
+ if (str == null) {
+ p.info.writePermission = owner.applicationInfo.permission;
+ } else {
+ p.info.writePermission =
+ str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ p.info.grantUriPermissions = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_grantUriPermissions,
+ false);
+
+ p.info.forceUriPermissions = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_forceUriPermissions,
+ false);
+
+ p.info.multiprocess = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_multiprocess,
+ false);
+
+ p.info.initOrder = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
+ 0);
+
+ p.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0);
+
+ p.info.flags = 0;
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
+ false)) {
+ p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
+ }
+
+ p.info.directBootAware = sa.getBoolean(
+ R.styleable.AndroidManifestProvider_directBootAware,
+ false);
+ if (p.info.directBootAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
+ }
+
+ final boolean visibleToEphemeral =
+ sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
+ if (visibleToEphemeral) {
+ p.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ owner.visibleToInstantApps = true;
+ }
+
+ sa.recycle();
+
+ if ((owner.applicationInfo.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
+ != 0) {
+ // A heavy-weight application can not have providers in its main process
+ // We can do direct compare because we intern all strings.
+ if (p.info.processName == owner.packageName) {
+ outError[0] = "Heavy-weight applications can not have providers in main process";
+ return null;
+ }
+ }
+
+ if (cpname == null) {
+ outError[0] = "<provider> does not include authorities attribute";
+ return null;
+ }
+ if (cpname.length() <= 0) {
+ outError[0] = "<provider> has empty authorities attribute";
+ return null;
+ }
+ p.info.authority = cpname.intern();
+
+ if (!parseProviderTags(
+ res, parser, visibleToEphemeral, p, outError)) {
+ return null;
+ }
+
+ return p;
+ }
+
+ private boolean parseProviderTags(Resources res, XmlResourceParser parser,
+ boolean visibleToEphemeral, Provider outInfo, String[] outError)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getName().equals("intent-filter")) {
+ ProviderIntentInfo intent = new ProviderIntentInfo(outInfo);
+ if (!parseIntent(res, parser, true /*allowGlobs*/, false /*allowAutoVerify*/,
+ intent, outError)) {
+ return false;
+ }
+ if (visibleToEphemeral) {
+ 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")) {
+ if ((outInfo.metaData=parseMetaData(res, parser,
+ outInfo.metaData, outError)) == null) {
+ return false;
+ }
+
+ } else if (parser.getName().equals("grant-uri-permission")) {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission);
+
+ PatternMatcher pa = null;
+
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_path, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ sa.recycle();
+
+ if (pa != null) {
+ if (outInfo.info.uriPermissionPatterns == null) {
+ outInfo.info.uriPermissionPatterns = new PatternMatcher[1];
+ outInfo.info.uriPermissionPatterns[0] = pa;
+ } else {
+ final int N = outInfo.info.uriPermissionPatterns.length;
+ PatternMatcher[] newp = new PatternMatcher[N+1];
+ System.arraycopy(outInfo.info.uriPermissionPatterns, 0, newp, 0, N);
+ newp[N] = pa;
+ outInfo.info.uriPermissionPatterns = newp;
+ }
+ outInfo.info.grantUriPermissions = true;
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <path-permission>: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>";
+ return false;
+ }
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (parser.getName().equals("path-permission")) {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestPathPermission);
+
+ PathPermission pa = null;
+
+ String permission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_permission, 0);
+ String readPermission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission, 0);
+ if (readPermission == null) {
+ readPermission = permission;
+ }
+ String writePermission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission, 0);
+ if (writePermission == null) {
+ writePermission = permission;
+ }
+
+ boolean havePerm = false;
+ if (readPermission != null) {
+ readPermission = readPermission.intern();
+ havePerm = true;
+ }
+ if (writePermission != null) {
+ writePermission = writePermission.intern();
+ havePerm = true;
+ }
+
+ if (!havePerm) {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "No readPermission or writePermssion for <path-permission>: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "No readPermission or writePermssion for <path-permission>";
+ return false;
+ }
+ }
+
+ String path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_path, 0);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_LITERAL, readPermission, writePermission);
+ }
+
+ path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix, 0);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_PREFIX, readPermission, writePermission);
+ }
+
+ path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern, 0);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission);
+ }
+
+ path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathAdvancedPattern, 0);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_ADVANCED_GLOB, readPermission, writePermission);
+ }
+
+ sa.recycle();
+
+ if (pa != null) {
+ if (outInfo.info.pathPermissions == null) {
+ outInfo.info.pathPermissions = new PathPermission[1];
+ outInfo.info.pathPermissions[0] = pa;
+ } else {
+ final int N = outInfo.info.pathPermissions.length;
+ PathPermission[] newp = new PathPermission[N+1];
+ System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N);
+ newp[N] = pa;
+ outInfo.info.pathPermissions = newp;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>";
+ return false;
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <provider>: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under <provider>: " + parser.getName();
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private Service parseService(Package owner, Resources res,
+ XmlResourceParser parser, int flags, String[] outError,
+ CachedComponentArgs cachedArgs)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestService);
+
+ if (cachedArgs.mServiceArgs == null) {
+ cachedArgs.mServiceArgs = new ParseComponentArgs(owner, outError,
+ com.android.internal.R.styleable.AndroidManifestService_name,
+ com.android.internal.R.styleable.AndroidManifestService_label,
+ com.android.internal.R.styleable.AndroidManifestService_icon,
+ com.android.internal.R.styleable.AndroidManifestService_roundIcon,
+ com.android.internal.R.styleable.AndroidManifestService_logo,
+ com.android.internal.R.styleable.AndroidManifestService_banner,
+ mSeparateProcesses,
+ com.android.internal.R.styleable.AndroidManifestService_process,
+ com.android.internal.R.styleable.AndroidManifestService_description,
+ com.android.internal.R.styleable.AndroidManifestService_enabled);
+ cachedArgs.mServiceArgs.tag = "<service>";
+ }
+
+ cachedArgs.mServiceArgs.sa = sa;
+ cachedArgs.mServiceArgs.flags = flags;
+
+ Service s = new Service(cachedArgs.mServiceArgs, new ServiceInfo());
+ if (outError[0] != null) {
+ sa.recycle();
+ return null;
+ }
+
+ boolean setExported = sa.hasValue(
+ com.android.internal.R.styleable.AndroidManifestService_exported);
+ if (setExported) {
+ s.info.exported = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_exported, false);
+ }
+
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestService_permission, 0);
+ if (str == null) {
+ s.info.permission = owner.applicationInfo.permission;
+ } else {
+ s.info.permission = str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ s.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0);
+
+ s.info.mForegroundServiceType = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestService_foregroundServiceType,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+
+ s.info.flags = 0;
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_STOP_WITH_TASK;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_isolatedProcess,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_ISOLATED_PROCESS;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_externalService,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_EXTERNAL_SERVICE;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_useAppZygote,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_USE_APP_ZYGOTE;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_singleUser,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
+ }
+
+ s.info.directBootAware = sa.getBoolean(
+ R.styleable.AndroidManifestService_directBootAware,
+ false);
+ if (s.info.directBootAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
+ }
+
+ boolean visibleToEphemeral =
+ sa.getBoolean(R.styleable.AndroidManifestService_visibleToInstantApps, false);
+ if (visibleToEphemeral) {
+ s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ owner.visibleToInstantApps = true;
+ }
+
+ sa.recycle();
+
+ if ((owner.applicationInfo.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
+ != 0) {
+ // A heavy-weight application can not have services in its main process
+ // We can do direct compare because we intern all strings.
+ if (s.info.processName == owner.packageName) {
+ outError[0] = "Heavy-weight applications can not have services in main process";
+ return null;
+ }
+ }
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getName().equals("intent-filter")) {
+ ServiceIntentInfo intent = new ServiceIntentInfo(s);
+ if (!parseIntent(res, parser, true /*allowGlobs*/, false /*allowAutoVerify*/,
+ intent, outError)) {
+ return null;
+ }
+ if (visibleToEphemeral) {
+ 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;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <service>: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under <service>: " + parser.getName();
+ return null;
+ }
+ }
+ }
+
+ if (!setExported) {
+ s.info.exported = s.intents.size() > 0;
+ }
+
+ return s;
+ }
+
+ private boolean isImplicitlyExposedIntent(IntentInfo intent) {
+ return intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+ || intent.hasAction(Intent.ACTION_SEND)
+ || intent.hasAction(Intent.ACTION_SENDTO)
+ || intent.hasAction(Intent.ACTION_SEND_MULTIPLE);
+ }
+
+ private boolean parseAllMetaData(Resources res, XmlResourceParser parser, String tag,
+ Component<?> outInfo, String[] outError) throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getName().equals("meta-data")) {
+ if ((outInfo.metaData=parseMetaData(res, parser,
+ outInfo.metaData, outError)) == null) {
+ return false;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under " + tag + ": "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under " + tag + ": " + parser.getName();
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private Bundle parseMetaData(Resources res,
+ XmlResourceParser parser, Bundle data, String[] outError)
+ throws XmlPullParserException, IOException {
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestMetaData);
+
+ if (data == null) {
+ data = new Bundle();
+ }
+
+ String name = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestMetaData_name, 0);
+ if (name == null) {
+ outError[0] = "<meta-data> requires an android:name attribute";
+ sa.recycle();
+ return null;
+ }
+
+ name = name.intern();
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestMetaData_resource);
+ if (v != null && v.resourceId != 0) {
+ //Slog.i(TAG, "Meta data ref " + name + ": " + v);
+ data.putInt(name, v.resourceId);
+ } else {
+ v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestMetaData_value);
+ //Slog.i(TAG, "Meta data " + name + ": " + v);
+ if (v != null) {
+ if (v.type == TypedValue.TYPE_STRING) {
+ CharSequence cs = v.coerceToString();
+ data.putString(name, cs != null ? cs.toString() : null);
+ } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+ data.putBoolean(name, v.data != 0);
+ } else if (v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT) {
+ data.putInt(name, v.data);
+ } else if (v.type == TypedValue.TYPE_FLOAT) {
+ data.putFloat(name, v.getFloat());
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "<meta-data> only supports string, integer, float, color, boolean, and resource reference types: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ } else {
+ outError[0] = "<meta-data> only supports string, integer, float, color, boolean, and resource reference types";
+ data = null;
+ }
+ }
+ } else {
+ outError[0] = "<meta-data> requires an android:value or android:resource attribute";
+ data = null;
+ }
+ }
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
+
+ return data;
+ }
+
+ private static VerifierInfo parseVerifier(AttributeSet attrs) {
+ String packageName = null;
+ String encodedPublicKey = null;
+
+ final int attrCount = attrs.getAttributeCount();
+ for (int i = 0; i < attrCount; i++) {
+ final int attrResId = attrs.getAttributeNameResource(i);
+ switch (attrResId) {
+ case com.android.internal.R.attr.name:
+ packageName = attrs.getAttributeValue(i);
+ break;
+
+ case com.android.internal.R.attr.publicKey:
+ encodedPublicKey = attrs.getAttributeValue(i);
+ break;
+ }
+ }
+
+ if (packageName == null || packageName.length() == 0) {
+ Slog.i(TAG, "verifier package name was null; skipping");
+ return null;
+ }
+
+ final PublicKey publicKey = parsePublicKey(encodedPublicKey);
+ if (publicKey == null) {
+ Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
+ return null;
+ }
+
+ return new VerifierInfo(packageName, publicKey);
+ }
+
+ public static final PublicKey parsePublicKey(final String encodedPublicKey) {
+ if (encodedPublicKey == null) {
+ Slog.w(TAG, "Could not parse null public key");
+ return null;
+ }
+
+ try {
+ return parsePublicKey(Base64.decode(encodedPublicKey, Base64.DEFAULT));
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Could not parse verifier public key; invalid Base64");
+ return null;
+ }
+ }
+
+ public static final PublicKey parsePublicKey(final byte[] publicKey) {
+ if (publicKey == null) {
+ Slog.w(TAG, "Could not parse null public key");
+ return null;
+ }
+
+ EncodedKeySpec keySpec;
+ try {
+ keySpec = new X509EncodedKeySpec(publicKey);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Could not parse verifier public key; invalid Base64");
+ return null;
+ }
+
+ /* First try the key as an RSA key. */
+ try {
+ final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return keyFactory.generatePublic(keySpec);
+ } catch (NoSuchAlgorithmException e) {
+ Slog.wtf(TAG, "Could not parse public key: RSA KeyFactory not included in build");
+ } catch (InvalidKeySpecException e) {
+ // Not a RSA public key.
+ }
+
+ /* Now try it as a ECDSA key. */
+ try {
+ final KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return keyFactory.generatePublic(keySpec);
+ } catch (NoSuchAlgorithmException e) {
+ Slog.wtf(TAG, "Could not parse public key: EC KeyFactory not included in build");
+ } catch (InvalidKeySpecException e) {
+ // Not a ECDSA public key.
+ }
+
+ /* Now try it as a DSA key. */
+ try {
+ final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
+ return keyFactory.generatePublic(keySpec);
+ } catch (NoSuchAlgorithmException e) {
+ Slog.wtf(TAG, "Could not parse public key: DSA KeyFactory not included in build");
+ } catch (InvalidKeySpecException e) {
+ // Not a DSA public key.
+ }
+
+ /* Not a supported key type */
+ return null;
+ }
+
+ public static final String ANDROID_RESOURCES
+ = "http://schemas.android.com/apk/res/android";
+
+ private boolean parseIntent(Resources res, XmlResourceParser parser, boolean allowGlobs,
+ boolean allowAutoVerify, IntentInfo outInfo, String[] outError)
+ throws XmlPullParserException, IOException {
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestIntentFilter);
+
+ int priority = sa.getInt(
+ 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) {
+ outInfo.nonLocalizedLabel = v.coerceToString();
+ }
+
+ int roundIconVal = sUseRoundIcon ? sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0) : 0;
+ if (roundIconVal != 0) {
+ outInfo.icon = roundIconVal;
+ } else {
+ outInfo.icon = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
+ }
+
+ outInfo.logo = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0);
+
+ outInfo.banner = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_banner, 0);
+
+ if (allowAutoVerify) {
+ outInfo.setAutoVerify(sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_autoVerify,
+ false));
+ }
+
+ sa.recycle();
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String nodeName = parser.getName();
+ if (nodeName.equals("action")) {
+ String value = parser.getAttributeValue(
+ ANDROID_RESOURCES, "name");
+ if (value == null || value == "") {
+ outError[0] = "No value supplied for <android:name>";
+ return false;
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ outInfo.addAction(value);
+ } else if (nodeName.equals("category")) {
+ String value = parser.getAttributeValue(
+ ANDROID_RESOURCES, "name");
+ if (value == null || value == "") {
+ outError[0] = "No value supplied for <android:name>";
+ return false;
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ outInfo.addCategory(value);
+
+ } else if (nodeName.equals("data")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestData);
+
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_mimeType, 0);
+ if (str != null) {
+ try {
+ outInfo.addDataType(str);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ outError[0] = e.toString();
+ sa.recycle();
+ return false;
+ }
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_scheme, 0);
+ if (str != null) {
+ outInfo.addDataScheme(str);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_ssp, 0);
+ if (str != null) {
+ outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_sspPrefix, 0);
+ if (str != null) {
+ outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_sspPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ outError[0] = "sspPattern not allowed here; ssp must be literal";
+ return false;
+ }
+ outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ String host = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_host, 0);
+ String port = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_port, 0);
+ if (host != null) {
+ outInfo.addDataAuthority(host, port);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_path, 0);
+ if (str != null) {
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_pathPrefix, 0);
+ if (str != null) {
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_pathPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ outError[0] = "pathPattern not allowed here; path must be literal";
+ return false;
+ }
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ outError[0] = "pathAdvancedPattern not allowed here; path must be literal";
+ return false;
+ }
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ }
+
+ sa.recycle();
+ XmlUtils.skipCurrentTag(parser);
+ } else if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <intent-filter>: "
+ + parser.getName() + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ outError[0] = "Bad element under <intent-filter>: " + parser.getName();
+ return false;
+ }
+ }
+
+ outInfo.hasDefault = outInfo.hasCategory(Intent.CATEGORY_DEFAULT);
+
+ if (DEBUG_PARSER) {
+ final StringBuilder cats = new StringBuilder("Intent d=");
+ cats.append(outInfo.hasDefault);
+ cats.append(", cat=");
+
+ final Iterator<String> it = outInfo.categoriesIterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ cats.append(' ');
+ cats.append(it.next());
+ }
+ }
+ Slog.d(TAG, cats.toString());
+ }
+
+ return true;
+ }
+
+ /**
+ * A container for signing-related data of an application package.
+ * @hide
+ */
+ public static final class SigningDetails implements Parcelable {
+
+ @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN,
+ SigningDetails.SignatureSchemeVersion.JAR,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4})
+ public @interface SignatureSchemeVersion {
+ int UNKNOWN = 0;
+ int JAR = 1;
+ int SIGNING_BLOCK_V2 = 2;
+ int SIGNING_BLOCK_V3 = 3;
+ int SIGNING_BLOCK_V4 = 4;
+ }
+
+ @Nullable
+ @UnsupportedAppUsage
+ public final Signature[] signatures;
+ @SignatureSchemeVersion
+ public final int signatureSchemeVersion;
+ @Nullable
+ public final ArraySet<PublicKey> publicKeys;
+
+ /**
+ * 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;
+
+ /** allow pkg to continue to have auth access gated by this cert */
+ int AUTH = 16;
+ }
+
+ /** A representation of unknown signing details. Use instead of null. */
+ public static final SigningDetails UNKNOWN =
+ new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null);
+
+ @VisibleForTesting
+ public SigningDetails(Signature[] signatures,
+ @SignatureSchemeVersion int signatureSchemeVersion,
+ ArraySet<PublicKey> keys, Signature[] pastSigningCertificates) {
+ this.signatures = signatures;
+ this.signatureSchemeVersion = signatureSchemeVersion;
+ this.publicKeys = keys;
+ this.pastSigningCertificates = pastSigningCertificates;
+ }
+
+ public SigningDetails(Signature[] signatures,
+ @SignatureSchemeVersion int signatureSchemeVersion,
+ Signature[] pastSigningCertificates)
+ throws CertificateException {
+ this(signatures, signatureSchemeVersion, toSigningKeys(signatures),
+ pastSigningCertificates);
+ }
+
+ public SigningDetails(Signature[] signatures,
+ @SignatureSchemeVersion int signatureSchemeVersion)
+ throws CertificateException {
+ this(signatures, signatureSchemeVersion, null);
+ }
+
+ public SigningDetails(SigningDetails orig) {
+ if (orig != null) {
+ if (orig.signatures != null) {
+ this.signatures = orig.signatures.clone();
+ } else {
+ this.signatures = null;
+ }
+ this.signatureSchemeVersion = orig.signatureSchemeVersion;
+ this.publicKeys = new ArraySet<>(orig.publicKeys);
+ if (orig.pastSigningCertificates != null) {
+ this.pastSigningCertificates = orig.pastSigningCertificates.clone();
+ } else {
+ this.pastSigningCertificates = null;
+ }
+ } else {
+ this.signatures = null;
+ this.signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+ this.publicKeys = null;
+ this.pastSigningCertificates = null;
+ }
+ }
+
+ /**
+ * Merges the signing lineage of this instance with the lineage in the provided {@code
+ * otherSigningDetails} when one has the same or an ancestor signer of the other.
+ *
+ * <p>Merging two signing lineages will result in a new {@code SigningDetails} instance
+ * containing the longest common lineage with the most restrictive capabilities. If the two
+ * lineages contain the same signers with the same capabilities then the instance on which
+ * this was invoked is returned without any changes. Similarly if neither instance has a
+ * lineage, or if neither has the same or an ancestor signer then this instance is returned.
+ *
+ * Following are some example results of this method for lineages with signers A, B, C, D:
+ * - lineage B merged with lineage A -> B returns lineage A -> B.
+ * - lineage A -> B merged with lineage B -> C returns lineage A -> B -> C
+ * - lineage A -> B with the {@code PERMISSION} capability revoked for A merged with
+ * lineage A -> B with the {@code SHARED_USER_ID} capability revoked for A returns
+ * lineage A -> B with both capabilities revoked for A.
+ * - lineage A -> B -> C merged with lineage A -> B -> D would return the original lineage
+ * A -> B -> C since the current signer of both instances is not the same or in the
+ * lineage of the other.
+ */
+ public SigningDetails mergeLineageWith(SigningDetails otherSigningDetails) {
+ if (!hasPastSigningCertificates()) {
+ return otherSigningDetails.hasPastSigningCertificates()
+ && otherSigningDetails.hasAncestorOrSelf(this) ? otherSigningDetails : this;
+ }
+ if (!otherSigningDetails.hasPastSigningCertificates()) {
+ return this;
+ }
+ // Use the utility method to determine which SigningDetails instance is the descendant
+ // and to confirm that the signing lineage does not diverge.
+ SigningDetails descendantSigningDetails = getDescendantOrSelf(otherSigningDetails);
+ if (descendantSigningDetails == null) {
+ return this;
+ }
+ return descendantSigningDetails == this ? mergeLineageWithAncestorOrSelf(
+ otherSigningDetails) : otherSigningDetails.mergeLineageWithAncestorOrSelf(this);
+ }
+
+ /**
+ * Merges the signing lineage of this instance with the lineage of the ancestor (or same)
+ * signer in the provided {@code otherSigningDetails}.
+ */
+ private SigningDetails mergeLineageWithAncestorOrSelf(SigningDetails otherSigningDetails) {
+ // This method should only be called with instances that contain lineages.
+ int index = pastSigningCertificates.length - 1;
+ int otherIndex = otherSigningDetails.pastSigningCertificates.length - 1;
+ if (index < 0 || otherIndex < 0) {
+ return this;
+ }
+
+ List<Signature> mergedSignatures = new ArrayList<>();
+ boolean capabilitiesModified = false;
+ // If this is a descendant lineage then add all of the descendant signer(s) to the
+ // merged lineage until the ancestor signer is reached.
+ while (index >= 0 && !pastSigningCertificates[index].equals(
+ otherSigningDetails.pastSigningCertificates[otherIndex])) {
+ mergedSignatures.add(new Signature(pastSigningCertificates[index--]));
+ }
+ // If the signing lineage was exhausted then the provided ancestor is not actually an
+ // ancestor of this lineage.
+ if (index < 0) {
+ return this;
+ }
+
+ do {
+ // Add the common signer to the merged lineage with the most restrictive
+ // capabilities of the two lineages.
+ Signature signature = pastSigningCertificates[index--];
+ Signature ancestorSignature =
+ otherSigningDetails.pastSigningCertificates[otherIndex--];
+ Signature mergedSignature = new Signature(signature);
+ int mergedCapabilities = signature.getFlags() & ancestorSignature.getFlags();
+ if (signature.getFlags() != mergedCapabilities) {
+ capabilitiesModified = true;
+ mergedSignature.setFlags(mergedCapabilities);
+ }
+ mergedSignatures.add(mergedSignature);
+ } while (index >= 0 && otherIndex >= 0 && pastSigningCertificates[index].equals(
+ otherSigningDetails.pastSigningCertificates[otherIndex]));
+
+ // If both lineages still have elements then their lineages have diverged; since this is
+ // not supported return the invoking instance.
+ if (index >= 0 && otherIndex >= 0) {
+ return this;
+ }
+
+ // Add any remaining elements from either lineage that is not yet exhausted to the
+ // the merged lineage.
+ while (otherIndex >= 0) {
+ mergedSignatures.add(new Signature(
+ otherSigningDetails.pastSigningCertificates[otherIndex--]));
+ }
+ while (index >= 0) {
+ mergedSignatures.add(new Signature(pastSigningCertificates[index--]));
+ }
+
+ // if this lineage already contains all the elements in the ancestor and none of the
+ // capabilities were changed then just return this instance.
+ if (mergedSignatures.size() == pastSigningCertificates.length
+ && !capabilitiesModified) {
+ return this;
+ }
+ // Since the signatures were added to the merged lineage from newest to oldest reverse
+ // the list to ensure the oldest signer is at index 0.
+ Collections.reverse(mergedSignatures);
+ try {
+ return new SigningDetails(new Signature[]{new Signature(signatures[0])},
+ signatureSchemeVersion, mergedSignatures.toArray(new Signature[0]));
+ } catch (CertificateException e) {
+ Slog.e(TAG, "Caught an exception creating the merged lineage: ", e);
+ return this;
+ }
+ }
+
+ /**
+ * Returns whether this and the provided {@code otherSigningDetails} share a common
+ * ancestor.
+ *
+ * <p>The two SigningDetails have a common ancestor if any of the following conditions are
+ * met:
+ * - If neither has a lineage and their current signer(s) are equal.
+ * - If only one has a lineage and the signer of the other is the same or in the lineage.
+ * - If both have a lineage and their current signers are the same or one is in the lineage
+ * of the other, and their lineages do not diverge to different signers.
+ */
+ public boolean hasCommonAncestor(SigningDetails otherSigningDetails) {
+ if (!hasPastSigningCertificates()) {
+ // If this instance does not have a lineage then it must either be in the ancestry
+ // of or the same signer of the otherSigningDetails.
+ return otherSigningDetails.hasAncestorOrSelf(this);
+ }
+ if (!otherSigningDetails.hasPastSigningCertificates()) {
+ return hasAncestorOrSelf(otherSigningDetails);
+ }
+ // If both have a lineage then use getDescendantOrSelf to obtain the descendant signing
+ // details; a null return from that method indicates there is no common lineage between
+ // the two or that they diverge at a point in the lineage.
+ return getDescendantOrSelf(otherSigningDetails) != null;
+ }
+
+ /**
+ * Returns whether this instance is currently signed, or has ever been signed, with a
+ * signing certificate from the provided {@link Set} of {@code certDigests}.
+ *
+ * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+ * of each trusted certificate with the digest characters in upper case. If this instance
+ * has multiple signers then all signers must be in the provided {@code Set}. If this
+ * instance has a signing lineage then this method will return true if any of the previous
+ * signers in the lineage match one of the entries in the {@code Set}.
+ */
+ public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
+ if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+ return false;
+ }
+ // If an app is signed by multiple signers then all of the signers must be in the Set.
+ if (signatures.length > 1) {
+ // If the Set has less elements than the number of signatures then immediately
+ // return false as there's no way to satisfy the requirement of all signatures being
+ // in the Set.
+ if (certDigests.size() < signatures.length) {
+ return false;
+ }
+ for (Signature signature : signatures) {
+ String signatureDigest = PackageUtils.computeSha256Digest(
+ signature.toByteArray());
+ if (!certDigests.contains(signatureDigest)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
+ if (certDigests.contains(signatureDigest)) {
+ return true;
+ }
+ if (hasPastSigningCertificates()) {
+ // The last element in the pastSigningCertificates array is the current signer;
+ // since that was verified above just check all the signers in the lineage.
+ for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+ signatureDigest = PackageUtils.computeSha256Digest(
+ pastSigningCertificates[i].toByteArray());
+ if (certDigests.contains(signatureDigest)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the SigningDetails with a descendant (or same) signer after verifying the
+ * descendant has the same, a superset, or a subset of the lineage of the ancestor.
+ *
+ * <p>If this instance and the provided {@code otherSigningDetails} do not share an
+ * ancestry, or if their lineages diverge then null is returned to indicate there is no
+ * valid descendant SigningDetails.
+ */
+ private SigningDetails getDescendantOrSelf(SigningDetails otherSigningDetails) {
+ SigningDetails descendantSigningDetails;
+ SigningDetails ancestorSigningDetails;
+ if (hasAncestorOrSelf(otherSigningDetails)) {
+ // If the otherSigningDetails has the same signer or a signer in the lineage of this
+ // instance then treat this instance as the descendant.
+ descendantSigningDetails = this;
+ ancestorSigningDetails = otherSigningDetails;
+ } else if (otherSigningDetails.hasAncestor(this)) {
+ // The above check confirmed that the two instances do not have the same signer and
+ // the signer of otherSigningDetails is not in this instance's lineage; if this
+ // signer is in the otherSigningDetails lineage then treat this as the ancestor.
+ descendantSigningDetails = otherSigningDetails;
+ ancestorSigningDetails = this;
+ } else {
+ // The signers are not the same and neither has the current signer of the other in
+ // its lineage; return null to indicate there is no descendant signer.
+ return null;
+ }
+ // Once the descent (or same) signer is identified iterate through the ancestry until
+ // the current signer of the ancestor is found.
+ int descendantIndex = descendantSigningDetails.pastSigningCertificates.length - 1;
+ int ancestorIndex = ancestorSigningDetails.pastSigningCertificates.length - 1;
+ while (descendantIndex >= 0
+ && !descendantSigningDetails.pastSigningCertificates[descendantIndex].equals(
+ ancestorSigningDetails.pastSigningCertificates[ancestorIndex])) {
+ descendantIndex--;
+ }
+ // Since the ancestry was verified above the descendant lineage should never be
+ // exhausted, but if for some reason the ancestor signer is not found then return null.
+ if (descendantIndex < 0) {
+ return null;
+ }
+ // Once the common ancestor (or same) signer is found iterate over the lineage of both
+ // to ensure that they are either the same or one is a subset of the other.
+ do {
+ descendantIndex--;
+ ancestorIndex--;
+ } while (descendantIndex >= 0 && ancestorIndex >= 0
+ && descendantSigningDetails.pastSigningCertificates[descendantIndex].equals(
+ ancestorSigningDetails.pastSigningCertificates[ancestorIndex]));
+
+ // If both lineages still have elements then they diverge and cannot be considered a
+ // valid common lineage.
+ if (descendantIndex >= 0 && ancestorIndex >= 0) {
+ return null;
+ }
+ // Since one or both of the lineages was exhausted they are either the same or one is a
+ // subset of the other; return the valid descendant.
+ return descendantSigningDetails;
+ }
+
+ /** Returns true if the signing details have one or more signatures. */
+ public boolean hasSignatures() {
+ return signatures != null && signatures.length > 0;
+ }
+
+ /** Returns true if the signing details have past signing certificates. */
+ public boolean hasPastSigningCertificates() {
+ 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[0])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether this {@code SigningDetails} has a signer in common with the provided
+ * {@code otherDetails} with the specified {@code flags} capabilities provided by this
+ * signer.
+ *
+ * <p>Note this method allows for the signing lineage to diverge, so this should only be
+ * used for instances where the only requirement is a common signer in the lineage with
+ * the specified capabilities. If the current signer of this instance is an ancestor of
+ * {@code otherDetails} then {@code true} is immediately returned since the current signer
+ * has all capabilities granted.
+ */
+ public boolean hasCommonSignerWithCapability(SigningDetails otherDetails,
+ @CertCapabilities int flags) {
+ if (this == UNKNOWN || otherDetails == UNKNOWN) {
+ return false;
+ }
+ // If either is signed with more than one signer then both must be signed by the same
+ // signers to consider the capabilities granted.
+ if (signatures.length > 1 || otherDetails.signatures.length > 1) {
+ return signaturesMatchExactly(otherDetails);
+ }
+ // The Signature class does not use the granted capabilities in the hashCode
+ // computation, so a Set can be used to check for a common signer.
+ Set<Signature> otherSignatures = new ArraySet<>();
+ if (otherDetails.hasPastSigningCertificates()) {
+ otherSignatures.addAll(Arrays.asList(otherDetails.pastSigningCertificates));
+ } else {
+ otherSignatures.addAll(Arrays.asList(otherDetails.signatures));
+ }
+ // If the current signer of this instance is an ancestor of the other than return true
+ // since all capabilities are granted to the current signer.
+ if (otherSignatures.contains(signatures[0])) {
+ return true;
+ }
+ if (hasPastSigningCertificates()) {
+ // Since the current signer was checked above and the last signature in the
+ // pastSigningCertificates is the current signer skip checking the last element.
+ for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+ if (otherSignatures.contains(pastSigningCertificates[i])) {
+ // If the caller specified multiple capabilities ensure all are set.
+ if ((pastSigningCertificates[i].getFlags() & flags) == flags) {
+ 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])
+ && pastSigningCertificates[i].getFlags() == 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 & pastSigningCertificates[i].getFlags()) == 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 = sha256String == null
+ ? null : HexEncoding.decode(sha256String, false /* allowSingleChar */);
+ 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 & pastSigningCertificates[i].getFlags()) == 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);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ boolean isUnknown = UNKNOWN == this;
+ dest.writeBoolean(isUnknown);
+ if (isUnknown) {
+ return;
+ }
+ dest.writeTypedArray(this.signatures, flags);
+ dest.writeInt(this.signatureSchemeVersion);
+ dest.writeArraySet(this.publicKeys);
+ dest.writeTypedArray(this.pastSigningCertificates, flags);
+ }
+
+ protected SigningDetails(Parcel in) {
+ final ClassLoader boot = Object.class.getClassLoader();
+ this.signatures = in.createTypedArray(Signature.CREATOR);
+ this.signatureSchemeVersion = in.readInt();
+ this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
+ this.pastSigningCertificates = in.createTypedArray(Signature.CREATOR);
+ }
+
+ public static final @android.annotation.NonNull Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() {
+ @Override
+ public SigningDetails createFromParcel(Parcel source) {
+ if (source.readBoolean()) {
+ return UNKNOWN;
+ }
+ return new SigningDetails(source);
+ }
+
+ @Override
+ public SigningDetails[] newArray(int size) {
+ return new SigningDetails[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SigningDetails)) return false;
+
+ SigningDetails that = (SigningDetails) o;
+
+ if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
+ if (!Signature.areExactMatch(signatures, that.signatures)) return false;
+ if (publicKeys != null) {
+ if (!publicKeys.equals((that.publicKeys))) {
+ return false;
+ }
+ } else if (that.publicKeys != null) {
+ return false;
+ }
+
+ // can't use Signature.areExactMatch() because order matters with the past signing certs
+ if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) {
+ return false;
+ }
+ // The capabilities for the past signing certs must match as well.
+ for (int i = 0; i < pastSigningCertificates.length; i++) {
+ if (pastSigningCertificates[i].getFlags()
+ != that.pastSigningCertificates[i].getFlags()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = +Arrays.hashCode(signatures);
+ result = 31 * result + signatureSchemeVersion;
+ result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0);
+ result = 31 * result + Arrays.hashCode(pastSigningCertificates);
+ return result;
+ }
+
+ /**
+ * Builder of {@code SigningDetails} instances.
+ */
+ public static class Builder {
+ private Signature[] mSignatures;
+ private int mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+ private Signature[] mPastSigningCertificates;
+
+ @UnsupportedAppUsage
+ public Builder() {
+ }
+
+ /** get signing certificates used to sign the current APK */
+ @UnsupportedAppUsage
+ public Builder setSignatures(Signature[] signatures) {
+ mSignatures = signatures;
+ return this;
+ }
+
+ /** set the signature scheme version used to sign the APK */
+ @UnsupportedAppUsage
+ public Builder setSignatureSchemeVersion(int signatureSchemeVersion) {
+ mSignatureSchemeVersion = signatureSchemeVersion;
+ return this;
+ }
+
+ /** set the signing certificates by which the APK proved it can be authenticated */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Builder setPastSigningCertificates(Signature[] pastSigningCertificates) {
+ mPastSigningCertificates = pastSigningCertificates;
+ return this;
+ }
+
+ private void checkInvariants() {
+ // must have signatures and scheme version set
+ if (mSignatures == null) {
+ throw new IllegalStateException("SigningDetails requires the current signing"
+ + " certificates.");
+ }
+ }
+ /** build a {@code SigningDetails} object */
+ @UnsupportedAppUsage
+ public SigningDetails build()
+ throws CertificateException {
+ checkInvariants();
+ return new SigningDetails(mSignatures, mSignatureSchemeVersion,
+ mPastSigningCertificates);
+ }
+ }
+ }
+
+ /**
+ * Representation of a full package parsed from APK files on disk. A package
+ * consists of a single base APK, and zero or more split APKs.
+ *
+ * Deprecated internally. Use AndroidPackage instead.
+ */
+ public final static class Package implements Parcelable {
+
+ @UnsupportedAppUsage
+ public String packageName;
+
+ // The package name declared in the manifest as the package can be
+ // renamed, for example static shared libs use synthetic package names.
+ public String manifestPackageName;
+
+ /** Names of any split APKs, ordered by parsed splitName */
+ public String[] splitNames;
+
+ // TODO: work towards making these paths invariant
+
+ public String volumeUuid;
+
+ /**
+ * Path where this package was found on disk. For monolithic packages
+ * this is path to single base APK file; for cluster packages this is
+ * path to the cluster directory.
+ */
+ public String codePath;
+
+ /** Path of base APK */
+ public String baseCodePath;
+ /** Paths of any split APKs, ordered by parsed splitName */
+ public String[] splitCodePaths;
+
+ /** Revision code of base APK */
+ public int baseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ public int[] splitRevisionCodes;
+
+ /** Flags of any split APKs; ordered by parsed splitName */
+ public int[] splitFlags;
+
+ /**
+ * Private flags of any split APKs; ordered by parsed splitName.
+ *
+ * {@hide}
+ */
+ public int[] splitPrivateFlags;
+
+ public boolean baseHardwareAccelerated;
+
+ // For now we only support one application per package.
+ @UnsupportedAppUsage
+ public ApplicationInfo applicationInfo = new ApplicationInfo();
+
+ @UnsupportedAppUsage
+ public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
+ @UnsupportedAppUsage
+ public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
+ @UnsupportedAppUsage
+ public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
+ @UnsupportedAppUsage
+ public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
+ @UnsupportedAppUsage
+ public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
+ @UnsupportedAppUsage
+ public final ArrayList<Service> services = new ArrayList<Service>(0);
+ @UnsupportedAppUsage
+ public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
+
+ @UnsupportedAppUsage
+ public final ArrayList<String> requestedPermissions = new ArrayList<String>();
+
+ /** Permissions requested but not in the manifest. */
+ public final ArrayList<String> implicitPermissions = new ArrayList<>();
+
+ @UnsupportedAppUsage
+ public ArrayList<String> protectedBroadcasts;
+
+ public Package parentPackage;
+ public ArrayList<Package> childPackages;
+
+ public String staticSharedLibName = null;
+ public long staticSharedLibVersion = 0;
+ public ArrayList<String> libraryNames = null;
+ @UnsupportedAppUsage
+ public ArrayList<String> usesLibraries = null;
+ public ArrayList<String> usesStaticLibraries = null;
+ public long[] usesStaticLibrariesVersions = null;
+ public String[][] usesStaticLibrariesCertDigests = null;
+ @UnsupportedAppUsage
+ public ArrayList<String> usesOptionalLibraries = null;
+ @UnsupportedAppUsage
+ public String[] usesLibraryFiles = null;
+ public ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
+
+ public ArrayList<ActivityIntentInfo> preferredActivityFilters = null;
+
+ public ArrayList<String> mOriginalPackages = null;
+ public String mRealPackage = null;
+ public ArrayList<String> mAdoptPermissions = null;
+
+ // We store the application meta-data independently to avoid multiple unwanted references
+ @UnsupportedAppUsage
+ public Bundle mAppMetaData = null;
+
+ // The version code declared for this package.
+ @UnsupportedAppUsage
+ public int mVersionCode;
+
+ // The major version code declared for this package.
+ public int mVersionCodeMajor;
+
+ // Return long containing mVersionCode and mVersionCodeMajor.
+ public long getLongVersionCode() {
+ return PackageInfo.composeLongVersionCode(mVersionCodeMajor, mVersionCode);
+ }
+
+ // The version name declared for this package.
+ @UnsupportedAppUsage
+ public String mVersionName;
+
+ // The shared user id that this package wants to use.
+ @UnsupportedAppUsage
+ public String mSharedUserId;
+
+ // The shared user label that this package wants to use.
+ @UnsupportedAppUsage
+ public int mSharedUserLabel;
+
+ // Signatures that were read from the package.
+ @UnsupportedAppUsage
+ @NonNull public SigningDetails mSigningDetails = SigningDetails.UNKNOWN;
+
+ // For use by package manager service for quick lookup of
+ // preferred up order.
+ @UnsupportedAppUsage
+ public int mPreferredOrder = 0;
+
+ // For use by package manager to keep track of when a package was last used.
+ public long[] mLastPackageUsageTimeInMills =
+ new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT];
+
+ // // User set enabled state.
+ // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ //
+ // // Whether the package has been stopped.
+ // public boolean mSetStopped = false;
+
+ // Additional data supplied by callers.
+ @UnsupportedAppUsage
+ public Object mExtras;
+
+ // Applications hardware preferences
+ @UnsupportedAppUsage
+ public ArrayList<ConfigurationInfo> configPreferences = null;
+
+ // Applications requested features
+ @UnsupportedAppUsage
+ public ArrayList<FeatureInfo> reqFeatures = null;
+
+ // Applications requested feature groups
+ public ArrayList<FeatureGroupInfo> featureGroups = null;
+
+ @UnsupportedAppUsage
+ public int installLocation;
+
+ public boolean coreApp;
+
+ /* An app that's required for all users and cannot be uninstalled for a user */
+ public boolean mRequiredForAllUsers;
+
+ /* The restricted account authenticator type that is used by this application */
+ public String mRestrictedAccountType;
+
+ /* The required account type without which this application will not function */
+ public String mRequiredAccountType;
+
+ public String mOverlayTarget;
+ public String mOverlayTargetName;
+ public String mOverlayCategory;
+ public int mOverlayPriority;
+ public boolean mOverlayIsStatic;
+
+ public int mCompileSdkVersion;
+ public String mCompileSdkVersionCodename;
+
+ /**
+ * Data used to feed the KeySetManagerService
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ArraySet<String> mUpgradeKeySets;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping;
+
+ /**
+ * The install time abi override for this package, if any.
+ *
+ * TODO: This seems like a horrible place to put the abiOverride because
+ * this isn't something the packageParser parsers. However, this fits in with
+ * the rest of the PackageManager where package scanning randomly pushes
+ * and prods fields out of {@code this.applicationInfo}.
+ */
+ public String cpuAbiOverride;
+ /**
+ * The install time abi override to choose 32bit abi's when multiple abi's
+ * are present. This is only meaningfull for multiarch applications.
+ * The use32bitAbi attribute is ignored if cpuAbiOverride is also set.
+ */
+ public boolean use32bitAbi;
+
+ public byte[] restrictUpdateHash;
+
+ /** Set if the app or any of its components are visible to instant applications. */
+ public boolean visibleToInstantApps;
+ /** Whether or not the package is a stub and must be replaced by the full version. */
+ public boolean isStub;
+
+ @UnsupportedAppUsage
+ public Package(String packageName) {
+ this.packageName = packageName;
+ this.manifestPackageName = packageName;
+ applicationInfo.packageName = packageName;
+ applicationInfo.uid = -1;
+ }
+
+ public void setApplicationVolumeUuid(String volumeUuid) {
+ final UUID storageUuid = StorageManager.convert(volumeUuid);
+ this.applicationInfo.volumeUuid = volumeUuid;
+ this.applicationInfo.storageUuid = storageUuid;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).applicationInfo.volumeUuid = volumeUuid;
+ childPackages.get(i).applicationInfo.storageUuid = storageUuid;
+ }
+ }
+ }
+
+ public void setApplicationInfoCodePath(String codePath) {
+ this.applicationInfo.setCodePath(codePath);
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).applicationInfo.setCodePath(codePath);
+ }
+ }
+ }
+
+ /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+ @Deprecated
+ public void setApplicationInfoResourcePath(String resourcePath) {
+ this.applicationInfo.setResourcePath(resourcePath);
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).applicationInfo.setResourcePath(resourcePath);
+ }
+ }
+ }
+
+ /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+ @Deprecated
+ public void setApplicationInfoBaseResourcePath(String resourcePath) {
+ this.applicationInfo.setBaseResourcePath(resourcePath);
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).applicationInfo.setBaseResourcePath(resourcePath);
+ }
+ }
+ }
+
+ public void setApplicationInfoBaseCodePath(String baseCodePath) {
+ this.applicationInfo.setBaseCodePath(baseCodePath);
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).applicationInfo.setBaseCodePath(baseCodePath);
+ }
+ }
+ }
+
+ public List<String> getChildPackageNames() {
+ if (childPackages == null) {
+ return null;
+ }
+ final int childCount = childPackages.size();
+ final List<String> childPackageNames = new ArrayList<>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ String childPackageName = childPackages.get(i).packageName;
+ childPackageNames.add(childPackageName);
+ }
+ return childPackageNames;
+ }
+
+ public boolean hasChildPackage(String packageName) {
+ final int childCount = (childPackages != null) ? childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ if (childPackages.get(i).packageName.equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setApplicationInfoSplitCodePaths(String[] splitCodePaths) {
+ this.applicationInfo.setSplitCodePaths(splitCodePaths);
+ // Children have no splits
+ }
+
+ /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+ @Deprecated
+ public void setApplicationInfoSplitResourcePaths(String[] resroucePaths) {
+ this.applicationInfo.setSplitResourcePaths(resroucePaths);
+ // Children have no splits
+ }
+
+ public void setSplitCodePaths(String[] codePaths) {
+ this.splitCodePaths = codePaths;
+ }
+
+ public void setCodePath(String codePath) {
+ this.codePath = codePath;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).codePath = codePath;
+ }
+ }
+ }
+
+ public void setBaseCodePath(String baseCodePath) {
+ this.baseCodePath = baseCodePath;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).baseCodePath = baseCodePath;
+ }
+ }
+ }
+
+ /** Sets signing details on the package and any of its children. */
+ public void setSigningDetails(@NonNull SigningDetails signingDetails) {
+ mSigningDetails = signingDetails;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).mSigningDetails = signingDetails;
+ }
+ }
+ }
+
+ public void setVolumeUuid(String volumeUuid) {
+ this.volumeUuid = volumeUuid;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).volumeUuid = volumeUuid;
+ }
+ }
+ }
+
+ public void setApplicationInfoFlags(int mask, int flags) {
+ applicationInfo.flags = (applicationInfo.flags & ~mask) | (mask & flags);
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).applicationInfo.flags =
+ (applicationInfo.flags & ~mask) | (mask & flags);
+ }
+ }
+ }
+
+ public void setUse32bitAbi(boolean use32bitAbi) {
+ this.use32bitAbi = use32bitAbi;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).use32bitAbi = use32bitAbi;
+ }
+ }
+ }
+
+ public boolean isLibrary() {
+ return staticSharedLibName != null || !ArrayUtils.isEmpty(libraryNames);
+ }
+
+ public List<String> getAllCodePaths() {
+ ArrayList<String> paths = new ArrayList<>();
+ paths.add(baseCodePath);
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ Collections.addAll(paths, splitCodePaths);
+ }
+ return paths;
+ }
+
+ /**
+ * Filtered set of {@link #getAllCodePaths()} that excludes
+ * resource-only APKs.
+ */
+ public List<String> getAllCodePathsExcludingResourceOnly() {
+ ArrayList<String> paths = new ArrayList<>();
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ paths.add(baseCodePath);
+ }
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ for (int i = 0; i < splitCodePaths.length; i++) {
+ if ((splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ paths.add(splitCodePaths[i]);
+ }
+ }
+ }
+ return paths;
+ }
+
+ @UnsupportedAppUsage
+ public void setPackageName(String newName) {
+ packageName = newName;
+ applicationInfo.packageName = newName;
+ for (int i=permissions.size()-1; i>=0; i--) {
+ permissions.get(i).setPackageName(newName);
+ }
+ for (int i=permissionGroups.size()-1; i>=0; i--) {
+ permissionGroups.get(i).setPackageName(newName);
+ }
+ for (int i=activities.size()-1; i>=0; i--) {
+ activities.get(i).setPackageName(newName);
+ }
+ for (int i=receivers.size()-1; i>=0; i--) {
+ receivers.get(i).setPackageName(newName);
+ }
+ for (int i=providers.size()-1; i>=0; i--) {
+ providers.get(i).setPackageName(newName);
+ }
+ for (int i=services.size()-1; i>=0; i--) {
+ services.get(i).setPackageName(newName);
+ }
+ for (int i=instrumentation.size()-1; i>=0; i--) {
+ instrumentation.get(i).setPackageName(newName);
+ }
+ }
+
+ public boolean hasComponentClassName(String name) {
+ for (int i=activities.size()-1; i>=0; i--) {
+ if (name.equals(activities.get(i).className)) {
+ return true;
+ }
+ }
+ for (int i=receivers.size()-1; i>=0; i--) {
+ if (name.equals(receivers.get(i).className)) {
+ return true;
+ }
+ }
+ for (int i=providers.size()-1; i>=0; i--) {
+ if (name.equals(providers.get(i).className)) {
+ return true;
+ }
+ }
+ for (int i=services.size()-1; i>=0; i--) {
+ if (name.equals(services.get(i).className)) {
+ return true;
+ }
+ }
+ for (int i=instrumentation.size()-1; i>=0; i--) {
+ if (name.equals(instrumentation.get(i).className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ public boolean isExternal() {
+ return applicationInfo.isExternal();
+ }
+
+ /** @hide */
+ public boolean isForwardLocked() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean isOem() {
+ return applicationInfo.isOem();
+ }
+
+ /** @hide */
+ public boolean isVendor() {
+ return applicationInfo.isVendor();
+ }
+
+ /** @hide */
+ public boolean isProduct() {
+ return applicationInfo.isProduct();
+ }
+
+ /** @hide */
+ public boolean isSystemExt() {
+ return applicationInfo.isSystemExt();
+ }
+
+ /** @hide */
+ public boolean isOdm() {
+ return applicationInfo.isOdm();
+ }
+
+ /** @hide */
+ public boolean isPrivileged() {
+ return applicationInfo.isPrivilegedApp();
+ }
+
+ /** @hide */
+ public boolean isSystem() {
+ return applicationInfo.isSystemApp();
+ }
+
+ /** @hide */
+ public boolean isUpdatedSystemApp() {
+ return applicationInfo.isUpdatedSystemApp();
+ }
+
+ /** @hide */
+ public boolean canHaveOatDir() {
+ // Nobody should be calling this method ever, but we can't rely on this.
+ // Thus no logic here and a reasonable return value.
+ return true;
+ }
+
+ public boolean isMatch(int flags) {
+ if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+ return isSystem();
+ }
+ return true;
+ }
+
+ public long getLatestPackageUseTimeInMills() {
+ long latestUse = 0L;
+ for (long use : mLastPackageUsageTimeInMills) {
+ latestUse = Math.max(latestUse, use);
+ }
+ return latestUse;
+ }
+
+ public long getLatestForegroundPackageUseTimeInMills() {
+ int[] foregroundReasons = {
+ PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY,
+ PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE
+ };
+
+ long latestUse = 0L;
+ for (int reason : foregroundReasons) {
+ latestUse = Math.max(latestUse, mLastPackageUsageTimeInMills[reason]);
+ }
+ return latestUse;
+ }
+
+ public String toString() {
+ return "Package{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public Package(Parcel dest) {
+ // We use the boot classloader for all classes that we load.
+ final ClassLoader boot = Object.class.getClassLoader();
+
+ packageName = dest.readString().intern();
+ manifestPackageName = dest.readString();
+ splitNames = dest.readStringArray();
+ volumeUuid = dest.readString();
+ codePath = dest.readString();
+ baseCodePath = dest.readString();
+ splitCodePaths = dest.readStringArray();
+ baseRevisionCode = dest.readInt();
+ splitRevisionCodes = dest.createIntArray();
+ splitFlags = dest.createIntArray();
+ splitPrivateFlags = dest.createIntArray();
+ baseHardwareAccelerated = (dest.readInt() == 1);
+ applicationInfo = dest.readParcelable(boot);
+ if (applicationInfo.permission != null) {
+ applicationInfo.permission = applicationInfo.permission.intern();
+ }
+
+ // We don't serialize the "owner" package and the application info object for each of
+ // these components, in order to save space and to avoid circular dependencies while
+ // serialization. We need to fix them all up here.
+ dest.readParcelableList(permissions, boot);
+ fixupOwner(permissions);
+ dest.readParcelableList(permissionGroups, boot);
+ fixupOwner(permissionGroups);
+ dest.readParcelableList(activities, boot);
+ fixupOwner(activities);
+ dest.readParcelableList(receivers, boot);
+ fixupOwner(receivers);
+ dest.readParcelableList(providers, boot);
+ fixupOwner(providers);
+ dest.readParcelableList(services, boot);
+ fixupOwner(services);
+ dest.readParcelableList(instrumentation, boot);
+ fixupOwner(instrumentation);
+
+ dest.readStringList(requestedPermissions);
+ internStringArrayList(requestedPermissions);
+ dest.readStringList(implicitPermissions);
+ internStringArrayList(implicitPermissions);
+ protectedBroadcasts = dest.createStringArrayList();
+ internStringArrayList(protectedBroadcasts);
+
+ parentPackage = dest.readParcelable(boot);
+
+ childPackages = new ArrayList<>();
+ dest.readParcelableList(childPackages, boot);
+ if (childPackages.size() == 0) {
+ childPackages = null;
+ }
+
+ staticSharedLibName = dest.readString();
+ if (staticSharedLibName != null) {
+ staticSharedLibName = staticSharedLibName.intern();
+ }
+ staticSharedLibVersion = dest.readLong();
+ libraryNames = dest.createStringArrayList();
+ internStringArrayList(libraryNames);
+ usesLibraries = dest.createStringArrayList();
+ internStringArrayList(usesLibraries);
+ usesOptionalLibraries = dest.createStringArrayList();
+ internStringArrayList(usesOptionalLibraries);
+ usesLibraryFiles = dest.readStringArray();
+
+ usesLibraryInfos = dest.createTypedArrayList(SharedLibraryInfo.CREATOR);
+
+ final int libCount = dest.readInt();
+ if (libCount > 0) {
+ usesStaticLibraries = new ArrayList<>(libCount);
+ dest.readStringList(usesStaticLibraries);
+ internStringArrayList(usesStaticLibraries);
+ usesStaticLibrariesVersions = new long[libCount];
+ dest.readLongArray(usesStaticLibrariesVersions);
+ usesStaticLibrariesCertDigests = new String[libCount][];
+ for (int i = 0; i < libCount; i++) {
+ usesStaticLibrariesCertDigests[i] = dest.createStringArray();
+ }
+ }
+
+ preferredActivityFilters = new ArrayList<>();
+ dest.readParcelableList(preferredActivityFilters, boot);
+ if (preferredActivityFilters.size() == 0) {
+ preferredActivityFilters = null;
+ }
+
+ mOriginalPackages = dest.createStringArrayList();
+ mRealPackage = dest.readString();
+ mAdoptPermissions = dest.createStringArrayList();
+ mAppMetaData = dest.readBundle();
+ mVersionCode = dest.readInt();
+ mVersionCodeMajor = dest.readInt();
+ mVersionName = dest.readString();
+ if (mVersionName != null) {
+ mVersionName = mVersionName.intern();
+ }
+ mSharedUserId = dest.readString();
+ if (mSharedUserId != null) {
+ mSharedUserId = mSharedUserId.intern();
+ }
+ mSharedUserLabel = dest.readInt();
+
+ mSigningDetails = dest.readParcelable(boot);
+
+ mPreferredOrder = dest.readInt();
+
+ // long[] packageUsageTimeMillis is not persisted because it isn't information that
+ // is parsed from the APK.
+
+ // Object mExtras is not persisted because it is not information that is read from
+ // the APK, rather, it is supplied by callers.
+
+
+ configPreferences = new ArrayList<>();
+ dest.readParcelableList(configPreferences, boot);
+ if (configPreferences.size() == 0) {
+ configPreferences = null;
+ }
+
+ reqFeatures = new ArrayList<>();
+ dest.readParcelableList(reqFeatures, boot);
+ if (reqFeatures.size() == 0) {
+ reqFeatures = null;
+ }
+
+ featureGroups = new ArrayList<>();
+ dest.readParcelableList(featureGroups, boot);
+ if (featureGroups.size() == 0) {
+ featureGroups = null;
+ }
+
+ installLocation = dest.readInt();
+ coreApp = (dest.readInt() == 1);
+ mRequiredForAllUsers = (dest.readInt() == 1);
+ mRestrictedAccountType = dest.readString();
+ mRequiredAccountType = dest.readString();
+ mOverlayTarget = dest.readString();
+ mOverlayTargetName = dest.readString();
+ mOverlayCategory = dest.readString();
+ mOverlayPriority = dest.readInt();
+ mOverlayIsStatic = (dest.readInt() == 1);
+ mCompileSdkVersion = dest.readInt();
+ mCompileSdkVersionCodename = dest.readString();
+ mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
+
+ mKeySetMapping = readKeySetMapping(dest);
+
+ cpuAbiOverride = dest.readString();
+ use32bitAbi = (dest.readInt() == 1);
+ restrictUpdateHash = dest.createByteArray();
+ visibleToInstantApps = dest.readInt() == 1;
+ }
+
+ private static void internStringArrayList(List<String> list) {
+ if (list != null) {
+ final int N = list.size();
+ for (int i = 0; i < N; ++i) {
+ list.set(i, list.get(i).intern());
+ }
+ }
+ }
+
+ /**
+ * Sets the package owner and the the {@code applicationInfo} for every component
+ * owner by this package.
+ */
+ public void fixupOwner(List<? extends Component<?>> list) {
+ if (list != null) {
+ for (Component<?> c : list) {
+ c.owner = this;
+ if (c instanceof Activity) {
+ ((Activity) c).info.applicationInfo = this.applicationInfo;
+ } else if (c instanceof Service) {
+ ((Service) c).info.applicationInfo = this.applicationInfo;
+ } else if (c instanceof Provider) {
+ ((Provider) c).info.applicationInfo = this.applicationInfo;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(packageName);
+ dest.writeString(manifestPackageName);
+ dest.writeStringArray(splitNames);
+ dest.writeString(volumeUuid);
+ dest.writeString(codePath);
+ dest.writeString(baseCodePath);
+ dest.writeStringArray(splitCodePaths);
+ dest.writeInt(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
+ dest.writeIntArray(splitFlags);
+ dest.writeIntArray(splitPrivateFlags);
+ dest.writeInt(baseHardwareAccelerated ? 1 : 0);
+ dest.writeParcelable(applicationInfo, flags);
+
+ dest.writeParcelableList(permissions, flags);
+ dest.writeParcelableList(permissionGroups, flags);
+ dest.writeParcelableList(activities, flags);
+ dest.writeParcelableList(receivers, flags);
+ dest.writeParcelableList(providers, flags);
+ dest.writeParcelableList(services, flags);
+ dest.writeParcelableList(instrumentation, flags);
+
+ dest.writeStringList(requestedPermissions);
+ dest.writeStringList(implicitPermissions);
+ dest.writeStringList(protectedBroadcasts);
+
+ // TODO: This doesn't work: b/64295061
+ dest.writeParcelable(parentPackage, flags);
+ dest.writeParcelableList(childPackages, flags);
+
+ dest.writeString(staticSharedLibName);
+ dest.writeLong(staticSharedLibVersion);
+ dest.writeStringList(libraryNames);
+ dest.writeStringList(usesLibraries);
+ dest.writeStringList(usesOptionalLibraries);
+ dest.writeStringArray(usesLibraryFiles);
+ dest.writeTypedList(usesLibraryInfos);
+
+ if (ArrayUtils.isEmpty(usesStaticLibraries)) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(usesStaticLibraries.size());
+ dest.writeStringList(usesStaticLibraries);
+ dest.writeLongArray(usesStaticLibrariesVersions);
+ for (String[] usesStaticLibrariesCertDigest : usesStaticLibrariesCertDigests) {
+ dest.writeStringArray(usesStaticLibrariesCertDigest);
+ }
+ }
+
+ dest.writeParcelableList(preferredActivityFilters, flags);
+
+ dest.writeStringList(mOriginalPackages);
+ dest.writeString(mRealPackage);
+ dest.writeStringList(mAdoptPermissions);
+ dest.writeBundle(mAppMetaData);
+ dest.writeInt(mVersionCode);
+ dest.writeInt(mVersionCodeMajor);
+ dest.writeString(mVersionName);
+ dest.writeString(mSharedUserId);
+ dest.writeInt(mSharedUserLabel);
+
+ dest.writeParcelable(mSigningDetails, flags);
+
+ dest.writeInt(mPreferredOrder);
+
+ // long[] packageUsageTimeMillis is not persisted because it isn't information that
+ // is parsed from the APK.
+
+ // Object mExtras is not persisted because it is not information that is read from
+ // the APK, rather, it is supplied by callers.
+
+ dest.writeParcelableList(configPreferences, flags);
+ dest.writeParcelableList(reqFeatures, flags);
+ dest.writeParcelableList(featureGroups, flags);
+
+ dest.writeInt(installLocation);
+ dest.writeInt(coreApp ? 1 : 0);
+ dest.writeInt(mRequiredForAllUsers ? 1 : 0);
+ dest.writeString(mRestrictedAccountType);
+ dest.writeString(mRequiredAccountType);
+ dest.writeString(mOverlayTarget);
+ dest.writeString(mOverlayTargetName);
+ dest.writeString(mOverlayCategory);
+ dest.writeInt(mOverlayPriority);
+ dest.writeInt(mOverlayIsStatic ? 1 : 0);
+ dest.writeInt(mCompileSdkVersion);
+ dest.writeString(mCompileSdkVersionCodename);
+ dest.writeArraySet(mUpgradeKeySets);
+ writeKeySetMapping(dest, mKeySetMapping);
+ dest.writeString(cpuAbiOverride);
+ dest.writeInt(use32bitAbi ? 1 : 0);
+ dest.writeByteArray(restrictUpdateHash);
+ dest.writeInt(visibleToInstantApps ? 1 : 0);
+ }
+
+ /**
+ * Writes the keyset mapping to the provided package. {@code null} mappings are permitted.
+ */
+ private static void writeKeySetMapping(
+ Parcel dest, ArrayMap<String, ArraySet<PublicKey>> keySetMapping) {
+ if (keySetMapping == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ final int N = keySetMapping.size();
+ dest.writeInt(N);
+
+ for (int i = 0; i < N; i++) {
+ dest.writeString(keySetMapping.keyAt(i));
+ ArraySet<PublicKey> keys = keySetMapping.valueAt(i);
+ if (keys == null) {
+ dest.writeInt(-1);
+ continue;
+ }
+
+ final int M = keys.size();
+ dest.writeInt(M);
+ for (int j = 0; j < M; j++) {
+ dest.writeSerializable(keys.valueAt(j));
+ }
+ }
+ }
+
+ /**
+ * Reads a keyset mapping from the given parcel at the given data position. May return
+ * {@code null} if the serialized mapping was {@code null}.
+ */
+ private static ArrayMap<String, ArraySet<PublicKey>> readKeySetMapping(Parcel in) {
+ final int N = in.readInt();
+ if (N == -1) {
+ return null;
+ }
+
+ ArrayMap<String, ArraySet<PublicKey>> keySetMapping = new ArrayMap<>();
+ for (int i = 0; i < N; ++i) {
+ String key = in.readString();
+ final int M = in.readInt();
+ if (M == -1) {
+ keySetMapping.put(key, null);
+ continue;
+ }
+
+ ArraySet<PublicKey> keys = new ArraySet<>(M);
+ for (int j = 0; j < M; ++j) {
+ PublicKey pk = (PublicKey) in.readSerializable();
+ keys.add(pk);
+ }
+
+ keySetMapping.put(key, keys);
+ }
+
+ return keySetMapping;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Package>() {
+ public Package createFromParcel(Parcel in) {
+ return new Package(in);
+ }
+
+ public Package[] newArray(int size) {
+ return new Package[size];
+ }
+ };
+ }
+
+ public static abstract class Component<II extends IntentInfo> {
+ @UnsupportedAppUsage
+ public final ArrayList<II> intents;
+ @UnsupportedAppUsage
+ public final String className;
+
+ @UnsupportedAppUsage
+ public Bundle metaData;
+ @UnsupportedAppUsage
+ public Package owner;
+ /** The order of this component in relation to its peers */
+ public int order;
+
+ ComponentName componentName;
+ String componentShortName;
+
+ public Component(Package owner, ArrayList<II> intents, String className) {
+ this.owner = owner;
+ this.intents = intents;
+ this.className = className;
+ }
+
+ public Component(Package owner) {
+ this.owner = owner;
+ this.intents = null;
+ this.className = null;
+ }
+
+ public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) {
+ owner = args.owner;
+ intents = new ArrayList<II>(0);
+ if (parsePackageItemInfo(args.owner, outInfo, args.outError, args.tag, args.sa,
+ true /*nameRequired*/, args.nameRes, args.labelRes, args.iconRes,
+ args.roundIconRes, args.logoRes, args.bannerRes)) {
+ className = outInfo.name;
+ } else {
+ className = null;
+ }
+ }
+
+ public Component(final ParseComponentArgs args, final ComponentInfo outInfo) {
+ this(args, (PackageItemInfo)outInfo);
+ if (args.outError[0] != null) {
+ return;
+ }
+
+ if (args.processRes != 0) {
+ CharSequence pname;
+ if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ pname = args.sa.getNonConfigurationString(args.processRes,
+ Configuration.NATIVE_CONFIG_VERSION);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ pname = args.sa.getNonResourceString(args.processRes);
+ }
+ outInfo.processName = buildProcessName(owner.applicationInfo.packageName,
+ owner.applicationInfo.processName, pname,
+ args.flags, args.sepProcesses, args.outError);
+ }
+
+ if (args.descriptionRes != 0) {
+ outInfo.descriptionRes = args.sa.getResourceId(args.descriptionRes, 0);
+ }
+
+ outInfo.enabled = args.sa.getBoolean(args.enabledRes, true);
+ }
+
+ public Component(Component<II> clone) {
+ owner = clone.owner;
+ intents = clone.intents;
+ className = clone.className;
+ componentName = clone.componentName;
+ componentShortName = clone.componentShortName;
+ }
+
+ @UnsupportedAppUsage
+ public ComponentName getComponentName() {
+ if (componentName != null) {
+ return componentName;
+ }
+ if (className != null) {
+ componentName = new ComponentName(owner.applicationInfo.packageName,
+ className);
+ }
+ return componentName;
+ }
+
+ protected Component(Parcel in) {
+ className = in.readString();
+ metaData = in.readBundle();
+ intents = createIntentsList(in);
+
+ owner = null;
+ }
+
+ protected void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(className);
+ dest.writeBundle(metaData);
+
+ writeIntentsList(intents, dest, flags);
+ }
+
+ /**
+ * <p>
+ * Implementation note: The serialized form for the intent list also contains the name
+ * of the concrete class that's stored in the list, and assumes that every element of the
+ * list is of the same type. This is very similar to the original parcelable mechanism.
+ * We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable
+ * and is public API. It also declares Parcelable related methods as final which means
+ * we can't extend them. The approach of using composition instead of inheritance leads to
+ * a large set of cascading changes in the PackageManagerService, which seem undesirable.
+ *
+ * <p>
+ * <b>WARNING: </b> The list of objects returned by this function might need to be fixed up
+ * to make sure their owner fields are consistent. See {@code fixupOwner}.
+ */
+ private static void writeIntentsList(ArrayList<? extends IntentInfo> list, Parcel out,
+ int flags) {
+ if (list == null) {
+ out.writeInt(-1);
+ return;
+ }
+
+ final int N = list.size();
+ out.writeInt(N);
+
+ // Don't bother writing the component name if the list is empty.
+ if (N > 0) {
+ IntentInfo info = list.get(0);
+ out.writeString(info.getClass().getName());
+
+ for (int i = 0; i < N;i++) {
+ list.get(i).writeIntentInfoToParcel(out, flags);
+ }
+ }
+ }
+
+ private static <T extends IntentInfo> ArrayList<T> createIntentsList(Parcel in) {
+ int N = in.readInt();
+ if (N == -1) {
+ return null;
+ }
+
+ if (N == 0) {
+ return new ArrayList<>(0);
+ }
+
+ String componentName = in.readString();
+ final ArrayList<T> intentsList;
+ try {
+ final Class<T> cls = (Class<T>) Class.forName(componentName);
+ final Constructor<T> cons = cls.getConstructor(Parcel.class);
+
+ intentsList = new ArrayList<>(N);
+ for (int i = 0; i < N; ++i) {
+ intentsList.add(cons.newInstance(in));
+ }
+ } catch (ReflectiveOperationException ree) {
+ throw new AssertionError("Unable to construct intent list for: " + componentName);
+ }
+
+ return intentsList;
+ }
+
+ public void appendComponentShortName(StringBuilder sb) {
+ ComponentName.appendShortString(sb, owner.applicationInfo.packageName, className);
+ }
+
+ public void printComponentShortName(PrintWriter pw) {
+ ComponentName.printShortString(pw, owner.applicationInfo.packageName, className);
+ }
+
+ public void setPackageName(String packageName) {
+ componentName = null;
+ componentShortName = null;
+ }
+ }
+
+ public final static class Permission extends Component<IntentInfo> implements Parcelable {
+ @UnsupportedAppUsage
+ public final PermissionInfo info;
+ @UnsupportedAppUsage
+ public boolean tree;
+ @UnsupportedAppUsage
+ public PermissionGroup group;
+
+ /**
+ * @hide
+ */
+ public Permission(Package owner, @Nullable String backgroundPermission) {
+ super(owner);
+ info = new PermissionInfo(backgroundPermission);
+ }
+
+ @UnsupportedAppUsage
+ public Permission(Package _owner, PermissionInfo _info) {
+ super(_owner);
+ info = _info;
+ }
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
+ public String toString() {
+ return "Permission{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + info.name + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(info, flags);
+ dest.writeInt(tree ? 1 : 0);
+ dest.writeParcelable(group, flags);
+ }
+
+ /** @hide */
+ public boolean isAppOp() {
+ return info.isAppOp();
+ }
+
+ private Permission(Parcel in) {
+ super(in);
+ final ClassLoader boot = Object.class.getClassLoader();
+ info = in.readParcelable(boot);
+ if (info.group != null) {
+ info.group = info.group.intern();
+ }
+
+ tree = (in.readInt() == 1);
+ group = in.readParcelable(boot);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Permission>() {
+ public Permission createFromParcel(Parcel in) {
+ return new Permission(in);
+ }
+
+ public Permission[] newArray(int size) {
+ return new Permission[size];
+ }
+ };
+ }
+
+ public final static class PermissionGroup extends Component<IntentInfo> implements Parcelable {
+ @UnsupportedAppUsage
+ public final PermissionGroupInfo info;
+
+ public PermissionGroup(Package owner, @StringRes int requestDetailResourceId,
+ @StringRes int backgroundRequestResourceId,
+ @StringRes int backgroundRequestDetailResourceId) {
+ super(owner);
+ info = new PermissionGroupInfo(requestDetailResourceId, backgroundRequestResourceId,
+ backgroundRequestDetailResourceId);
+ }
+
+ public PermissionGroup(Package _owner, PermissionGroupInfo _info) {
+ super(_owner);
+ info = _info;
+ }
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
+ public String toString() {
+ return "PermissionGroup{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + info.name + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(info, flags);
+ }
+
+ private PermissionGroup(Parcel in) {
+ super(in);
+ info = in.readParcelable(Object.class.getClassLoader());
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<PermissionGroup>() {
+ public PermissionGroup createFromParcel(Parcel in) {
+ return new PermissionGroup(in);
+ }
+
+ public PermissionGroup[] newArray(int size) {
+ return new PermissionGroup[size];
+ }
+ };
+ }
+
+ private static boolean copyNeeded(int flags, Package p,
+ PackageUserState state, Bundle metaData, int userId) {
+ if (userId != UserHandle.USER_SYSTEM) {
+ // We always need to copy for other users, since we need
+ // to fix up the uid.
+ return true;
+ }
+ if (state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+ boolean enabled = state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ if (p.applicationInfo.enabled != enabled) {
+ return true;
+ }
+ }
+ boolean suspended = (p.applicationInfo.flags & FLAG_SUSPENDED) != 0;
+ if (state.suspended != suspended) {
+ return true;
+ }
+ if (!state.installed || state.hidden) {
+ return true;
+ }
+ if (state.stopped) {
+ return true;
+ }
+ if (state.instantApp != p.applicationInfo.isInstantApp()) {
+ return true;
+ }
+ if ((flags & PackageManager.GET_META_DATA) != 0
+ && (metaData != null || p.mAppMetaData != null)) {
+ return true;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0
+ && p.usesLibraryFiles != null) {
+ return true;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0
+ && p.usesLibraryInfos != null) {
+ return true;
+ }
+ if (p.staticSharedLibName != null) {
+ return true;
+ }
+ return false;
+ }
+
+ @UnsupportedAppUsage
+ public static ApplicationInfo generateApplicationInfo(Package p, int flags,
+ PackageUserState state) {
+ return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
+ }
+
+ private static void updateApplicationInfo(ApplicationInfo ai, int flags,
+ PackageUserState state) {
+ // CompatibilityMode is global state.
+ if (!sCompatibilityModeEnabled) {
+ ai.disableCompatibilityMode();
+ }
+ if (state.installed) {
+ ai.flags |= ApplicationInfo.FLAG_INSTALLED;
+ } else {
+ ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+ }
+ if (state.suspended) {
+ ai.flags |= ApplicationInfo.FLAG_SUSPENDED;
+ } else {
+ ai.flags &= ~ApplicationInfo.FLAG_SUSPENDED;
+ }
+ if (state.instantApp) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
+ } else {
+ ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_INSTANT;
+ }
+ if (state.virtualPreload) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD;
+ } else {
+ ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD;
+ }
+ if (state.hidden) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
+ } else {
+ ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HIDDEN;
+ }
+ if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ ai.enabled = true;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ ai.enabled = false;
+ }
+ ai.enabledSetting = state.enabled;
+ if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ ai.category = state.categoryHint;
+ }
+ if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
+ }
+ ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
+ final OverlayPaths overlayPaths = state.getAllOverlayPaths();
+ if (overlayPaths != null) {
+ ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]);
+ ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]);
+ }
+ ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
+ }
+
+ @UnsupportedAppUsage
+ public static ApplicationInfo generateApplicationInfo(Package p, int flags,
+ PackageUserState state, int userId) {
+ if (p == null) return null;
+ if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
+ return null;
+ }
+ if (!copyNeeded(flags, p, state, null, userId)
+ && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0
+ || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+ // In this case it is safe to directly modify the internal ApplicationInfo state:
+ // - CompatibilityMode is global state, so will be the same for every call.
+ // - We only come in to here if the app should reported as installed; this is the
+ // default state, and we will do a copy otherwise.
+ // - The enable state will always be reported the same for the application across
+ // calls; the only exception is for the UNTIL_USED mode, and in that case we will
+ // be doing a copy.
+ updateApplicationInfo(p.applicationInfo, flags, state);
+ return p.applicationInfo;
+ }
+
+ // Make shallow copy so we can store the metadata/libraries safely
+ ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
+ ai.initForUser(userId);
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ ai.metaData = p.mAppMetaData;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+ ai.sharedLibraryFiles = p.usesLibraryFiles;
+ ai.sharedLibraryInfos = p.usesLibraryInfos;
+ }
+ if (state.stopped) {
+ ai.flags |= ApplicationInfo.FLAG_STOPPED;
+ } else {
+ ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
+ }
+ updateApplicationInfo(ai, flags, state);
+ return ai;
+ }
+
+ public static ApplicationInfo generateApplicationInfo(ApplicationInfo ai, int flags,
+ PackageUserState state, int userId) {
+ if (ai == null) return null;
+ if (!checkUseInstalledOrHidden(flags, state, ai)) {
+ return null;
+ }
+ // This is only used to return the ResolverActivity; we will just always
+ // make a copy.
+ ai = new ApplicationInfo(ai);
+ ai.initForUser(userId);
+ if (state.stopped) {
+ ai.flags |= ApplicationInfo.FLAG_STOPPED;
+ } else {
+ ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
+ }
+ updateApplicationInfo(ai, flags, state);
+ return ai;
+ }
+
+ @UnsupportedAppUsage
+ public static final PermissionInfo generatePermissionInfo(
+ Permission p, int flags) {
+ if (p == null) return null;
+ if ((flags&PackageManager.GET_META_DATA) == 0) {
+ return p.info;
+ }
+ PermissionInfo pi = new PermissionInfo(p.info);
+ pi.metaData = p.metaData;
+ return pi;
+ }
+
+ @UnsupportedAppUsage
+ public static final PermissionGroupInfo generatePermissionGroupInfo(
+ PermissionGroup pg, int flags) {
+ if (pg == null) return null;
+ if ((flags&PackageManager.GET_META_DATA) == 0) {
+ return pg.info;
+ }
+ PermissionGroupInfo pgi = new PermissionGroupInfo(pg.info);
+ pgi.metaData = pg.metaData;
+ return pgi;
+ }
+
+ public final static class Activity extends Component<ActivityIntentInfo> implements Parcelable {
+ @UnsupportedAppUsage
+ public final ActivityInfo info;
+ private boolean mHasMaxAspectRatio;
+ private boolean mHasMinAspectRatio;
+
+ private boolean hasMaxAspectRatio() {
+ return mHasMaxAspectRatio;
+ }
+
+ private boolean hasMinAspectRatio() {
+ return mHasMinAspectRatio;
+ }
+
+ // To construct custom activity which does not exist in manifest
+ Activity(final Package owner, final String className, final ActivityInfo info) {
+ super(owner, new ArrayList<>(0), className);
+ this.info = info;
+ this.info.applicationInfo = owner.applicationInfo;
+ }
+
+ public Activity(final ParseComponentArgs args, final ActivityInfo _info) {
+ super(args, _info);
+ info = _info;
+ info.applicationInfo = args.owner.applicationInfo;
+ }
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
+
+ private void setMaxAspectRatio(float maxAspectRatio) {
+ if (info.resizeMode == RESIZE_MODE_RESIZEABLE
+ || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ return;
+ }
+
+ if (maxAspectRatio < 1.0f && maxAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ return;
+ }
+
+ info.setMaxAspectRatio(maxAspectRatio);
+ mHasMaxAspectRatio = true;
+ }
+
+ private void setMinAspectRatio(float minAspectRatio) {
+ if (info.resizeMode == RESIZE_MODE_RESIZEABLE
+ || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ return;
+ }
+
+ if (minAspectRatio < 1.0f && minAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ return;
+ }
+
+ info.setMinAspectRatio(minAspectRatio);
+ mHasMinAspectRatio = true;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Activity{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ dest.writeBoolean(mHasMaxAspectRatio);
+ dest.writeBoolean(mHasMinAspectRatio);
+ }
+
+ private Activity(Parcel in) {
+ super(in);
+ info = in.readParcelable(Object.class.getClassLoader());
+ mHasMaxAspectRatio = in.readBoolean();
+ mHasMinAspectRatio = in.readBoolean();
+
+ for (ActivityIntentInfo aii : intents) {
+ aii.activity = this;
+ order = Math.max(aii.getOrder(), order);
+ }
+
+ if (info.permission != null) {
+ info.permission = info.permission.intern();
+ }
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Activity>() {
+ public Activity createFromParcel(Parcel in) {
+ return new Activity(in);
+ }
+
+ public Activity[] newArray(int size) {
+ return new Activity[size];
+ }
+ };
+ }
+
+ @UnsupportedAppUsage
+ public static final ActivityInfo generateActivityInfo(Activity a, int flags,
+ PackageUserState state, int userId) {
+ if (a == null) return null;
+ if (!checkUseInstalledOrHidden(flags, state, a.owner.applicationInfo)) {
+ return null;
+ }
+ if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
+ updateApplicationInfo(a.info.applicationInfo, flags, state);
+ return a.info;
+ }
+ // Make shallow copies so we can store the metadata safely
+ ActivityInfo ai = new ActivityInfo(a.info);
+ ai.metaData = a.metaData;
+ ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+ return ai;
+ }
+
+ public static final ActivityInfo generateActivityInfo(ActivityInfo ai, int flags,
+ PackageUserState state, int userId) {
+ if (ai == null) return null;
+ if (!checkUseInstalledOrHidden(flags, state, ai.applicationInfo)) {
+ return null;
+ }
+ // This is only used to return the ResolverActivity; we will just always
+ // make a copy.
+ ai = new ActivityInfo(ai);
+ ai.applicationInfo = generateApplicationInfo(ai.applicationInfo, flags, state, userId);
+ return ai;
+ }
+
+ public final static class Service extends Component<ServiceIntentInfo> implements Parcelable {
+ @UnsupportedAppUsage
+ public final ServiceInfo info;
+
+ public Service(final ParseComponentArgs args, final ServiceInfo _info) {
+ super(args, _info);
+ info = _info;
+ info.applicationInfo = args.owner.applicationInfo;
+ }
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Service{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ }
+
+ private Service(Parcel in) {
+ super(in);
+ info = in.readParcelable(Object.class.getClassLoader());
+
+ for (ServiceIntentInfo aii : intents) {
+ aii.service = this;
+ order = Math.max(aii.getOrder(), order);
+ }
+
+ if (info.permission != null) {
+ info.permission = info.permission.intern();
+ }
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Service>() {
+ public Service createFromParcel(Parcel in) {
+ return new Service(in);
+ }
+
+ public Service[] newArray(int size) {
+ return new Service[size];
+ }
+ };
+ }
+
+ @UnsupportedAppUsage
+ public static final ServiceInfo generateServiceInfo(Service s, int flags,
+ PackageUserState state, int userId) {
+ if (s == null) return null;
+ if (!checkUseInstalledOrHidden(flags, state, s.owner.applicationInfo)) {
+ return null;
+ }
+ if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) {
+ updateApplicationInfo(s.info.applicationInfo, flags, state);
+ return s.info;
+ }
+ // Make shallow copies so we can store the metadata safely
+ ServiceInfo si = new ServiceInfo(s.info);
+ si.metaData = s.metaData;
+ si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+ return si;
+ }
+
+ public final static class Provider extends Component<ProviderIntentInfo> implements Parcelable {
+ @UnsupportedAppUsage
+ public final ProviderInfo info;
+ @UnsupportedAppUsage
+ public boolean syncable;
+
+ public Provider(final ParseComponentArgs args, final ProviderInfo _info) {
+ super(args, _info);
+ info = _info;
+ info.applicationInfo = args.owner.applicationInfo;
+ syncable = false;
+ }
+
+ @UnsupportedAppUsage
+ public Provider(Provider existingProvider) {
+ super(existingProvider);
+ this.info = existingProvider.info;
+ this.syncable = existingProvider.syncable;
+ }
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Provider{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ dest.writeInt((syncable) ? 1 : 0);
+ }
+
+ private Provider(Parcel in) {
+ super(in);
+ info = in.readParcelable(Object.class.getClassLoader());
+ syncable = (in.readInt() == 1);
+
+ for (ProviderIntentInfo aii : intents) {
+ aii.provider = this;
+ }
+
+ if (info.readPermission != null) {
+ info.readPermission = info.readPermission.intern();
+ }
+
+ if (info.writePermission != null) {
+ info.writePermission = info.writePermission.intern();
+ }
+
+ if (info.authority != null) {
+ info.authority = info.authority.intern();
+ }
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Provider>() {
+ public Provider createFromParcel(Parcel in) {
+ return new Provider(in);
+ }
+
+ public Provider[] newArray(int size) {
+ return new Provider[size];
+ }
+ };
+ }
+
+ @UnsupportedAppUsage
+ public static final ProviderInfo generateProviderInfo(Provider p, int flags,
+ PackageUserState state, int userId) {
+ if (p == null) return null;
+ if (!checkUseInstalledOrHidden(flags, state, p.owner.applicationInfo)) {
+ return null;
+ }
+ if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
+ && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
+ || p.info.uriPermissionPatterns == null)) {
+ updateApplicationInfo(p.info.applicationInfo, flags, state);
+ return p.info;
+ }
+ // Make shallow copies so we can store the metadata safely
+ ProviderInfo pi = new ProviderInfo(p.info);
+ pi.metaData = p.metaData;
+ if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+ pi.uriPermissionPatterns = null;
+ }
+ pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+ return pi;
+ }
+
+ public final static class Instrumentation extends Component<IntentInfo> implements
+ Parcelable {
+ @UnsupportedAppUsage
+ public final InstrumentationInfo info;
+
+ public Instrumentation(final ParsePackageItemArgs args, final InstrumentationInfo _info) {
+ super(args, _info);
+ info = _info;
+ }
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Instrumentation{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(info, flags);
+ }
+
+ private Instrumentation(Parcel in) {
+ super(in);
+ info = in.readParcelable(Object.class.getClassLoader());
+
+ if (info.targetPackage != null) {
+ info.targetPackage = info.targetPackage.intern();
+ }
+
+ if (info.targetProcesses != null) {
+ info.targetProcesses = info.targetProcesses.intern();
+ }
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Instrumentation>() {
+ public Instrumentation createFromParcel(Parcel in) {
+ return new Instrumentation(in);
+ }
+
+ public Instrumentation[] newArray(int size) {
+ return new Instrumentation[size];
+ }
+ };
+ }
+
+ @UnsupportedAppUsage
+ public static final InstrumentationInfo generateInstrumentationInfo(
+ Instrumentation i, int flags) {
+ if (i == null) return null;
+ if ((flags&PackageManager.GET_META_DATA) == 0) {
+ return i.info;
+ }
+ InstrumentationInfo ii = new InstrumentationInfo(i.info);
+ ii.metaData = i.metaData;
+ return ii;
+ }
+
+ public static abstract class IntentInfo extends IntentFilter {
+ @UnsupportedAppUsage
+ public boolean hasDefault;
+ @UnsupportedAppUsage
+ public int labelRes;
+ @UnsupportedAppUsage
+ public CharSequence nonLocalizedLabel;
+ @UnsupportedAppUsage
+ public int icon;
+ @UnsupportedAppUsage
+ public int logo;
+ @UnsupportedAppUsage
+ public int banner;
+ public int preferred;
+
+ @UnsupportedAppUsage
+ protected IntentInfo() {
+ }
+
+ protected IntentInfo(Parcel dest) {
+ super(dest);
+ hasDefault = (dest.readInt() == 1);
+ labelRes = dest.readInt();
+ nonLocalizedLabel = dest.readCharSequence();
+ icon = dest.readInt();
+ logo = dest.readInt();
+ banner = dest.readInt();
+ preferred = dest.readInt();
+ }
+
+
+ public void writeIntentInfoToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(hasDefault ? 1 : 0);
+ dest.writeInt(labelRes);
+ dest.writeCharSequence(nonLocalizedLabel);
+ dest.writeInt(icon);
+ dest.writeInt(logo);
+ dest.writeInt(banner);
+ dest.writeInt(preferred);
+ }
+ }
+
+ public final static class ActivityIntentInfo extends IntentInfo {
+ @UnsupportedAppUsage
+ public Activity activity;
+
+ public ActivityIntentInfo(Activity _activity) {
+ activity = _activity;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ActivityIntentInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ activity.appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public ActivityIntentInfo(Parcel in) {
+ super(in);
+ }
+ }
+
+ public final static class ServiceIntentInfo extends IntentInfo {
+ @UnsupportedAppUsage
+ public Service service;
+
+ public ServiceIntentInfo(Service _service) {
+ service = _service;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ServiceIntentInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ service.appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public ServiceIntentInfo(Parcel in) {
+ super(in);
+ }
+ }
+
+ public static final class ProviderIntentInfo extends IntentInfo {
+ @UnsupportedAppUsage
+ public Provider provider;
+
+ public ProviderIntentInfo(Provider provider) {
+ this.provider = provider;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ProviderIntentInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ provider.appendComponentShortName(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public ProviderIntentInfo(Parcel in) {
+ super(in);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
+ sCompatibilityModeEnabled = compatibilityModeEnabled;
+ }
+
+ /**
+ * @hide
+ */
+ public static void readConfigUseRoundIcon(Resources r) {
+ if (r != null) {
+ sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ return;
+ }
+
+ ApplicationInfo androidAppInfo;
+ try {
+ androidAppInfo = ActivityThread.getPackageManager().getApplicationInfo(
+ "android", 0 /* flags */,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ Resources systemResources = Resources.getSystem();
+
+ // Create in-flight as this overlayable resource is only used when config changes
+ Resources overlayableRes = ResourcesManager.getInstance().getResources(null,
+ null,
+ null,
+ androidAppInfo.resourceDirs,
+ androidAppInfo.overlayPaths,
+ androidAppInfo.sharedLibraryFiles,
+ null,
+ null,
+ systemResources.getCompatibilityInfo(),
+ systemResources.getClassLoader(),
+ null);
+
+ sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ }
+
+ public static class PackageParserException extends Exception {
+ public final int error;
+
+ public PackageParserException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+
+ public PackageParserException(int error, String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ this.error = error;
+ }
+ }
+
+ // Duplicate the SplitAsset related classes with PackageParser.Package/ApkLite here, and
+ // change the original one using new Package/ApkLite. The propose is that we don't want to
+ // have two branches of methods in SplitAsset related classes so we can keep real classes
+ // clean and move all the legacy code to one place.
+
+ /**
+ * A helper class that implements the dependency tree traversal for splits. Callbacks
+ * are implemented by subclasses to notify whether a split has already been constructed
+ * and is cached, and to actually create the split requested.
+ *
+ * This helper is meant to be subclassed so as to reduce the number of allocations
+ * needed to make use of it.
+ *
+ * All inputs and outputs are assumed to be indices into an array of splits.
+ *
+ * @hide
+ * @deprecated Do not use. New changes should use
+ * {@link android.content.pm.split.SplitDependencyLoader} instead.
+ */
+ @Deprecated
+ private abstract static class SplitDependencyLoader<E extends Exception> {
+ private final @NonNull SparseArray<int[]> mDependencies;
+
+ /**
+ * Construct a new SplitDependencyLoader. Meant to be called from the
+ * subclass constructor.
+ * @param dependencies The dependency tree of splits.
+ */
+ protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) {
+ mDependencies = dependencies;
+ }
+
+ /**
+ * Traverses the dependency tree and constructs any splits that are not already
+ * cached. This routine short-circuits and skips the creation of splits closer to the
+ * root if they are cached, as reported by the subclass implementation of
+ * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+ * implementation of {@link #constructSplit(int, int[], int)}.
+ * @param splitIdx The index of the split to load. 0 represents the base Application.
+ */
+ protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+ // Quick check before any allocations are done.
+ if (isSplitCached(splitIdx)) {
+ return;
+ }
+
+ // Special case the base, since it has no dependencies.
+ if (splitIdx == 0) {
+ final int[] configSplitIndices = collectConfigSplitIndices(0);
+ constructSplit(0, configSplitIndices, -1);
+ return;
+ }
+
+ // Build up the dependency hierarchy.
+ final IntArray linearDependencies = new IntArray();
+ linearDependencies.add(splitIdx);
+
+ // Collect all the dependencies that need to be constructed.
+ // They will be listed from leaf to root.
+ while (true) {
+ // Only follow the first index into the array. The others are config splits and
+ // get loaded with the split.
+ final int[] deps = mDependencies.get(splitIdx);
+ if (deps != null && deps.length > 0) {
+ splitIdx = deps[0];
+ } else {
+ splitIdx = -1;
+ }
+
+ if (splitIdx < 0 || isSplitCached(splitIdx)) {
+ break;
+ }
+
+ linearDependencies.add(splitIdx);
+ }
+
+ // Visit each index, from right to left (root to leaf).
+ int parentIdx = splitIdx;
+ for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+ final int idx = linearDependencies.get(i);
+ final int[] configSplitIndices = collectConfigSplitIndices(idx);
+ constructSplit(idx, configSplitIndices, parentIdx);
+ parentIdx = idx;
+ }
+ }
+
+ private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+ // The config splits appear after the first element.
+ final int[] deps = mDependencies.get(splitIdx);
+ if (deps == null || deps.length <= 1) {
+ return EmptyArray.INT;
+ }
+ return Arrays.copyOfRange(deps, 1, deps.length);
+ }
+
+ /**
+ * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+ * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+ * @param splitIdx The index of the split to check for in the cache.
+ * @return true if the split is cached and does not need to be constructed.
+ */
+ protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx);
+
+ /**
+ * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+ * The result is expected to be cached by the subclass in its own structures.
+ * @param splitIdx The index of the split to construct. 0 represents the base Application.
+ * @param configSplitIndices The array of configuration splits to load along with this
+ * split. May be empty (length == 0) but never null.
+ * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+ * @throws E Subclass defined exception representing failure to construct a split.
+ */
+ protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+ @NonNull @IntRange(from = 1) int[] configSplitIndices,
+ @IntRange(from = -1) int parentSplitIdx) throws E;
+
+ public static class IllegalDependencyException extends Exception {
+ private IllegalDependencyException(String message) {
+ super(message);
+ }
+ }
+
+ private static int[] append(int[] src, int elem) {
+ if (src == null) {
+ return new int[] { elem };
+ }
+ int[] dst = Arrays.copyOf(src, src.length + 1);
+ dst[src.length] = elem;
+ return dst;
+ }
+
+ public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+ PackageLite pkg)
+ throws SplitDependencyLoader.IllegalDependencyException {
+ // The data structure that holds the dependencies. In PackageParser, splits are stored
+ // in their own array, separate from the base. We treat all paths as equals, so
+ // we need to insert the base as index 0, and shift all other splits.
+ final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+ // The base depends on nothing.
+ splitDependencies.put(0, new int[] {-1});
+
+ // First write out the <uses-split> dependencies. These must appear first in the
+ // array of ints, as is convention in this class.
+ for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+ if (!pkg.isFeatureSplits[splitIdx]) {
+ // Non-feature splits don't have dependencies.
+ continue;
+ }
+
+ // Implicit dependency on the base.
+ final int targetIdx;
+ final String splitDependency = pkg.usesSplitNames[splitIdx];
+ if (splitDependency != null) {
+ final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+ if (depIdx < 0) {
+ throw new SplitDependencyLoader.IllegalDependencyException(
+ "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
+ + splitDependency + "', which is missing.");
+ }
+ targetIdx = depIdx + 1;
+ } else {
+ // Implicitly depend on the base.
+ targetIdx = 0;
+ }
+ splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+ }
+
+ // Write out the configForSplit reverse-dependencies. These appear after the
+ // <uses-split> dependencies and are considered leaves.
+ //
+ // At this point, all splits in splitDependencies have the first element in their
+ // array set.
+ for (int splitIdx = 0, size = pkg.splitNames.length; splitIdx < size; splitIdx++) {
+ if (pkg.isFeatureSplits[splitIdx]) {
+ // Feature splits are not configForSplits.
+ continue;
+ }
+
+ // Implicit feature for the base.
+ final int targetSplitIdx;
+ final String configForSplit = pkg.configForSplit[splitIdx];
+ if (configForSplit != null) {
+ final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+ if (depIdx < 0) {
+ throw new SplitDependencyLoader.IllegalDependencyException(
+ "Split '" + pkg.splitNames[splitIdx] + "' targets split '"
+ + configForSplit + "', which is missing.");
+ }
+
+ if (!pkg.isFeatureSplits[depIdx]) {
+ throw new SplitDependencyLoader.IllegalDependencyException(
+ "Split '" + pkg.splitNames[splitIdx] + "' declares itself as "
+ + "configuration split for a non-feature split '"
+ + pkg.splitNames[depIdx] + "'");
+ }
+ targetSplitIdx = depIdx + 1;
+ } else {
+ targetSplitIdx = 0;
+ }
+ splitDependencies.put(targetSplitIdx,
+ append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+ }
+
+ // Verify that there are no cycles.
+ final BitSet bitset = new BitSet();
+ for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+ int splitIdx = splitDependencies.keyAt(i);
+
+ bitset.clear();
+ while (splitIdx != -1) {
+ // Check if this split has been visited yet.
+ if (bitset.get(splitIdx)) {
+ throw new SplitDependencyLoader.IllegalDependencyException(
+ "Cycle detected in split dependencies.");
+ }
+
+ // Mark the split so that if we visit it again, we no there is a cycle.
+ bitset.set(splitIdx);
+
+ // Follow the first dependency only, the others are leaves by definition.
+ final int[] deps = splitDependencies.get(splitIdx);
+ splitIdx = deps != null ? deps[0] : -1;
+ }
+ }
+ return splitDependencies;
+ }
+ }
+
+ /**
+ * Loads the base and split APKs into a single AssetManager.
+ * @hide
+ * @deprecated Do not use. New changes should use
+ * {@link android.content.pm.split.DefaultSplitAssetLoader} instead.
+ */
+ @Deprecated
+ private static class DefaultSplitAssetLoader implements SplitAssetLoader {
+ private final String mBaseCodePath;
+ private final String[] mSplitCodePaths;
+ private final @ParseFlags int mFlags;
+ private AssetManager mCachedAssetManager;
+
+ private ApkAssets mBaseApkAssets;
+
+ DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
+ mBaseCodePath = pkg.baseCodePath;
+ mSplitCodePaths = pkg.splitCodePaths;
+ mFlags = flags;
+ }
+
+ 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 {
+ 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 PackageParserException {
+ if (mCachedAssetManager != null) {
+ return mCachedAssetManager;
+ }
+
+ ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
+ ? mSplitCodePaths.length : 0) + 1];
+
+ mBaseApkAssets = loadApkAssets(mBaseCodePath, mFlags);
+
+ // Load the base.
+ int splitIdx = 0;
+ apkAssets[splitIdx++] = mBaseApkAssets;
+
+ // 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 PackageParserException {
+ return getBaseAssetManager();
+ }
+
+ @Override
+ public void close() throws Exception {
+ IoUtils.closeQuietly(mCachedAssetManager);
+ }
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mBaseApkAssets;
+ }
+ }
+
+ /**
+ * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+ * is to be used when an application opts-in to isolated split loading.
+ * @hide
+ * @deprecated Do not use. New changes should use
+ * {@link android.content.pm.split.SplitAssetDependencyLoader} instead.
+ */
+ @Deprecated
+ private static class SplitAssetDependencyLoader extends
+ SplitDependencyLoader<PackageParserException> implements SplitAssetLoader {
+ private final String[] mSplitPaths;
+ private final @ParseFlags int mFlags;
+ private final ApkAssets[][] mCachedSplitApks;
+ private final AssetManager[] mCachedAssetManagers;
+
+ SplitAssetDependencyLoader(PackageLite pkg,
+ 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.
+ mSplitPaths = new String[pkg.splitCodePaths.length + 1];
+ mSplitPaths[0] = pkg.baseCodePath;
+ System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+
+ mFlags = flags;
+ mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
+ mCachedAssetManagers = new AssetManager[mSplitPaths.length];
+ }
+
+ @Override
+ protected boolean isSplitCached(int splitIdx) {
+ return mCachedAssetManagers[splitIdx] != null;
+ }
+
+ 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 {
+ 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 PackageParserException {
+ final ArrayList<ApkAssets> assets = new ArrayList<>();
+
+ // Include parent ApkAssets.
+ if (parentSplitIdx >= 0) {
+ Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
+ }
+
+ // Include this ApkAssets.
+ assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
+
+ // Load and include all config splits for this feature.
+ for (int configSplitIdx : configSplitIndices) {
+ assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
+ }
+
+ // Cache the results.
+ mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
+ mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(
+ mCachedSplitApks[splitIdx]);
+ }
+
+ @Override
+ public AssetManager getBaseAssetManager() throws PackageParserException {
+ loadDependenciesForSplit(0);
+ return mCachedAssetManagers[0];
+ }
+
+ @Override
+ 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);
+ return mCachedAssetManagers[idx + 1];
+ }
+
+ @Override
+ public void close() throws Exception {
+ for (AssetManager assets : mCachedAssetManagers) {
+ IoUtils.closeQuietly(assets);
+ }
+ }
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mCachedSplitApks[0][0];
+ }
+ }
+}
diff --git a/android/content/pm/PackageParserCacheHelper.java b/android/content/pm/PackageParserCacheHelper.java
new file mode 100644
index 0000000..e03c3ee
--- /dev/null
+++ b/android/content/pm/PackageParserCacheHelper.java
@@ -0,0 +1,178 @@
+/*
+ * 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.pm;
+
+import android.os.Parcel;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Helper classes to read from and write to Parcel with pooled strings.
+ *
+ * @hide
+ */
+public class PackageParserCacheHelper {
+ private PackageParserCacheHelper() {
+ }
+
+ private static final String TAG = "PackageParserCacheHelper";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Parcel read helper with a string pool.
+ */
+ public static class ReadHelper extends Parcel.ReadWriteHelper {
+ private final ArrayList<String> mStrings = new ArrayList<>();
+
+ private final Parcel mParcel;
+
+ public ReadHelper(Parcel p) {
+ mParcel = p;
+ }
+
+ /**
+ * Prepare to read from a parcel, and install itself as a read-write helper.
+ *
+ * (We don't do it in the constructor to avoid calling methods before the constructor
+ * finishes.)
+ */
+ public void startAndInstall() {
+ mStrings.clear();
+
+ final int poolPosition = mParcel.readInt();
+ if (poolPosition < 0) {
+ throw new IllegalStateException("Invalid string pool position: " + poolPosition);
+ }
+ final int startPosition = mParcel.dataPosition();
+
+ // The pool is at the end of the parcel.
+ mParcel.setDataPosition(poolPosition);
+ mParcel.readStringList(mStrings);
+
+ // Then move back.
+ mParcel.setDataPosition(startPosition);
+
+ if (DEBUG) {
+ Log.i(TAG, "Read " + mStrings.size() + " strings");
+ for (int i = 0; i < mStrings.size(); i++) {
+ Log.i(TAG, " " + i + ": \"" + mStrings.get(i) + "\"");
+ }
+ }
+
+ mParcel.setReadWriteHelper(this);
+ }
+
+ /**
+ * Read an string index from a parcel, and returns the corresponding string from the pool.
+ */
+ public String readString(Parcel p) {
+ return mStrings.get(p.readInt());
+ }
+
+ @Override
+ public String readString8(Parcel p) {
+ return readString(p);
+ }
+
+ @Override
+ public String readString16(Parcel p) {
+ return readString(p);
+ }
+ }
+
+ /**
+ * Parcel write helper with a string pool.
+ */
+ public static class WriteHelper extends Parcel.ReadWriteHelper {
+ private final ArrayList<String> mStrings = new ArrayList<>();
+
+ private final HashMap<String, Integer> mIndexes = new HashMap<>();
+
+ private final Parcel mParcel;
+ private final int mStartPos;
+
+ /**
+ * Constructor. Prepare a parcel, and install it self as a read-write helper.
+ */
+ public WriteHelper(Parcel p) {
+ mParcel = p;
+ mStartPos = p.dataPosition();
+ mParcel.writeInt(0); // We come back later here and write the pool position.
+
+ mParcel.setReadWriteHelper(this);
+ }
+
+ /**
+ * Instead of writing a string directly to a parcel, this method adds it to the pool,
+ * and write the index in the pool to the parcel.
+ */
+ public void writeString(Parcel p, String s) {
+ final Integer cur = mIndexes.get(s);
+ if (cur != null) {
+ // String already in the pool. Just write the index.
+ p.writeInt(cur); // Already in the pool.
+ if (DEBUG) {
+ Log.i(TAG, "Duplicate '" + s + "' at " + cur);
+ }
+ } else {
+ // Not in the pool. Add to the pool, and write the index.
+ final int index = mStrings.size();
+ mIndexes.put(s, index);
+ mStrings.add(s);
+
+ if (DEBUG) {
+ Log.i(TAG, "New '" + s + "' at " + index);
+ }
+
+ p.writeInt(index);
+ }
+ }
+
+ @Override
+ public void writeString8(Parcel p, String s) {
+ writeString(p, s);
+ }
+
+ @Override
+ public void writeString16(Parcel p, String s) {
+ writeString(p, s);
+ }
+
+ /**
+ * Closes a parcel by appending the string pool at the end and updating the pool offset,
+ * which it assumes is at the first byte. It also uninstalls itself as a read-write helper.
+ */
+ public void finishAndUninstall() {
+ // Uninstall first, so that writeStringList() uses the native writeString.
+ mParcel.setReadWriteHelper(null);
+
+ final int poolPosition = mParcel.dataPosition();
+ mParcel.writeStringList(mStrings);
+
+ mParcel.setDataPosition(mStartPos);
+ mParcel.writeInt(poolPosition);
+
+ // Move back to the end.
+ mParcel.setDataPosition(mParcel.dataSize());
+ if (DEBUG) {
+ Log.i(TAG, "Wrote " + mStrings.size() + " strings");
+ }
+ }
+ }
+}
diff --git a/android/content/pm/PackagePartitions.java b/android/content/pm/PackagePartitions.java
new file mode 100644
index 0000000..d157768
--- /dev/null
+++ b/android/content/pm/PackagePartitions.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2020 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Environment;
+import android.os.FileUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Function;
+
+/**
+ * Exposes {@link #SYSTEM_PARTITIONS} which represents the partitions in which application packages
+ * can be installed. The partitions are ordered from most generic (lowest priority) to most specific
+ * (greatest priority).
+ *
+ * @hide
+ **/
+public class PackagePartitions {
+ public static final int PARTITION_SYSTEM = 0;
+ public static final int PARTITION_VENDOR = 1;
+ public static final int PARTITION_ODM = 2;
+ public static final int PARTITION_OEM = 3;
+ public static final int PARTITION_PRODUCT = 4;
+ public static final int PARTITION_SYSTEM_EXT = 5;
+
+ @IntDef(prefix = { "PARTITION_" }, value = {
+ PARTITION_SYSTEM,
+ PARTITION_VENDOR,
+ PARTITION_ODM,
+ PARTITION_OEM,
+ PARTITION_PRODUCT,
+ PARTITION_SYSTEM_EXT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PartitionType {}
+
+ /**
+ * The list of all system partitions that may contain packages in ascending order of
+ * specificity (the more generic, the earlier in the list a partition appears).
+ */
+ private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS =
+ new ArrayList<>(Arrays.asList(
+ new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM,
+ true /* containsPrivApp */, false /* containsOverlay */),
+ new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR,
+ true /* containsPrivApp */, true /* containsOverlay */),
+ new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM,
+ true /* containsPrivApp */, true /* containsOverlay */),
+ new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM,
+ false /* containsPrivApp */, true /* containsOverlay */),
+ new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT,
+ true /* containsPrivApp */, true /* containsOverlay */),
+ new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT,
+ true /* containsPrivApp */, true /* containsOverlay */)));
+
+ /**
+ * Returns a list in which the elements are products of the specified function applied to the
+ * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order.
+ */
+ public static <T> ArrayList<T> getOrderedPartitions(
+ @NonNull Function<SystemPartition, T> producer) {
+ final ArrayList<T> out = new ArrayList<>();
+ for (int i = 0, n = SYSTEM_PARTITIONS.size(); i < n; i++) {
+ final T v = producer.apply(SYSTEM_PARTITIONS.get(i));
+ if (v != null) {
+ out.add(v);
+ }
+ }
+ return out;
+ }
+
+ private static File canonicalize(File path) {
+ try {
+ return path.getCanonicalFile();
+ } catch (IOException e) {
+ return path;
+ }
+ }
+
+ /** Represents a partition that contains application packages. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static class SystemPartition {
+ @PartitionType
+ public final int type;
+
+ @NonNull
+ private final DeferredCanonicalFile mFolder;
+
+ @Nullable
+ private final DeferredCanonicalFile mAppFolder;
+
+ @Nullable
+ private final DeferredCanonicalFile mPrivAppFolder;
+
+ @Nullable
+ private final DeferredCanonicalFile mOverlayFolder;
+
+ @NonNull
+ private final File mNonConicalFolder;
+
+ private SystemPartition(@NonNull File folder, @PartitionType int type,
+ boolean containsPrivApp, boolean containsOverlay) {
+ this.type = type;
+ this.mFolder = new DeferredCanonicalFile(folder);
+ this.mAppFolder = new DeferredCanonicalFile(folder, "app");
+ this.mPrivAppFolder = containsPrivApp ? new DeferredCanonicalFile(folder, "priv-app")
+ : null;
+ this.mOverlayFolder = containsOverlay ? new DeferredCanonicalFile(folder, "overlay")
+ : null;
+ this.mNonConicalFolder = folder;
+ }
+
+ public SystemPartition(@NonNull SystemPartition original) {
+ this.type = original.type;
+ this.mFolder = new DeferredCanonicalFile(original.mFolder.getFile());
+ this.mAppFolder = original.mAppFolder;
+ this.mPrivAppFolder = original.mPrivAppFolder;
+ this.mOverlayFolder = original.mOverlayFolder;
+ this.mNonConicalFolder = original.mNonConicalFolder;
+ }
+
+ /**
+ * Creates a partition containing the same folders as the original partition but with a
+ * different root folder.
+ */
+ public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) {
+ this(rootFolder, partition.type, partition.mPrivAppFolder != null,
+ partition.mOverlayFolder != null);
+ }
+
+ /** Returns the canonical folder of the partition. */
+ @NonNull
+ public File getFolder() {
+ return mFolder.getFile();
+ }
+
+ /** Returns the non-canonical folder of the partition. */
+ @NonNull
+ public File getNonConicalFolder() {
+ return mNonConicalFolder;
+ }
+
+ /** Returns the canonical app folder of the partition. */
+ @Nullable
+ public File getAppFolder() {
+ return mAppFolder == null ? null : mAppFolder.getFile();
+ }
+
+ /** Returns the canonical priv-app folder of the partition, if one exists. */
+ @Nullable
+ public File getPrivAppFolder() {
+ return mPrivAppFolder == null ? null : mPrivAppFolder.getFile();
+ }
+
+ /** Returns the canonical overlay folder of the partition, if one exists. */
+ @Nullable
+ public File getOverlayFolder() {
+ return mOverlayFolder == null ? null : mOverlayFolder.getFile();
+ }
+
+ /** Returns whether the partition contains the specified file. */
+ public boolean containsPath(@NonNull String path) {
+ return containsFile(new File(path));
+ }
+
+ /** Returns whether the partition contains the specified file. */
+ public boolean containsFile(@NonNull File file) {
+ return FileUtils.contains(mFolder.getFile(), canonicalize(file));
+ }
+
+ /** Returns whether the partition contains the specified file in its priv-app folder. */
+ public boolean containsPrivApp(@NonNull File scanFile) {
+ return mPrivAppFolder != null
+ && FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile));
+ }
+
+ /** Returns whether the partition contains the specified file in its app folder. */
+ public boolean containsApp(@NonNull File scanFile) {
+ return mAppFolder != null
+ && FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile));
+ }
+
+ /** Returns whether the partition contains the specified file in its overlay folder. */
+ public boolean containsOverlay(@NonNull File scanFile) {
+ return mOverlayFolder != null
+ && FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile));
+ }
+ }
+
+ /**
+ * A class that defers the canonicalization of its underlying file. This must be done so
+ * processes do not attempt to canonicalize files in directories for which the process does not
+ * have the correct selinux policies.
+ */
+ private static class DeferredCanonicalFile {
+ private boolean mIsCanonical = false;
+
+ @NonNull
+ private File mFile;
+
+ private DeferredCanonicalFile(@NonNull File dir) {
+ mFile = dir;
+ }
+
+ private DeferredCanonicalFile(@NonNull File dir, @NonNull String fileName) {
+ mFile = new File(dir, fileName);
+ }
+
+ @NonNull
+ private File getFile() {
+ if (!mIsCanonical) {
+ mFile = canonicalize(mFile);
+ mIsCanonical = true;
+ }
+ return mFile;
+ }
+ }
+}
diff --git a/android/content/pm/PackageSettingsSnapshotProvider.java b/android/content/pm/PackageSettingsSnapshotProvider.java
new file mode 100644
index 0000000..b9130d7
--- /dev/null
+++ b/android/content/pm/PackageSettingsSnapshotProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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 com.android.internal.util.FunctionalUtils;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSetting;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/** @hide */
+public interface PackageSettingsSnapshotProvider {
+
+ /**
+ * Run a function block that requires access to {@link PackageSetting} data. This will
+ * ensure the {@link PackageManagerService} lock is taken before any caller's internal lock
+ * to avoid deadlock. Note that this method may or may not lock. If a snapshot is available
+ * and valid, it will iterate the snapshot set of data.
+ */
+ void withPackageSettingsSnapshot(
+ @NonNull Consumer<Function<String, PackageSetting>> block);
+
+ /**
+ * Variant which returns a value to the caller.
+ * @see #withPackageSettingsSnapshot(Consumer)
+ */
+ <Output> Output withPackageSettingsSnapshotReturning(
+ @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
+ block);
+
+ /**
+ * Variant which throws.
+ * @see #withPackageSettingsSnapshot(Consumer)
+ */
+ <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing(
+ @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, PackageSetting>,
+ ExceptionType> block) throws ExceptionType;
+
+ /**
+ * Variant which throws 2 exceptions.
+ * @see #withPackageSettingsSnapshot(Consumer)
+ */
+ <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
+ withPackageSettingsSnapshotThrowing2(
+ @NonNull FunctionalUtils.ThrowingChecked2Consumer<
+ Function<String, PackageSetting>, ExceptionOne, ExceptionTwo> block)
+ throws ExceptionOne, ExceptionTwo;
+
+ /**
+ * Variant which returns a value to the caller and throws.
+ * @see #withPackageSettingsSnapshot(Consumer)
+ */
+ <Output, ExceptionType extends Exception> Output
+ withPackageSettingsSnapshotReturningThrowing(
+ @NonNull FunctionalUtils.ThrowingCheckedFunction<
+ Function<String, PackageSetting>, Output, ExceptionType> block)
+ throws ExceptionType;
+}
diff --git a/android/content/pm/PackageStats.java b/android/content/pm/PackageStats.java
new file mode 100644
index 0000000..81cfc07
--- /dev/null
+++ b/android/content/pm/PackageStats.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2008 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.Nullable;
+import android.app.usage.StorageStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * implementation of PackageStats associated with a application package.
+ *
+ * @deprecated this class is an orphan that could never be obtained from a valid
+ * public API. If you need package storage statistics use the new
+ * {@link StorageStatsManager} APIs.
+ */
+@Deprecated
+public class PackageStats implements Parcelable {
+ /** Name of the package to which this stats applies. */
+ public String packageName;
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public int userHandle;
+
+ /** Size of the code (e.g., APK) */
+ public long codeSize;
+
+ /**
+ * Size of the internal data size for the application. (e.g.,
+ * /data/data/<app>)
+ */
+ public long dataSize;
+
+ /** Size of cache used by the application. (e.g., /data/data/<app>/cache) */
+ public long cacheSize;
+
+ /**
+ * Size of the secure container on external storage holding the
+ * application's code.
+ */
+ public long externalCodeSize;
+
+ /**
+ * Size of the external data used by the application (e.g.,
+ * <sdcard>/Android/data/<app>)
+ */
+ public long externalDataSize;
+
+ /**
+ * Size of the external cache used by the application (i.e., on the SD
+ * card). If this is a subdirectory of the data directory, this size will be
+ * subtracted out of the external data size.
+ */
+ public long externalCacheSize;
+
+ /** Size of the external media size used by the application. */
+ public long externalMediaSize;
+
+ /** Size of the package's OBBs placed on external media. */
+ public long externalObbSize;
+
+ public static final @android.annotation.NonNull Parcelable.Creator<PackageStats> CREATOR
+ = new Parcelable.Creator<PackageStats>() {
+ public PackageStats createFromParcel(Parcel in) {
+ return new PackageStats(in);
+ }
+
+ public PackageStats[] newArray(int size) {
+ return new PackageStats[size];
+ }
+ };
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("PackageStats{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" ");
+ sb.append(packageName);
+ if (codeSize != 0) {
+ sb.append(" code=");
+ sb.append(codeSize);
+ }
+ if (dataSize != 0) {
+ sb.append(" data=");
+ sb.append(dataSize);
+ }
+ if (cacheSize != 0) {
+ sb.append(" cache=");
+ sb.append(cacheSize);
+ }
+ if (externalCodeSize != 0) {
+ sb.append(" extCode=");
+ sb.append(externalCodeSize);
+ }
+ if (externalDataSize != 0) {
+ sb.append(" extData=");
+ sb.append(externalDataSize);
+ }
+ if (externalCacheSize != 0) {
+ sb.append(" extCache=");
+ sb.append(externalCacheSize);
+ }
+ if (externalMediaSize != 0) {
+ sb.append(" media=");
+ sb.append(externalMediaSize);
+ }
+ if (externalObbSize != 0) {
+ sb.append(" obb=");
+ sb.append(externalObbSize);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ public PackageStats(String pkgName) {
+ packageName = pkgName;
+ userHandle = UserHandle.myUserId();
+ }
+
+ /** @hide */
+ public PackageStats(String pkgName, int userHandle) {
+ this.packageName = pkgName;
+ this.userHandle = userHandle;
+ }
+
+ public PackageStats(Parcel source) {
+ packageName = source.readString();
+ userHandle = source.readInt();
+ codeSize = source.readLong();
+ dataSize = source.readLong();
+ cacheSize = source.readLong();
+ externalCodeSize = source.readLong();
+ externalDataSize = source.readLong();
+ externalCacheSize = source.readLong();
+ externalMediaSize = source.readLong();
+ externalObbSize = source.readLong();
+ }
+
+ public PackageStats(PackageStats pStats) {
+ packageName = pStats.packageName;
+ userHandle = pStats.userHandle;
+ codeSize = pStats.codeSize;
+ dataSize = pStats.dataSize;
+ cacheSize = pStats.cacheSize;
+ externalCodeSize = pStats.externalCodeSize;
+ externalDataSize = pStats.externalDataSize;
+ externalCacheSize = pStats.externalCacheSize;
+ externalMediaSize = pStats.externalMediaSize;
+ externalObbSize = pStats.externalObbSize;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags){
+ dest.writeString(packageName);
+ dest.writeInt(userHandle);
+ dest.writeLong(codeSize);
+ dest.writeLong(dataSize);
+ dest.writeLong(cacheSize);
+ dest.writeLong(externalCodeSize);
+ dest.writeLong(externalDataSize);
+ dest.writeLong(externalCacheSize);
+ dest.writeLong(externalMediaSize);
+ dest.writeLong(externalObbSize);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof PackageStats)) {
+ return false;
+ }
+
+ final PackageStats otherStats = (PackageStats) obj;
+ return ((TextUtils.equals(packageName, otherStats.packageName))
+ && userHandle == otherStats.userHandle
+ && codeSize == otherStats.codeSize
+ && dataSize == otherStats.dataSize
+ && cacheSize == otherStats.cacheSize
+ && externalCodeSize == otherStats.externalCodeSize
+ && externalDataSize == otherStats.externalDataSize
+ && externalCacheSize == otherStats.externalCacheSize
+ && externalMediaSize == otherStats.externalMediaSize
+ && externalObbSize == otherStats.externalObbSize);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packageName, userHandle, codeSize, dataSize,
+ cacheSize, externalCodeSize, externalDataSize, externalCacheSize, externalMediaSize,
+ externalObbSize);
+ }
+
+}
diff --git a/android/content/pm/PackageUserState.java b/android/content/pm/PackageUserState.java
new file mode 100644
index 0000000..84317b3
--- /dev/null
+++ b/android/content/pm/PackageUserState.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2012 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.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageRead;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.os.BaseBundle;
+import android.os.Debug;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Per-user state information about a package.
+ * @hide
+ */
+public class PackageUserState {
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "PackageUserState";
+
+ public long ceDataInode;
+ public boolean installed;
+ public boolean stopped;
+ public boolean notLaunched;
+ public boolean hidden; // Is the app restricted by owner / admin
+ public int distractionFlags;
+ public boolean suspended;
+ public ArrayMap<String, SuspendParams> suspendParams; // Suspending package to suspend params
+ public boolean instantApp;
+ public boolean virtualPreload;
+ public int enabled;
+ public String lastDisableAppCaller;
+ public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
+ public int installReason;
+ public @PackageManager.UninstallReason int uninstallReason;
+ public String harmfulAppWarning;
+ public String splashScreenTheme;
+
+ public ArraySet<String> disabledComponents;
+ public ArraySet<String> enabledComponents;
+
+ private OverlayPaths overlayPaths;
+ // Maps library name to overlay paths.
+ private ArrayMap<String, OverlayPaths> sharedLibraryOverlayPaths;
+ private OverlayPaths cachedOverlayPaths;
+
+ @Nullable
+ private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap;
+
+ @UnsupportedAppUsage
+ public PackageUserState() {
+ installed = true;
+ hidden = false;
+ suspended = false;
+ enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+ installReason = PackageManager.INSTALL_REASON_UNKNOWN;
+ uninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;
+ }
+
+ @VisibleForTesting
+ public PackageUserState(PackageUserState o) {
+ ceDataInode = o.ceDataInode;
+ installed = o.installed;
+ stopped = o.stopped;
+ notLaunched = o.notLaunched;
+ hidden = o.hidden;
+ distractionFlags = o.distractionFlags;
+ suspended = o.suspended;
+ suspendParams = new ArrayMap<>(o.suspendParams);
+ instantApp = o.instantApp;
+ virtualPreload = o.virtualPreload;
+ enabled = o.enabled;
+ lastDisableAppCaller = o.lastDisableAppCaller;
+ categoryHint = o.categoryHint;
+ installReason = o.installReason;
+ uninstallReason = o.uninstallReason;
+ disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
+ enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
+ overlayPaths = o.overlayPaths;
+ if (o.sharedLibraryOverlayPaths != null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+ }
+ harmfulAppWarning = o.harmfulAppWarning;
+ if (o.componentLabelIconOverrideMap != null) {
+ this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
+ }
+ splashScreenTheme = o.splashScreenTheme;
+ }
+
+ @Nullable
+ public OverlayPaths getOverlayPaths() {
+ return overlayPaths;
+ }
+
+ @Nullable
+ public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
+ return sharedLibraryOverlayPaths;
+ }
+
+ /**
+ * Sets the path of overlays currently enabled for this package and user combination.
+ * @return true if the path contents differ than what they were previously
+ */
+ @Nullable
+ public boolean setOverlayPaths(@Nullable OverlayPaths paths) {
+ if (Objects.equals(paths, overlayPaths)) {
+ return false;
+ }
+ if ((overlayPaths == null && paths.isEmpty())
+ || (paths == null && overlayPaths.isEmpty())) {
+ return false;
+ }
+ overlayPaths = paths;
+ cachedOverlayPaths = null;
+ return true;
+ }
+
+ /**
+ * Sets the path of overlays currently enabled for a library that this package uses.
+ *
+ * @return true if the path contents for the library differ than what they were previously
+ */
+ public boolean setSharedLibraryOverlayPaths(@NonNull String library,
+ @Nullable OverlayPaths paths) {
+ if (sharedLibraryOverlayPaths == null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>();
+ }
+ final OverlayPaths currentPaths = sharedLibraryOverlayPaths.get(library);
+ if (Objects.equals(paths, currentPaths)) {
+ return false;
+ }
+ cachedOverlayPaths = null;
+ if (paths == null || paths.isEmpty()) {
+ return sharedLibraryOverlayPaths.remove(library) != null;
+ } else {
+ sharedLibraryOverlayPaths.put(library, paths);
+ return true;
+ }
+ }
+
+ /**
+ * Overrides the non-localized label and icon of a component.
+ *
+ * @return true if the label or icon was changed.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean overrideLabelAndIcon(@NonNull ComponentName component,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
+ String existingLabel = null;
+ Integer existingIcon = null;
+
+ if (componentLabelIconOverrideMap != null) {
+ Pair<String, Integer> pair = componentLabelIconOverrideMap.get(component);
+ if (pair != null) {
+ existingLabel = pair.first;
+ existingIcon = pair.second;
+ }
+ }
+
+ boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel)
+ || !Objects.equals(existingIcon, icon);
+
+ if (changed) {
+ if (nonLocalizedLabel == null && icon == null) {
+ componentLabelIconOverrideMap.remove(component);
+ if (componentLabelIconOverrideMap.isEmpty()) {
+ componentLabelIconOverrideMap = null;
+ }
+ } else {
+ if (componentLabelIconOverrideMap == null) {
+ componentLabelIconOverrideMap = new ArrayMap<>(1);
+ }
+
+ componentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon));
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName,
+ * String, Integer)}.
+ *
+ * This is done when the package is updated as the components and resource IDs may have changed.
+ */
+ public void resetOverrideComponentLabelIcon() {
+ componentLabelIconOverrideMap = null;
+ }
+
+ @Nullable
+ public Pair<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) {
+ if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) {
+ return null;
+ }
+
+ return componentLabelIconOverrideMap.get(componentName);
+ }
+
+
+ /**
+ * Test if this package is installed.
+ */
+ public boolean isAvailable(int flags) {
+ // True if it is installed for this user and it is not hidden. If it is hidden,
+ // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES
+ final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0;
+ final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
+ return matchAnyUser
+ || (this.installed
+ && (!this.hidden || matchUninstalled));
+ }
+
+ public boolean isMatch(ComponentInfo componentInfo, int flags) {
+ return isMatch(componentInfo.applicationInfo.isSystemApp(),
+ componentInfo.applicationInfo.enabled, componentInfo.enabled,
+ componentInfo.directBootAware, componentInfo.name, flags);
+ }
+
+ public boolean isMatch(boolean isSystem, boolean isPackageEnabled,
+ ParsedMainComponent component, int flags) {
+ return isMatch(isSystem, isPackageEnabled, component.isEnabled(),
+ component.isDirectBootAware(), component.getName(), flags);
+ }
+
+ /**
+ * Test if the given component is considered installed, enabled and a match
+ * for the given flags.
+ *
+ * <p>
+ * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and
+ * {@link PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
+ * </p>
+ *
+ */
+ public boolean isMatch(boolean isSystem, boolean isPackageEnabled, boolean isComponentEnabled,
+ boolean isComponentDirectBootAware, String componentName, int flags) {
+ final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
+ if (!isAvailable(flags) && !(isSystem && matchUninstalled)) {
+ return reportIfDebug(false, flags);
+ }
+
+ if (!isEnabled(isPackageEnabled, isComponentEnabled, componentName, flags)) {
+ return reportIfDebug(false, flags);
+ }
+
+ if ((flags & MATCH_SYSTEM_ONLY) != 0) {
+ if (!isSystem) {
+ return reportIfDebug(false, flags);
+ }
+ }
+
+ final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
+ && !isComponentDirectBootAware;
+ final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
+ && isComponentDirectBootAware;
+ return reportIfDebug(matchesUnaware || matchesAware, flags);
+ }
+
+ public boolean reportIfDebug(boolean result, int flags) {
+ if (DEBUG && !result) {
+ Slog.i(LOG_TAG, "No match!; flags: "
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+ + Debug.getCaller());
+ }
+ return result;
+ }
+
+ public boolean isPackageEnabled(@NonNull ParsingPackageRead pkg) {
+ switch (this.enabled) {
+ case COMPONENT_ENABLED_STATE_ENABLED:
+ return true;
+ case COMPONENT_ENABLED_STATE_DISABLED:
+ case COMPONENT_ENABLED_STATE_DISABLED_USER:
+ case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ return false;
+ default:
+ case COMPONENT_ENABLED_STATE_DEFAULT:
+ return pkg.isEnabled();
+ }
+ }
+
+ public boolean isEnabled(ComponentInfo componentInfo, int flags) {
+ return isEnabled(componentInfo.applicationInfo.enabled, componentInfo.enabled,
+ componentInfo.name, flags);
+ }
+
+ public boolean isEnabled(boolean isPackageEnabled,
+ ParsedMainComponent parsedComponent, int flags) {
+ return isEnabled(isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(),
+ flags);
+ }
+
+ /**
+ * Test if the given component is considered enabled.
+ */
+ public boolean isEnabled(boolean isPackageEnabled, boolean isComponentEnabled,
+ String componentName, int flags) {
+ if ((flags & MATCH_DISABLED_COMPONENTS) != 0) {
+ return true;
+ }
+
+ // First check if the overall package is disabled; if the package is
+ // enabled then fall through to check specific component
+ switch (this.enabled) {
+ case COMPONENT_ENABLED_STATE_DISABLED:
+ case COMPONENT_ENABLED_STATE_DISABLED_USER:
+ return false;
+ case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) == 0) {
+ return false;
+ }
+ // fallthrough
+ case COMPONENT_ENABLED_STATE_DEFAULT:
+ if (!isPackageEnabled) {
+ return false;
+ }
+ // fallthrough
+ case COMPONENT_ENABLED_STATE_ENABLED:
+ break;
+ }
+
+ // Check if component has explicit state before falling through to
+ // the manifest default
+ if (ArrayUtils.contains(this.enabledComponents, componentName)) {
+ return true;
+ }
+ if (ArrayUtils.contains(this.disabledComponents, componentName)) {
+ return false;
+ }
+
+ return isComponentEnabled;
+ }
+
+ public OverlayPaths getAllOverlayPaths() {
+ if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+ return null;
+ }
+ if (cachedOverlayPaths != null) {
+ return cachedOverlayPaths;
+ }
+ final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
+ newPaths.addAll(overlayPaths);
+ if (sharedLibraryOverlayPaths != null) {
+ for (final OverlayPaths libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+ newPaths.addAll(libOverlayPaths);
+ }
+ }
+ cachedOverlayPaths = newPaths.build();
+ return cachedOverlayPaths;
+ }
+
+ @Override
+ final public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof PackageUserState)) {
+ return false;
+ }
+ final PackageUserState oldState = (PackageUserState) obj;
+ if (ceDataInode != oldState.ceDataInode) {
+ return false;
+ }
+ if (installed != oldState.installed) {
+ return false;
+ }
+ if (stopped != oldState.stopped) {
+ return false;
+ }
+ if (notLaunched != oldState.notLaunched) {
+ return false;
+ }
+ if (hidden != oldState.hidden) {
+ return false;
+ }
+ if (distractionFlags != oldState.distractionFlags) {
+ return false;
+ }
+ if (suspended != oldState.suspended) {
+ return false;
+ }
+ if (suspended) {
+ if (!Objects.equals(suspendParams, oldState.suspendParams)) {
+ return false;
+ }
+ }
+ if (instantApp != oldState.instantApp) {
+ return false;
+ }
+ if (virtualPreload != oldState.virtualPreload) {
+ return false;
+ }
+ if (enabled != oldState.enabled) {
+ return false;
+ }
+ if ((lastDisableAppCaller == null && oldState.lastDisableAppCaller != null)
+ || (lastDisableAppCaller != null
+ && !lastDisableAppCaller.equals(oldState.lastDisableAppCaller))) {
+ return false;
+ }
+ if (categoryHint != oldState.categoryHint) {
+ return false;
+ }
+ if (installReason != oldState.installReason) {
+ return false;
+ }
+ if (uninstallReason != oldState.uninstallReason) {
+ return false;
+ }
+ if ((disabledComponents == null && oldState.disabledComponents != null)
+ || (disabledComponents != null && oldState.disabledComponents == null)) {
+ return false;
+ }
+ if (disabledComponents != null) {
+ if (disabledComponents.size() != oldState.disabledComponents.size()) {
+ return false;
+ }
+ for (int i = disabledComponents.size() - 1; i >=0; --i) {
+ if (!oldState.disabledComponents.contains(disabledComponents.valueAt(i))) {
+ return false;
+ }
+ }
+ }
+ if ((enabledComponents == null && oldState.enabledComponents != null)
+ || (enabledComponents != null && oldState.enabledComponents == null)) {
+ return false;
+ }
+ if (enabledComponents != null) {
+ if (enabledComponents.size() != oldState.enabledComponents.size()) {
+ return false;
+ }
+ for (int i = enabledComponents.size() - 1; i >=0; --i) {
+ if (!oldState.enabledComponents.contains(enabledComponents.valueAt(i))) {
+ return false;
+ }
+ }
+ }
+ if (harmfulAppWarning == null && oldState.harmfulAppWarning != null
+ || (harmfulAppWarning != null
+ && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+ return false;
+ }
+
+ if (!Objects.equals(splashScreenTheme, oldState.splashScreenTheme)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = Long.hashCode(ceDataInode);
+ hashCode = 31 * hashCode + Boolean.hashCode(installed);
+ hashCode = 31 * hashCode + Boolean.hashCode(stopped);
+ hashCode = 31 * hashCode + Boolean.hashCode(notLaunched);
+ hashCode = 31 * hashCode + Boolean.hashCode(hidden);
+ hashCode = 31 * hashCode + distractionFlags;
+ hashCode = 31 * hashCode + Boolean.hashCode(suspended);
+ hashCode = 31 * hashCode + Objects.hashCode(suspendParams);
+ hashCode = 31 * hashCode + Boolean.hashCode(instantApp);
+ hashCode = 31 * hashCode + Boolean.hashCode(virtualPreload);
+ hashCode = 31 * hashCode + enabled;
+ hashCode = 31 * hashCode + Objects.hashCode(lastDisableAppCaller);
+ hashCode = 31 * hashCode + categoryHint;
+ hashCode = 31 * hashCode + installReason;
+ hashCode = 31 * hashCode + uninstallReason;
+ hashCode = 31 * hashCode + Objects.hashCode(disabledComponents);
+ hashCode = 31 * hashCode + Objects.hashCode(enabledComponents);
+ hashCode = 31 * hashCode + Objects.hashCode(harmfulAppWarning);
+ hashCode = 31 * hashCode + Objects.hashCode(splashScreenTheme);
+ return hashCode;
+ }
+
+ /**
+ * Container to describe suspension parameters.
+ */
+ public static final class SuspendParams {
+ private static final String TAG_DIALOG_INFO = "dialog-info";
+ private static final String TAG_APP_EXTRAS = "app-extras";
+ private static final String TAG_LAUNCHER_EXTRAS = "launcher-extras";
+
+ public SuspendDialogInfo dialogInfo;
+ public PersistableBundle appExtras;
+ public PersistableBundle launcherExtras;
+
+ private SuspendParams() {
+ }
+
+ /**
+ * Returns a {@link SuspendParams} object with the given fields. Returns {@code null} if all
+ * the fields are {@code null}.
+ *
+ * @param dialogInfo
+ * @param appExtras
+ * @param launcherExtras
+ * @return A {@link SuspendParams} object or {@code null}.
+ */
+ public static SuspendParams getInstanceOrNull(SuspendDialogInfo dialogInfo,
+ PersistableBundle appExtras, PersistableBundle launcherExtras) {
+ if (dialogInfo == null && appExtras == null && launcherExtras == null) {
+ return null;
+ }
+ final SuspendParams instance = new SuspendParams();
+ instance.dialogInfo = dialogInfo;
+ instance.appExtras = appExtras;
+ instance.launcherExtras = launcherExtras;
+ return instance;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SuspendParams)) {
+ return false;
+ }
+ final SuspendParams other = (SuspendParams) obj;
+ if (!Objects.equals(dialogInfo, other.dialogInfo)) {
+ return false;
+ }
+ if (!BaseBundle.kindofEquals(appExtras, other.appExtras)) {
+ return false;
+ }
+ if (!BaseBundle.kindofEquals(launcherExtras, other.launcherExtras)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = Objects.hashCode(dialogInfo);
+ hashCode = 31 * hashCode + ((appExtras != null) ? appExtras.size() : 0);
+ hashCode = 31 * hashCode + ((launcherExtras != null) ? launcherExtras.size() : 0);
+ return hashCode;
+ }
+
+ /**
+ * Serializes this object into an xml format
+ * @param out the {@link XmlSerializer} object
+ * @throws IOException
+ */
+ public void saveToXml(TypedXmlSerializer out) throws IOException {
+ if (dialogInfo != null) {
+ out.startTag(null, TAG_DIALOG_INFO);
+ dialogInfo.saveToXml(out);
+ out.endTag(null, TAG_DIALOG_INFO);
+ }
+ if (appExtras != null) {
+ out.startTag(null, TAG_APP_EXTRAS);
+ try {
+ appExtras.saveToXml(out);
+ } catch (XmlPullParserException e) {
+ Slog.e(LOG_TAG, "Exception while trying to write appExtras."
+ + " Will be lost on reboot", e);
+ }
+ out.endTag(null, TAG_APP_EXTRAS);
+ }
+ if (launcherExtras != null) {
+ out.startTag(null, TAG_LAUNCHER_EXTRAS);
+ try {
+ launcherExtras.saveToXml(out);
+ } catch (XmlPullParserException e) {
+ Slog.e(LOG_TAG, "Exception while trying to write launcherExtras."
+ + " Will be lost on reboot", e);
+ }
+ out.endTag(null, TAG_LAUNCHER_EXTRAS);
+ }
+ }
+
+ /**
+ * Parses this object from the xml format. Returns {@code null} if no object related
+ * information could be read.
+ * @param in the reader
+ * @return
+ */
+ public static SuspendParams restoreFromXml(TypedXmlPullParser in) throws IOException {
+ SuspendDialogInfo readDialogInfo = null;
+ PersistableBundle readAppExtras = null;
+ PersistableBundle readLauncherExtras = null;
+
+ final int currentDepth = in.getDepth();
+ int type;
+ try {
+ while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || in.getDepth() > currentDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ switch (in.getName()) {
+ case TAG_DIALOG_INFO:
+ readDialogInfo = SuspendDialogInfo.restoreFromXml(in);
+ break;
+ case TAG_APP_EXTRAS:
+ readAppExtras = PersistableBundle.restoreFromXml(in);
+ break;
+ case TAG_LAUNCHER_EXTRAS:
+ readLauncherExtras = PersistableBundle.restoreFromXml(in);
+ break;
+ default:
+ Slog.w(LOG_TAG, "Unknown tag " + in.getName()
+ + " in SuspendParams. Ignoring");
+ break;
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Slog.e(LOG_TAG, "Exception while trying to parse SuspendParams,"
+ + " some fields may default", e);
+ }
+ return getInstanceOrNull(readDialogInfo, readAppExtras, readLauncherExtras);
+ }
+ }
+}
diff --git a/android/content/pm/ParceledListSlice.java b/android/content/pm/ParceledListSlice.java
new file mode 100644
index 0000000..b54bc5d
--- /dev/null
+++ b/android/content/pm/ParceledListSlice.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * @see BaseParceledListSlice
+ *
+ * @hide
+ */
+public class ParceledListSlice<T extends Parcelable> extends BaseParceledListSlice<T> {
+ @UnsupportedAppUsage
+ public ParceledListSlice(List<T> list) {
+ super(list);
+ }
+
+ private ParceledListSlice(Parcel in, ClassLoader loader) {
+ super(in, loader);
+ }
+
+ public static <T extends Parcelable> ParceledListSlice<T> emptyList() {
+ return new ParceledListSlice<T>(Collections.<T> emptyList());
+ }
+
+ @Override
+ public int describeContents() {
+ int contents = 0;
+ final List<T> list = getList();
+ for (int i=0; i<list.size(); i++) {
+ contents |= list.get(i).describeContents();
+ }
+ return contents;
+ }
+
+ @Override
+ protected void writeElement(T parcelable, Parcel dest, int callFlags) {
+ parcelable.writeToParcel(dest, callFlags);
+ }
+
+ @Override
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ protected void writeParcelableCreator(T parcelable, Parcel dest) {
+ dest.writeParcelableCreator((Parcelable) parcelable);
+ }
+
+ @Override
+ protected Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
+ return from.readParcelableCreator(loader);
+ }
+
+ @SuppressWarnings("unchecked")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
+ public ParceledListSlice createFromParcel(Parcel in) {
+ return new ParceledListSlice(in, null);
+ }
+
+ @Override
+ public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new ParceledListSlice(in, loader);
+ }
+
+ @Override
+ public ParceledListSlice[] newArray(int size) {
+ return new ParceledListSlice[size];
+ }
+ };
+}
diff --git a/android/content/pm/PathPermission.java b/android/content/pm/PathPermission.java
new file mode 100644
index 0000000..11c9a7d
--- /dev/null
+++ b/android/content/pm/PathPermission.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 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.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+
+/**
+ * Description of permissions needed to access a particular path
+ * in a {@link ProviderInfo}.
+ */
+public class PathPermission extends PatternMatcher {
+ private final String mReadPermission;
+ private final String mWritePermission;
+
+ public PathPermission(String pattern, int type, String readPermission,
+ String writePermission) {
+ super(pattern, type);
+ mReadPermission = readPermission;
+ mWritePermission = writePermission;
+ }
+
+ public String getReadPermission() {
+ return mReadPermission;
+ }
+
+ public String getWritePermission() {
+ return mWritePermission;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mReadPermission);
+ dest.writeString(mWritePermission);
+ }
+
+ public PathPermission(Parcel src) {
+ super(src);
+ mReadPermission = src.readString();
+ mWritePermission = src.readString();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<PathPermission> CREATOR
+ = new Parcelable.Creator<PathPermission>() {
+ public PathPermission createFromParcel(Parcel source) {
+ return new PathPermission(source);
+ }
+
+ public PathPermission[] newArray(int size) {
+ return new PathPermission[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/android/content/pm/PermissionGroupInfo.java b/android/content/pm/PermissionGroupInfo.java
new file mode 100644
index 0000000..e65e742
--- /dev/null
+++ b/android/content/pm/PermissionGroupInfo.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2008 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.res.Resources.ID_NULL;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.SystemApi;
+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
+ * group known to the system. This corresponds to information collected from the
+ * AndroidManifest.xml's <permission-group> tags.
+ */
+public class PermissionGroupInfo extends PackageItemInfo implements Parcelable {
+ /**
+ * A string resource identifier (in the package's resources) of this
+ * permission's description. From the "description" attribute or,
+ * if not set, 0.
+ */
+ public @StringRes int descriptionRes;
+
+ /**
+ * A string resource identifier (in the package's resources) used to request the permissions.
+ * From the "request" attribute or, if not set, 0.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @StringRes int requestRes;
+
+ /**
+ * A string resource identifier (in the package's resources) used as subtitle when requesting
+ * only access while in the foreground.
+ *
+ * From the "requestDetail" attribute or, if not set, {@link
+ * android.content.res.Resources#ID_NULL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public final @StringRes int requestDetailResourceId;
+
+ /**
+ * A string resource identifier (in the package's resources) used when requesting background
+ * access. Also used when requesting both foreground and background access.
+ *
+ * From the "backgroundRequest" attribute or, if not set, {@link
+ * android.content.res.Resources#ID_NULL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public final @StringRes int backgroundRequestResourceId;
+
+ /**
+ * A string resource identifier (in the package's resources) used as subtitle when requesting
+ * background access.
+ *
+ * From the "backgroundRequestDetail" attribute or, if not set, {@link
+ * android.content.res.Resources#ID_NULL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public final @StringRes int backgroundRequestDetailResourceId;
+
+ /**
+ * The description string provided in the AndroidManifest file, if any. You
+ * probably don't want to use this, since it will be null if the description
+ * is in a resource. You probably want
+ * {@link PermissionInfo#loadDescription} instead.
+ */
+ public @Nullable CharSequence nonLocalizedDescription;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>personalInfo</code>
+ * value of {@link android.R.attr#permissionGroupFlags}.
+ */
+ public static final int FLAG_PERSONAL_INFO = 1<<0;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_PERSONAL_INFO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /**
+ * Additional flags about this group as given by
+ * {@link android.R.attr#permissionGroupFlags}.
+ */
+ public @Flags int flags;
+
+ /**
+ * Prioritization of this group, for visually sorting with other groups.
+ */
+ public int priority;
+
+ /**
+ * @hide
+ */
+ public PermissionGroupInfo(@StringRes int requestDetailResourceId,
+ @StringRes int backgroundRequestResourceId,
+ @StringRes int backgroundRequestDetailResourceId) {
+ this.requestDetailResourceId = requestDetailResourceId;
+ this.backgroundRequestResourceId = backgroundRequestResourceId;
+ this.backgroundRequestDetailResourceId = backgroundRequestDetailResourceId;
+ }
+
+ /**
+ * @deprecated Should only be created by the system.
+ */
+ @Deprecated
+ public PermissionGroupInfo() {
+ this(ID_NULL, ID_NULL, ID_NULL);
+ }
+
+ /**
+ * @deprecated Should only be created by the system.
+ */
+ @Deprecated
+ public PermissionGroupInfo(@NonNull PermissionGroupInfo orig) {
+ super(orig);
+ descriptionRes = orig.descriptionRes;
+ requestRes = orig.requestRes;
+ requestDetailResourceId = orig.requestDetailResourceId;
+ backgroundRequestResourceId = orig.backgroundRequestResourceId;
+ backgroundRequestDetailResourceId = orig.backgroundRequestDetailResourceId;
+ nonLocalizedDescription = orig.nonLocalizedDescription;
+ flags = orig.flags;
+ priority = orig.priority;
+ }
+
+ /**
+ * Retrieve the textual description of this permission. This
+ * will call back on the given PackageManager to load the description from
+ * the application.
+ *
+ * @param pm A PackageManager from which the label can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a CharSequence containing the permission's description.
+ * If there is no description, null is returned.
+ */
+ public @Nullable CharSequence loadDescription(@NonNull PackageManager pm) {
+ if (nonLocalizedDescription != null) {
+ return nonLocalizedDescription;
+ }
+ if (descriptionRes != 0) {
+ CharSequence label = pm.getText(packageName, descriptionRes, null);
+ if (label != null) {
+ return label;
+ }
+ }
+ return null;
+ }
+
+ public String toString() {
+ return "PermissionGroupInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + " flgs=0x" + Integer.toHexString(flags) + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(requestRes);
+ dest.writeInt(requestDetailResourceId);
+ dest.writeInt(backgroundRequestResourceId);
+ dest.writeInt(backgroundRequestDetailResourceId);
+ TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ dest.writeInt(flags);
+ dest.writeInt(priority);
+ }
+
+ public static final @NonNull Creator<PermissionGroupInfo> CREATOR =
+ new Creator<PermissionGroupInfo>() {
+ public PermissionGroupInfo createFromParcel(Parcel source) {
+ return new PermissionGroupInfo(source);
+ }
+ public PermissionGroupInfo[] newArray(int size) {
+ return new PermissionGroupInfo[size];
+ }
+ };
+
+ private PermissionGroupInfo(Parcel source) {
+ super(source);
+ descriptionRes = source.readInt();
+ requestRes = source.readInt();
+ requestDetailResourceId = source.readInt();
+ backgroundRequestResourceId = source.readInt();
+ backgroundRequestDetailResourceId = source.readInt();
+ nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ flags = source.readInt();
+ priority = source.readInt();
+ }
+}
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
new file mode 100644
index 0000000..691c69c
--- /dev/null
+++ b/android/content/pm/PermissionInfo.java
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2008 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Information you can retrieve about a particular security permission
+ * known to the system. This corresponds to information collected from the
+ * AndroidManifest.xml's <permission> tags.
+ */
+public class PermissionInfo extends PackageItemInfo implements Parcelable {
+ /**
+ * A normal application value for {@link #protectionLevel}, corresponding
+ * to the <code>normal</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_NORMAL = 0;
+
+ /**
+ * Dangerous value for {@link #protectionLevel}, corresponding
+ * to the <code>dangerous</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_DANGEROUS = 1;
+
+ /**
+ * System-level value for {@link #protectionLevel}, corresponding
+ * to the <code>signature</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_SIGNATURE = 2;
+
+ /**
+ * @deprecated Use {@link #PROTECTION_SIGNATURE}|{@link #PROTECTION_FLAG_PRIVILEGED}
+ * instead.
+ */
+ @Deprecated
+ public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
+
+ /**
+ * System-level value for {@link #protectionLevel}, corresponding
+ * to the <code>internal</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_INTERNAL = 4;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "PROTECTION_" }, value = {
+ PROTECTION_NORMAL,
+ PROTECTION_DANGEROUS,
+ PROTECTION_SIGNATURE,
+ PROTECTION_SIGNATURE_OR_SYSTEM,
+ PROTECTION_INTERNAL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Protection {}
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>privileged</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_PRIVILEGED = 0x10;
+
+ /**
+ * @deprecated Old name for {@link #PROTECTION_FLAG_PRIVILEGED}, which
+ * is now very confusing because it only applies to privileged apps, not all
+ * apps on the system image.
+ */
+ @Deprecated
+ public static final int PROTECTION_FLAG_SYSTEM = 0x10;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>development</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_DEVELOPMENT = 0x20;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>appop</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_APPOP = 0x40;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>pre23</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_PRE23 = 0x80;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>installer</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_INSTALLER = 0x100;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>verifier</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_VERIFIER = 0x200;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>preinstalled</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_PREINSTALLED = 0x400;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>setup</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_SETUP = 0x800;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>instant</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_INSTANT = 0x1000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>runtime</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_RUNTIME_ONLY = 0x2000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>oem</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_OEM = 0x4000;
+
+ /**
+ * Additional flag for {${link #protectionLevel}, corresponding
+ * to the <code>vendorPrivileged</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @TestApi
+ 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
+ public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 0x10000;
+
+ /**
+ * Additional flag for {${link #protectionLevel}, corresponding
+ * to the <code>wellbeing</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @deprecated this protectionLevel is obsolete. Permissions previously granted through this
+ * protectionLevel have been migrated to use <code>role</code> instead
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_WELLBEING = 0x20000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding to the
+ * {@code documenter} value of {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_DOCUMENTER = 0x40000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding to the
+ * {@code configurator} value of {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_CONFIGURATOR = 0x80000;
+
+ /**
+ * Additional flag for {${link #protectionLevel}, corresponding
+ * to the <code>incident_report_approver</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 0x100000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>app_predictor</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_APP_PREDICTOR = 0x200000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>companion</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_COMPANION = 0x800000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>retailDemo</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_RETAIL_DEMO = 0x1000000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>recents</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_RECENTS = 0x2000000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding to the <code>role</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_ROLE = 0x4000000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
+ * of {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
+
+ /** @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,
+ PROTECTION_FLAG_DOCUMENTER,
+ PROTECTION_FLAG_CONFIGURATOR,
+ PROTECTION_FLAG_INCIDENT_REPORT_APPROVER,
+ PROTECTION_FLAG_APP_PREDICTOR,
+ PROTECTION_FLAG_COMPANION,
+ PROTECTION_FLAG_RETAIL_DEMO,
+ PROTECTION_FLAG_RECENTS,
+ PROTECTION_FLAG_ROLE,
+ PROTECTION_FLAG_KNOWN_SIGNER,
+ })
+ @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. Use the following functions
+ * to extract them.
+ *
+ * <pre>
+ * int basePermissionType = permissionInfo.getProtection();
+ * int permissionFlags = permissionInfo.getProtectionFlags();
+ * </pre>
+ *
+ * <p></p>Base permission types are {@link #PROTECTION_NORMAL},
+ * {@link #PROTECTION_DANGEROUS}, {@link #PROTECTION_SIGNATURE}, {@link #PROTECTION_INTERNAL}
+ * 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;
+
+ /**
+ * The group this permission is a part of, as per
+ * {@link android.R.attr#permissionGroup}.
+ */
+ public @Nullable String group;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>costsMoney</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ */
+ public static final int FLAG_COSTS_MONEY = 1<<0;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>removed</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_REMOVED = 1<<1;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>hardRestricted</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ *
+ * <p> This permission is restricted by the platform and it would be
+ * grantable only to apps that meet special criteria per platform
+ * policy.
+ */
+ public static final int FLAG_HARD_RESTRICTED = 1<<2;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>softRestricted</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ *
+ * <p>This permission is restricted by the platform and it would be
+ * grantable in its full form to apps that meet special criteria
+ * per platform policy. Otherwise, a weaker form of the permission
+ * would be granted. The weak grant depends on the permission.
+ */
+ public static final int FLAG_SOFT_RESTRICTED = 1<<3;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>immutablyRestricted</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ *
+ * <p>This permission is restricted immutably which means that its
+ * restriction state may be specified only on the first install of
+ * the app and will stay in this initial allowlist state until
+ * the app is uninstalled.
+ */
+ public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4;
+
+ /**
+ * Flag for {@link #flags}, indicating that this permission has been
+ * installed into the system's globally defined permissions.
+ */
+ public static final int FLAG_INSTALLED = 1<<30;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_COSTS_MONEY,
+ FLAG_REMOVED,
+ FLAG_HARD_RESTRICTED,
+ FLAG_SOFT_RESTRICTED,
+ FLAG_IMMUTABLY_RESTRICTED,
+ FLAG_INSTALLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /**
+ * Additional flags about this permission as given by
+ * {@link android.R.attr#permissionFlags}.
+ */
+ public @Flags int flags;
+
+ /**
+ * A string resource identifier (in the package's resources) of this
+ * permission's description. From the "description" attribute or,
+ * if not set, 0.
+ */
+ public @StringRes int descriptionRes;
+
+ /**
+ * A string resource identifier (in the package's resources) used to request the permissions.
+ * From the "request" attribute or, if not set, 0.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @StringRes int requestRes;
+
+ /**
+ * Some permissions only grant access while the app is in foreground. Some of these permissions
+ * allow to add background capabilities by adding another permission.
+ *
+ * If this is such a permission, this is the name of the permission adding the background
+ * access.
+ *
+ * From the "backgroundPermission" attribute or, if not set null
+ *
+ * @hide
+ */
+ @SystemApi
+ public final @Nullable String backgroundPermission;
+
+ /**
+ * The description string provided in the AndroidManifest file, if any. You
+ * probably don't want to use this, since it will be null if the description
+ * is in a resource. You probably want
+ * {@link PermissionInfo#loadDescription} instead.
+ */
+ public @Nullable CharSequence nonLocalizedDescription;
+
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
+ /**
+ * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
+ * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
+ * if the app is signed by any of these certificates.
+ *
+ * @hide
+ */
+ public @Nullable Set<String> knownCerts;
+
+ /** @hide */
+ public static int fixProtectionLevel(int level) {
+ if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
+ level = PROTECTION_SIGNATURE | PROTECTION_FLAG_PRIVILEGED;
+ }
+ if ((level & PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0
+ && (level & PROTECTION_FLAG_PRIVILEGED) == 0) {
+ // 'vendorPrivileged' must be 'privileged'. If not,
+ // drop the vendorPrivileged.
+ level = level & ~PROTECTION_FLAG_VENDOR_PRIVILEGED;
+ }
+ return level;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static @NonNull String protectionToString(int level) {
+ final StringBuilder protLevel = new StringBuilder();
+ switch (level & PROTECTION_MASK_BASE) {
+ case PermissionInfo.PROTECTION_DANGEROUS:
+ protLevel.append("dangerous");
+ break;
+ case PermissionInfo.PROTECTION_NORMAL:
+ protLevel.append("normal");
+ break;
+ case PermissionInfo.PROTECTION_SIGNATURE:
+ protLevel.append("signature");
+ break;
+ case PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM:
+ protLevel.append("signatureOrSystem");
+ break;
+ case PermissionInfo.PROTECTION_INTERNAL:
+ protLevel.append("internal");
+ break;
+ default:
+ protLevel.append("????");
+ break;
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+ protLevel.append("|privileged");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ protLevel.append("|development");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+ protLevel.append("|appop");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_PRE23) != 0) {
+ protLevel.append("|pre23");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0) {
+ protLevel.append("|installer");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0) {
+ protLevel.append("|verifier");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
+ protLevel.append("|preinstalled");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
+ protLevel.append("|setup");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
+ protLevel.append("|instant");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
+ protLevel.append("|runtime");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_OEM) != 0) {
+ protLevel.append("|oem");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0) {
+ protLevel.append("|vendorPrivileged");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0) {
+ protLevel.append("|textClassifier");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0) {
+ protLevel.append("|documenter");
+ }
+ if ((level & PROTECTION_FLAG_CONFIGURATOR) != 0) {
+ protLevel.append("|configurator");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) != 0) {
+ protLevel.append("|incidentReportApprover");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) {
+ protLevel.append("|appPredictor");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0) {
+ protLevel.append("|companion");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0) {
+ protLevel.append("|retailDemo");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_RECENTS) != 0) {
+ protLevel.append("|recents");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
+ protLevel.append("|role");
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
+ protLevel.append("|knownSigner");
+ }
+ return protLevel.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public PermissionInfo(@Nullable String backgroundPermission) {
+ this.backgroundPermission = backgroundPermission;
+ }
+
+ /**
+ * @deprecated Should only be created by the system.
+ */
+ @Deprecated
+ public PermissionInfo() {
+ this((String) null);
+ }
+
+ /**
+ * @deprecated Should only be created by the system.
+ */
+ @Deprecated
+ public PermissionInfo(@NonNull PermissionInfo orig) {
+ super(orig);
+ protectionLevel = orig.protectionLevel;
+ flags = orig.flags;
+ group = orig.group;
+ backgroundPermission = orig.backgroundPermission;
+ descriptionRes = orig.descriptionRes;
+ requestRes = orig.requestRes;
+ nonLocalizedDescription = orig.nonLocalizedDescription;
+ }
+
+ /**
+ * Retrieve the textual description of this permission. This
+ * will call back on the given PackageManager to load the description from
+ * the application.
+ *
+ * @param pm A PackageManager from which the label can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a CharSequence containing the permission's description.
+ * If there is no description, null is returned.
+ */
+ public @Nullable CharSequence loadDescription(@NonNull PackageManager pm) {
+ if (nonLocalizedDescription != null) {
+ return nonLocalizedDescription;
+ }
+ if (descriptionRes != 0) {
+ CharSequence label = pm.getText(packageName, descriptionRes, null);
+ if (label != null) {
+ return label;
+ }
+ }
+ 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{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeInt(protectionLevel);
+ dest.writeInt(flags);
+ dest.writeString8(group);
+ dest.writeString8(backgroundPermission);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(requestRes);
+ TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ sForStringSet.parcel(knownCerts, dest, parcelableFlags);
+ }
+
+ /** @hide */
+ public int calculateFootprint() {
+ int size = name.length();
+ if (nonLocalizedLabel != null) {
+ size += nonLocalizedLabel.length();
+ }
+ if (nonLocalizedDescription != null) {
+ size += nonLocalizedDescription.length();
+ }
+ return size;
+ }
+
+ /** @hide */
+ public boolean isHardRestricted() {
+ return (flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0;
+ }
+
+ /** @hide */
+ public boolean isSoftRestricted() {
+ return (flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0;
+ }
+
+ /** @hide */
+ public boolean isRestricted() {
+ return isHardRestricted() || isSoftRestricted();
+ }
+
+ /** @hide */
+ public boolean isAppOp() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+ }
+
+ /** @hide */
+ public boolean isRuntime() {
+ return getProtection() == PROTECTION_DANGEROUS;
+ }
+
+ public static final @NonNull Creator<PermissionInfo> CREATOR =
+ new Creator<PermissionInfo>() {
+ @Override
+ public PermissionInfo createFromParcel(Parcel source) {
+ return new PermissionInfo(source);
+ }
+ @Override
+ public PermissionInfo[] newArray(int size) {
+ return new PermissionInfo[size];
+ }
+ };
+
+ private PermissionInfo(Parcel source) {
+ super(source);
+ protectionLevel = source.readInt();
+ flags = source.readInt();
+ group = source.readString8();
+ backgroundPermission = source.readString8();
+ descriptionRes = source.readInt();
+ requestRes = source.readInt();
+ nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ knownCerts = sForStringSet.unparcel(source);
+ }
+}
diff --git a/android/content/pm/ProcessInfo.java b/android/content/pm/ProcessInfo.java
new file mode 100644
index 0000000..632c0f5
--- /dev/null
+++ b/android/content/pm/ProcessInfo.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2020 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.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+/**
+ * Information about a process an app may run. This corresponds to information collected from the
+ * AndroidManifest.xml's <permission-group> tags.
+ * @hide
+ */
+@DataClass(genGetters = true, genSetters = false, genParcelable = true, genAidl = false,
+ genBuilder = false)
+public class ProcessInfo implements Parcelable {
+ /**
+ * The name of the process, fully-qualified based on the app's package name.
+ */
+ @NonNull
+ public String name;
+
+ /**
+ * If non-null, these are permissions that are not allowed in this process.
+ */
+ @Nullable
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringArraySet.class)
+ public ArraySet<String> deniedPermissions;
+
+ /**
+ * Indicates if the process has requested GWP-ASan to be enabled, disabled, or left unspecified.
+ */
+ public @ApplicationInfo.GwpAsanMode int gwpAsanMode;
+
+ /**
+ * Indicates if the process has requested Memtag to be enabled (in sync or async mode),
+ * disabled, or left unspecified.
+ */
+ public @ApplicationInfo.MemtagMode int memtagMode;
+
+ /**
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized;
+
+ @Deprecated
+ public ProcessInfo(@NonNull ProcessInfo orig) {
+ this.name = orig.name;
+ this.deniedPermissions = orig.deniedPermissions;
+ this.gwpAsanMode = orig.gwpAsanMode;
+ this.memtagMode = orig.memtagMode;
+ this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ProcessInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ProcessInfo.
+ *
+ * @param name
+ * The name of the process, fully-qualified based on the app's package name.
+ * @param deniedPermissions
+ * If non-null, these are permissions that are not allowed in this process.
+ * @param gwpAsanMode
+ * Indicates if the process has requested GWP-ASan to be enabled, disabled, or left unspecified.
+ * @param memtagMode
+ * Indicates if the process has requested Memtag to be enabled (in sync or async mode),
+ * disabled, or left unspecified.
+ * @param nativeHeapZeroInitialized
+ * Enable automatic zero-initialization of native heap memory allocations.
+ */
+ @DataClass.Generated.Member
+ public ProcessInfo(
+ @NonNull String name,
+ @Nullable ArraySet<String> deniedPermissions,
+ @ApplicationInfo.GwpAsanMode int gwpAsanMode,
+ @ApplicationInfo.MemtagMode int memtagMode,
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ this.name = name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.deniedPermissions = deniedPermissions;
+ this.gwpAsanMode = gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<ArraySet<String>> sParcellingForDeniedPermissions =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForInternedStringArraySet.class);
+ static {
+ if (sParcellingForDeniedPermissions == null) {
+ sParcellingForDeniedPermissions = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForInternedStringArraySet());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (deniedPermissions != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeString(name);
+ sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
+ dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ dest.writeInt(nativeHeapZeroInitialized);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ProcessInfo(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String _name = in.readString();
+ ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
+ int _gwpAsanMode = in.readInt();
+ int _memtagMode = in.readInt();
+ int _nativeHeapZeroInitialized = in.readInt();
+
+ this.name = _name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.deniedPermissions = _deniedPermissions;
+ this.gwpAsanMode = _gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = _memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ProcessInfo> CREATOR
+ = new Parcelable.Creator<ProcessInfo>() {
+ @Override
+ public ProcessInfo[] newArray(int size) {
+ return new ProcessInfo[size];
+ }
+
+ @Override
+ public ProcessInfo createFromParcel(@NonNull Parcel in) {
+ return new ProcessInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1615850184524L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
+ inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\[email protected](genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/ProviderInfo.java b/android/content/pm/ProviderInfo.java
new file mode 100644
index 0000000..3984ade
--- /dev/null
+++ b/android/content/pm/ProviderInfo.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2006 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.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.util.Printer;
+
+/**
+ * Holds information about a specific
+ * {@link android.content.ContentProvider content provider}. This is returned by
+ * {@link android.content.pm.PackageManager#resolveContentProvider(java.lang.String, int)
+ * PackageManager.resolveContentProvider()}.
+ */
+public final class ProviderInfo extends ComponentInfo
+ implements Parcelable {
+
+ /** The name provider is published under content:// */
+ public String authority = null;
+
+ /** Optional permission required for read-only access this content
+ * provider. */
+ public String readPermission = null;
+
+ /** Optional permission required for read/write access this content
+ * provider. */
+ public String writePermission = null;
+
+ /** If true, additional permissions to specific Uris in this content
+ * provider can be granted, as per the
+ * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+ * grantUriPermissions} attribute.
+ */
+ public boolean grantUriPermissions = false;
+
+ /** If true, always apply URI permission grants, as per the
+ * {@link android.R.styleable#AndroidManifestProvider_forceUriPermissions
+ * forceUriPermissions} attribute.
+ */
+ public boolean forceUriPermissions = false;
+
+ /**
+ * If non-null, these are the patterns that are allowed for granting URI
+ * permissions. Any URI that does not match one of these patterns will not
+ * allowed to be granted. If null, all URIs are allowed. The
+ * {@link PackageManager#GET_URI_PERMISSION_PATTERNS
+ * PackageManager.GET_URI_PERMISSION_PATTERNS} flag must be specified for
+ * this field to be filled in.
+ */
+ public PatternMatcher[] uriPermissionPatterns = null;
+
+ /**
+ * If non-null, these are path-specific permissions that are allowed for
+ * accessing the provider. Any permissions listed here will allow a
+ * holding client to access the provider, and the provider will check
+ * the URI it provides when making calls against the patterns here.
+ */
+ public PathPermission[] pathPermissions = null;
+
+ /** If true, this content provider allows multiple instances of itself
+ * to run in different process. If false, a single instances is always
+ * run in {@link #processName}. */
+ public boolean multiprocess = false;
+
+ /** Used to control initialization order of single-process providers
+ * running in the same process. Higher goes first. */
+ public int initOrder = 0;
+
+ /**
+ * Bit in {@link #flags} indicating if the provider is visible to ephemeral applications.
+ * @hide
+ */
+ public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
+
+ /**
+ * Bit in {@link #flags}: If set, a single instance of the provider will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+
+ /**
+ * Options that have been set in the provider declaration in the
+ * manifest.
+ * These include: {@link #FLAG_SINGLE_USER}.
+ */
+ public int flags = 0;
+
+ /**
+ * Whether or not this provider is syncable.
+ * @deprecated This flag is now being ignored. The current way to make a provider
+ * syncable is to provide a SyncAdapter service for a given provider/account type.
+ */
+ @Deprecated
+ public boolean isSyncable = false;
+
+ public ProviderInfo() {
+ }
+
+ public ProviderInfo(ProviderInfo orig) {
+ super(orig);
+ authority = orig.authority;
+ readPermission = orig.readPermission;
+ writePermission = orig.writePermission;
+ grantUriPermissions = orig.grantUriPermissions;
+ forceUriPermissions = orig.forceUriPermissions;
+ uriPermissionPatterns = orig.uriPermissionPatterns;
+ pathPermissions = orig.pathPermissions;
+ multiprocess = orig.multiprocess;
+ initOrder = orig.initOrder;
+ flags = orig.flags;
+ isSyncable = orig.isSyncable;
+ }
+
+ public void dump(Printer pw, String prefix) {
+ dump(pw, prefix, DUMP_FLAG_ALL);
+ }
+
+ /** @hide */
+ public void dump(Printer pw, String prefix, int dumpFlags) {
+ super.dumpFront(pw, prefix);
+ pw.println(prefix + "authority=" + authority);
+ pw.println(prefix + "flags=0x" + Integer.toHexString(flags));
+ super.dumpBack(pw, prefix, dumpFlags);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override public void writeToParcel(Parcel out, int parcelableFlags) {
+ super.writeToParcel(out, parcelableFlags);
+ out.writeString8(authority);
+ out.writeString8(readPermission);
+ out.writeString8(writePermission);
+ out.writeInt(grantUriPermissions ? 1 : 0);
+ out.writeInt(forceUriPermissions ? 1 : 0);
+ out.writeTypedArray(uriPermissionPatterns, parcelableFlags);
+ out.writeTypedArray(pathPermissions, parcelableFlags);
+ out.writeInt(multiprocess ? 1 : 0);
+ out.writeInt(initOrder);
+ out.writeInt(flags);
+ out.writeInt(isSyncable ? 1 : 0);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ProviderInfo> CREATOR
+ = new Parcelable.Creator<ProviderInfo>() {
+ public ProviderInfo createFromParcel(Parcel in) {
+ return new ProviderInfo(in);
+ }
+
+ public ProviderInfo[] newArray(int size) {
+ return new ProviderInfo[size];
+ }
+ };
+
+ public String toString() {
+ return "ContentProviderInfo{name=" + authority + " className=" + name + "}";
+ }
+
+ private ProviderInfo(Parcel in) {
+ super(in);
+ authority = in.readString8();
+ readPermission = in.readString8();
+ writePermission = in.readString8();
+ grantUriPermissions = in.readInt() != 0;
+ forceUriPermissions = in.readInt() != 0;
+ uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+ pathPermissions = in.createTypedArray(PathPermission.CREATOR);
+ multiprocess = in.readInt() != 0;
+ initOrder = in.readInt();
+ flags = in.readInt();
+ isSyncable = in.readInt() != 0;
+ }
+}
diff --git a/android/content/pm/ProviderInfoList.java b/android/content/pm/ProviderInfoList.java
new file mode 100644
index 0000000..566be2e
--- /dev/null
+++ b/android/content/pm/ProviderInfoList.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 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.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Equivalent to List<ProviderInfo>, but it "squashes" the ApplicationInfo in the elements.
+ *
+ * @hide
+ */
+@TestApi
+public final class ProviderInfoList implements Parcelable {
+ private final List<ProviderInfo> mList;
+
+ private ProviderInfoList(Parcel source) {
+ final ArrayList<ProviderInfo> list = new ArrayList<>();
+ source.readTypedList(list, ProviderInfo.CREATOR);
+ mList = list;
+ }
+
+ private ProviderInfoList(List<ProviderInfo> list) {
+ mList = list;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // Allow ApplicationInfo to be squashed.
+ final boolean prevAllowSquashing = dest.allowSquashing();
+ dest.writeTypedList(mList, flags);
+ dest.restoreAllowSquashing(prevAllowSquashing);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ProviderInfoList> CREATOR
+ = new Parcelable.Creator<ProviderInfoList>() {
+ @Override
+ public ProviderInfoList createFromParcel(@NonNull Parcel source) {
+ return new ProviderInfoList(source);
+ }
+
+ @Override
+ public ProviderInfoList[] newArray(int size) {
+ return new ProviderInfoList[size];
+ }
+ };
+
+ /**
+ * Return the stored list.
+ */
+ @NonNull
+ public List<ProviderInfo> getList() {
+ return mList;
+ }
+
+ /**
+ * Create a new instance with a {@code list}. The passed list will be shared with the new
+ * instance, so the caller shouldn't modify it.
+ */
+ @NonNull
+ public static ProviderInfoList fromList(@NonNull List<ProviderInfo> list) {
+ return new ProviderInfoList(list);
+ }
+}
diff --git a/android/content/pm/RegisteredServicesCache.java b/android/content/pm/RegisteredServicesCache.java
new file mode 100644
index 0000000..7696cbe
--- /dev/null
+++ b/android/content/pm/RegisteredServicesCache.java
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) 2009 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.Manifest;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.AtomicFile;
+import android.util.AttributeSet;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Cache of registered services. This cache is lazily built by interrogating
+ * {@link PackageManager} on a per-user basis. It's updated as packages are
+ * added, removed and changed. Users are responsible for calling
+ * {@link #invalidateCache(int)} when a user is started, since
+ * {@link PackageManager} broadcasts aren't sent for stopped users.
+ * <p>
+ * The services are referred to by type V and are made available via the
+ * {@link #getServiceInfo} method.
+ *
+ * @hide
+ */
+public abstract class RegisteredServicesCache<V> {
+ private static final String TAG = "PackageManager";
+ private static final boolean DEBUG = false;
+ protected static final String REGISTERED_SERVICES_DIR = "registered_services";
+
+ public final Context mContext;
+ private final String mInterfaceName;
+ private final String mMetaDataName;
+ private final String mAttributesName;
+ private final XmlSerializerAndParser<V> mSerializerAndParser;
+
+ protected final Object mServicesLock = new Object();
+
+ @GuardedBy("mServicesLock")
+ private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
+
+ private static class UserServices<V> {
+ @GuardedBy("mServicesLock")
+ final Map<V, Integer> persistentServices = Maps.newHashMap();
+ @GuardedBy("mServicesLock")
+ Map<V, ServiceInfo<V>> services = null;
+ @GuardedBy("mServicesLock")
+ boolean mPersistentServicesFileDidNotExist = true;
+ @GuardedBy("mServicesLock")
+ boolean mBindInstantServiceAllowed = false;
+ }
+
+ @GuardedBy("mServicesLock")
+ private UserServices<V> findOrCreateUserLocked(int userId) {
+ return findOrCreateUserLocked(userId, true);
+ }
+
+ @GuardedBy("mServicesLock")
+ private UserServices<V> findOrCreateUserLocked(int userId, boolean loadFromFileIfNew) {
+ UserServices<V> services = mUserServices.get(userId);
+ if (services == null) {
+ services = new UserServices<V>();
+ mUserServices.put(userId, services);
+ if (loadFromFileIfNew && mSerializerAndParser != null) {
+ // Check if user exists and try loading data from file
+ // clear existing data if there was an error during migration
+ UserInfo user = getUser(userId);
+ if (user != null) {
+ AtomicFile file = createFileForUser(user.id);
+ if (file.getBaseFile().exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, String.format("Loading u%s data from %s", user.id, file));
+ }
+ InputStream is = null;
+ try {
+ is = file.openRead();
+ readPersistentServicesLocked(is);
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading persistent services for user " + user.id, e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ }
+ }
+ }
+ return services;
+ }
+
+ // the listener and handler are synchronized on "this" and must be updated together
+ private RegisteredServicesCacheListener<V> mListener;
+ private Handler mHandler;
+
+ @UnsupportedAppUsage
+ public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
+ String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
+ mContext = context;
+ mInterfaceName = interfaceName;
+ mMetaDataName = metaDataName;
+ mAttributesName = attributeName;
+ mSerializerAndParser = serializerAndParser;
+
+ migrateIfNecessaryLocked();
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ Handler handler = BackgroundThread.getHandler();
+ mContext.registerReceiverAsUser(
+ mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+
+ // Register for user-related events
+ IntentFilter userFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+ }
+
+ private void handlePackageEvent(Intent intent, int userId) {
+ // Don't regenerate the services map when the package is removed or its
+ // ASEC container unmounted as a step in replacement. The subsequent
+ // _ADDED / _AVAILABLE call will regenerate the map in the final state.
+ final String action = intent.getAction();
+ // it's a new-component action if it isn't some sort of removal
+ final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action);
+ // if it's a removal, is it part of an update-in-place step?
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+ if (isRemoval && replacing) {
+ // package is going away, but it's the middle of an upgrade: keep the current
+ // state and do nothing here. This clause is intentionally empty.
+ } else {
+ int[] uids = null;
+ // either we're adding/changing, or it's a removal without replacement, so
+ // we need to update the set of available services
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)
+ || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ } else {
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (uid > 0) {
+ uids = new int[] { uid };
+ }
+ }
+ generateServicesMap(uids, userId);
+ }
+ }
+
+ private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (uid != -1) {
+ handlePackageEvent(intent, UserHandle.getUserId(uid));
+ }
+ }
+ };
+
+ private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // External apps can't coexist with multi-user, so scan owner
+ handlePackageEvent(intent, UserHandle.USER_SYSTEM);
+ }
+ };
+
+ private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (DEBUG) {
+ Slog.d(TAG, "u" + userId + " removed - cleaning up");
+ }
+ onUserRemoved(userId);
+ }
+ };
+
+ public void invalidateCache(int userId) {
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ user.services = null;
+ onServicesChangedLocked(userId);
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) {
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ if (user.services != null) {
+ fout.println("RegisteredServicesCache: " + user.services.size() + " services");
+ for (ServiceInfo<?> info : user.services.values()) {
+ fout.println(" " + info);
+ }
+ } else {
+ fout.println("RegisteredServicesCache: services not loaded");
+ }
+ }
+ }
+
+ public RegisteredServicesCacheListener<V> getListener() {
+ synchronized (this) {
+ return mListener;
+ }
+ }
+
+ public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
+ if (handler == null) {
+ handler = BackgroundThread.getHandler();
+ }
+ synchronized (this) {
+ mHandler = handler;
+ mListener = listener;
+ }
+ }
+
+ private void notifyListener(final V type, final int userId, final boolean removed) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
+ }
+ RegisteredServicesCacheListener<V> listener;
+ Handler handler;
+ synchronized (this) {
+ listener = mListener;
+ handler = mHandler;
+ }
+ if (listener == null) {
+ return;
+ }
+
+ final RegisteredServicesCacheListener<V> listener2 = listener;
+ handler.post(() -> {
+ try {
+ listener2.onServiceChanged(type, userId, removed);
+ } catch (Throwable th) {
+ Slog.wtf(TAG, "Exception from onServiceChanged", th);
+ }
+ });
+ }
+
+ /**
+ * Value type that describes a Service. The information within can be used
+ * to bind to the service.
+ */
+ public static class ServiceInfo<V> {
+ @UnsupportedAppUsage
+ public final V type;
+ public final ComponentInfo componentInfo;
+ @UnsupportedAppUsage
+ public final ComponentName componentName;
+ @UnsupportedAppUsage
+ public final int uid;
+
+ /** @hide */
+ public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) {
+ this.type = type;
+ this.componentInfo = componentInfo;
+ this.componentName = componentName;
+ this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1;
+ }
+
+ @Override
+ public String toString() {
+ return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+ }
+ }
+
+ /**
+ * Accessor for the registered authenticators.
+ * @param type the account type of the authenticator
+ * @return the AuthenticatorInfo that matches the account type or null if none is present
+ */
+ public ServiceInfo<V> getServiceInfo(V type, int userId) {
+ synchronized (mServicesLock) {
+ // Find user and lazily populate cache
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ if (user.services == null) {
+ generateServicesMap(null, userId);
+ }
+ return user.services.get(type);
+ }
+ }
+
+ /**
+ * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
+ * registered authenticators.
+ */
+ public Collection<ServiceInfo<V>> getAllServices(int userId) {
+ synchronized (mServicesLock) {
+ // Find user and lazily populate cache
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ if (user.services == null) {
+ generateServicesMap(null, userId);
+ }
+ return Collections.unmodifiableCollection(
+ new ArrayList<ServiceInfo<V>>(user.services.values()));
+ }
+ }
+
+ public void updateServices(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateServices u" + userId);
+ }
+ List<ServiceInfo<V>> allServices;
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ // If services haven't been initialized yet - no updates required
+ if (user.services == null) {
+ return;
+ }
+ allServices = new ArrayList<>(user.services.values());
+ }
+ IntArray updatedUids = null;
+ for (ServiceInfo<V> service : allServices) {
+ long versionCode = service.componentInfo.applicationInfo.versionCode;
+ String pkg = service.componentInfo.packageName;
+ ApplicationInfo newAppInfo = null;
+ try {
+ newAppInfo = mContext.getPackageManager().getApplicationInfoAsUser(pkg, 0, userId);
+ } catch (NameNotFoundException e) {
+ // Package uninstalled - treat as null app info
+ }
+ // If package updated or removed
+ if ((newAppInfo == null) || (newAppInfo.versionCode != versionCode)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Package " + pkg + " uid=" + service.uid
+ + " updated. New appInfo: " + newAppInfo);
+ }
+ if (updatedUids == null) {
+ updatedUids = new IntArray();
+ }
+ updatedUids.add(service.uid);
+ }
+ }
+ if (updatedUids != null && updatedUids.size() > 0) {
+ int[] updatedUidsArray = updatedUids.toArray();
+ generateServicesMap(updatedUidsArray, userId);
+ }
+ }
+
+ /**
+ * @return whether the binding to service is allowed for instant apps.
+ */
+ public boolean getBindInstantServiceAllowed(int userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "getBindInstantServiceAllowed");
+
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ return user.mBindInstantServiceAllowed;
+ }
+ }
+
+ /**
+ * Set whether the binding to service is allowed or not for instant apps.
+ */
+ public void setBindInstantServiceAllowed(int userId, boolean allowed) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "setBindInstantServiceAllowed");
+
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ user.mBindInstantServiceAllowed = allowed;
+ }
+ }
+
+ @VisibleForTesting
+ protected boolean inSystemImage(int callerUid) {
+ String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
+ if (packages != null) {
+ for (String name : packages) {
+ try {
+ PackageInfo packageInfo =
+ mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ protected List<ResolveInfo> queryIntentServices(int userId) {
+ final PackageManager pm = mContext.getPackageManager();
+ int flags = PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ if (user.mBindInstantServiceAllowed) {
+ flags |= PackageManager.MATCH_INSTANT;
+ }
+ }
+ return pm.queryIntentServicesAsUser(new Intent(mInterfaceName), flags, userId);
+ }
+
+ /**
+ * Populate {@link UserServices#services} by scanning installed packages for
+ * given {@link UserHandle}.
+ * @param changedUids the array of uids that have been affected, as mentioned in the broadcast
+ * or null to assume that everything is affected.
+ * @param userId the user for whom to update the services map.
+ */
+ private void generateServicesMap(int[] changedUids, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = "
+ + Arrays.toString(changedUids));
+ }
+
+ final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
+ final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ try {
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo);
+ if (info == null) {
+ Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
+ continue;
+ }
+ serviceInfos.add(info);
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
+ }
+ }
+
+ synchronized (mServicesLock) {
+ final UserServices<V> user = findOrCreateUserLocked(userId);
+ final boolean firstScan = user.services == null;
+ if (firstScan) {
+ user.services = Maps.newHashMap();
+ }
+
+ StringBuilder changes = new StringBuilder();
+ boolean changed = false;
+ for (ServiceInfo<V> info : serviceInfos) {
+ // four cases:
+ // - doesn't exist yet
+ // - add, notify user that it was added
+ // - exists and the UID is the same
+ // - replace, don't notify user
+ // - exists, the UID is different, and the new one is not a system package
+ // - ignore
+ // - exists, the UID is different, and the new one is a system package
+ // - add, notify user that it was added
+ Integer previousUid = user.persistentServices.get(info.type);
+ if (previousUid == null) {
+ if (DEBUG) {
+ changes.append(" New service added: ").append(info).append("\n");
+ }
+ changed = true;
+ user.services.put(info.type, info);
+ user.persistentServices.put(info.type, info.uid);
+ if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
+ notifyListener(info.type, userId, false /* removed */);
+ }
+ } else if (previousUid == info.uid) {
+ if (DEBUG) {
+ changes.append(" Existing service (nop): ").append(info).append("\n");
+ }
+ user.services.put(info.type, info);
+ } else if (inSystemImage(info.uid)
+ || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
+ if (DEBUG) {
+ if (inSystemImage(info.uid)) {
+ changes.append(" System service replacing existing: ").append(info)
+ .append("\n");
+ } else {
+ changes.append(" Existing service replacing a removed service: ")
+ .append(info).append("\n");
+ }
+ }
+ changed = true;
+ user.services.put(info.type, info);
+ user.persistentServices.put(info.type, info.uid);
+ notifyListener(info.type, userId, false /* removed */);
+ } else {
+ // ignore
+ if (DEBUG) {
+ changes.append(" Existing service with new uid ignored: ").append(info)
+ .append("\n");
+ }
+ }
+ }
+
+ ArrayList<V> toBeRemoved = Lists.newArrayList();
+ for (V v1 : user.persistentServices.keySet()) {
+ // Remove a persisted service that's not in the currently available services list.
+ // And only if it is in the list of changedUids.
+ if (!containsType(serviceInfos, v1)
+ && containsUid(changedUids, user.persistentServices.get(v1))) {
+ toBeRemoved.add(v1);
+ }
+ }
+ for (V v1 : toBeRemoved) {
+ if (DEBUG) {
+ changes.append(" Service removed: ").append(v1).append("\n");
+ }
+ changed = true;
+ user.persistentServices.remove(v1);
+ user.services.remove(v1);
+ notifyListener(v1, userId, true /* removed */);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "user.services=");
+ for (V v : user.services.keySet()) {
+ Log.d(TAG, " " + v + " " + user.services.get(v));
+ }
+ Log.d(TAG, "user.persistentServices=");
+ for (V v : user.persistentServices.keySet()) {
+ Log.d(TAG, " " + v + " " + user.persistentServices.get(v));
+ }
+ }
+ if (DEBUG) {
+ if (changes.length() > 0) {
+ Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
+ serviceInfos.size() + " services:\n" + changes);
+ } else {
+ Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
+ serviceInfos.size() + " services unchanged");
+ }
+ }
+ if (changed) {
+ onServicesChangedLocked(userId);
+ writePersistentServicesLocked(user, userId);
+ }
+ }
+ }
+
+ protected void onServicesChangedLocked(int userId) {
+ // Feel free to override
+ }
+
+ /**
+ * Returns true if the list of changed uids is null (wildcard) or the specified uid
+ * is contained in the list of changed uids.
+ */
+ private boolean containsUid(int[] changedUids, int uid) {
+ return changedUids == null || ArrayUtils.contains(changedUids, uid);
+ }
+
+ private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
+ for (int i = 0, N = serviceInfos.size(); i < N; i++) {
+ if (serviceInfos.get(i).type.equals(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
+ for (int i = 0, N = serviceInfos.size(); i < N; i++) {
+ final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
+ if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
+ throws XmlPullParserException, IOException {
+ android.content.pm.ServiceInfo si = service.serviceInfo;
+ ComponentName componentName = new ComponentName(si.packageName, si.name);
+
+ PackageManager pm = mContext.getPackageManager();
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, mMetaDataName);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!mAttributesName.equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with " + mAttributesName + " tag");
+ }
+
+ V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
+ si.packageName, attrs);
+ if (v == null) {
+ return null;
+ }
+ final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
+ return new ServiceInfo<V>(v, serviceInfo, componentName);
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException(
+ "Unable to load resources for pacakge " + si.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ /**
+ * Read all sync status back in to the initial engine state.
+ */
+ private void readPersistentServicesLocked(InputStream is)
+ throws XmlPullParserException, IOException {
+ TypedXmlPullParser parser = Xml.resolvePullParser(is);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ String tagName = parser.getName();
+ if ("services".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
+ tagName = parser.getName();
+ if ("service".equals(tagName)) {
+ V service = mSerializerAndParser.createFromXml(parser);
+ if (service == null) {
+ break;
+ }
+ final int uid = parser.getAttributeInt(null, "uid");
+ final int userId = UserHandle.getUserId(uid);
+ final UserServices<V> user = findOrCreateUserLocked(userId,
+ false /*loadFromFileIfNew*/) ;
+ user.persistentServices.put(service, uid);
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ }
+
+ private void migrateIfNecessaryLocked() {
+ if (mSerializerAndParser == null) {
+ return;
+ }
+ File systemDir = new File(getDataDirectory(), "system");
+ File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR);
+ AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml"));
+ boolean oldFileExists = oldFile.getBaseFile().exists();
+
+ if (oldFileExists) {
+ File marker = new File(syncDir, mInterfaceName + ".xml.migrated");
+ // if not migrated, perform the migration and add a marker
+ if (!marker.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Marker file " + marker + " does not exist - running migration");
+ }
+ InputStream is = null;
+ try {
+ is = oldFile.openRead();
+ mUserServices.clear();
+ readPersistentServicesLocked(is);
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading persistent services, starting from scratch", e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ try {
+ for (UserInfo user : getUsers()) {
+ UserServices<V> userServices = mUserServices.get(user.id);
+ if (userServices != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating u" + user.id + " services "
+ + userServices.persistentServices);
+ }
+ writePersistentServicesLocked(userServices, user.id);
+ }
+ }
+ marker.createNewFile();
+ } catch (Exception e) {
+ Log.w(TAG, "Migration failed", e);
+ }
+ // Migration is complete and we don't need to keep data for all users anymore,
+ // It will be loaded from a new location when requested
+ mUserServices.clear();
+ }
+ }
+ }
+
+ /**
+ * Writes services of a specified user to the file.
+ */
+ private void writePersistentServicesLocked(UserServices<V> user, int userId) {
+ if (mSerializerAndParser == null) {
+ return;
+ }
+ AtomicFile atomicFile = createFileForUser(userId);
+ FileOutputStream fos = null;
+ try {
+ fos = atomicFile.startWrite();
+ TypedXmlSerializer out = Xml.resolveSerializer(fos);
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "services");
+ for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
+ out.startTag(null, "service");
+ out.attributeInt(null, "uid", service.getValue());
+ mSerializerAndParser.writeAsXml(service.getKey(), out);
+ out.endTag(null, "service");
+ }
+ out.endTag(null, "services");
+ out.endDocument();
+ atomicFile.finishWrite(fos);
+ } catch (IOException e1) {
+ Log.w(TAG, "Error writing accounts", e1);
+ if (fos != null) {
+ atomicFile.failWrite(fos);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ protected void onUserRemoved(int userId) {
+ synchronized (mServicesLock) {
+ mUserServices.remove(userId);
+ }
+ }
+
+ @VisibleForTesting
+ protected List<UserInfo> getUsers() {
+ return UserManager.get(mContext).getAliveUsers();
+ }
+
+ @VisibleForTesting
+ protected UserInfo getUser(int userId) {
+ return UserManager.get(mContext).getUserInfo(userId);
+ }
+
+ private AtomicFile createFileForUser(int userId) {
+ File userDir = getUserSystemDirectory(userId);
+ File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
+ return new AtomicFile(userFile);
+ }
+
+ @VisibleForTesting
+ protected File getUserSystemDirectory(int userId) {
+ return Environment.getUserSystemDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataDirectory() {
+ return Environment.getDataDirectory();
+ }
+
+ @VisibleForTesting
+ protected Map<V, Integer> getPersistentServices(int userId) {
+ return findOrCreateUserLocked(userId).persistentServices;
+ }
+
+ public abstract V parseServiceAttributes(Resources res,
+ String packageName, AttributeSet attrs);
+}
diff --git a/android/content/pm/RegisteredServicesCacheListener.java b/android/content/pm/RegisteredServicesCacheListener.java
new file mode 100644
index 0000000..df79544
--- /dev/null
+++ b/android/content/pm/RegisteredServicesCacheListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * Listener for changes to the set of registered services managed by a RegisteredServicesCache.
+ * @hide
+ */
+public interface RegisteredServicesCacheListener<V> {
+ /**
+ * Invoked when a service is registered or changed.
+ * @param type the type of registered service
+ * @param removed true if the service was removed
+ */
+ void onServiceChanged(V type, int userId, boolean removed);
+}
diff --git a/android/content/pm/ResolveInfo.java b/android/content/pm/ResolveInfo.java
new file mode 100644
index 0000000..6f07dd7
--- /dev/null
+++ b/android/content/pm/ResolveInfo.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2007 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.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.util.Slog;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Information that is returned from resolving an intent
+ * against an IntentFilter. This partially corresponds to
+ * information collected from the AndroidManifest.xml's
+ * <intent> tags.
+ */
+public class ResolveInfo implements Parcelable {
+ private static final String TAG = "ResolveInfo";
+ private static final String INTENT_FORWARDER_ACTIVITY =
+ "com.android.internal.app.IntentForwarderActivity";
+
+ /**
+ * The activity or broadcast receiver that corresponds to this resolution
+ * match, if this resolution is for an activity or broadcast receiver.
+ * Exactly one of {@link #activityInfo}, {@link #serviceInfo}, or
+ * {@link #providerInfo} will be non-null.
+ */
+ public ActivityInfo activityInfo;
+
+ /**
+ * The service that corresponds to this resolution match, if this resolution
+ * is for a service. Exactly one of {@link #activityInfo},
+ * {@link #serviceInfo}, or {@link #providerInfo} will be non-null.
+ */
+ public ServiceInfo serviceInfo;
+
+ /**
+ * The provider that corresponds to this resolution match, if this
+ * resolution is for a provider. Exactly one of {@link #activityInfo},
+ * {@link #serviceInfo}, or {@link #providerInfo} will be non-null.
+ */
+ public ProviderInfo providerInfo;
+
+ /**
+ * An auxiliary response that may modify the resolved information. This is
+ * only set under certain circumstances; such as when resolving instant apps
+ * or components defined in un-installed splits.
+ * @hide
+ */
+ public AuxiliaryResolveInfo auxiliaryInfo;
+
+ /**
+ * Whether or not an instant app is available for the resolved intent.
+ */
+ public boolean isInstantAppAvailable;
+
+ /**
+ * The IntentFilter that was matched for this ResolveInfo.
+ */
+ public IntentFilter filter;
+
+ /**
+ * The declared priority of this match. Comes from the "priority"
+ * attribute or, if not set, defaults to 0. Higher values are a higher
+ * priority.
+ */
+ public int priority;
+
+ /**
+ * Order of result according to the user's preference. If the user
+ * has not set a preference for this result, the value is 0; higher
+ * values are a higher priority.
+ */
+ public int preferredOrder;
+
+ /**
+ * The system's evaluation of how well the activity matches the
+ * IntentFilter. This is a match constant, a combination of
+ * {@link IntentFilter#MATCH_CATEGORY_MASK IntentFilter.MATCH_CATEGORY_MASK}
+ * and {@link IntentFilter#MATCH_ADJUSTMENT_MASK IntentFiler.MATCH_ADJUSTMENT_MASK}.
+ */
+ public int match;
+
+ /**
+ * Only set when returned by
+ * {@link PackageManager#queryIntentActivityOptions}, this tells you
+ * which of the given specific intents this result came from. 0 is the
+ * first in the list, < 0 means it came from the generic Intent query.
+ */
+ public int specificIndex = -1;
+
+ /**
+ * This filter has specified the Intent.CATEGORY_DEFAULT, meaning it
+ * would like to be considered a default action that the user can
+ * perform on this data.
+ */
+ public boolean isDefault;
+
+ /**
+ * A string resource identifier (in the package's resources) of this
+ * match's label. From the "label" attribute or, if not set, 0.
+ */
+ public int labelRes;
+
+ /**
+ * The actual string retrieve from <var>labelRes</var> or null if none
+ * was provided.
+ */
+ public CharSequence nonLocalizedLabel;
+
+ /**
+ * A drawable resource identifier (in the package's resources) of this
+ * match's icon. From the "icon" attribute or, if not set, 0. It is
+ * set only if the icon can be obtained by resource id alone.
+ */
+ public int icon;
+
+ /**
+ * Optional -- if non-null, the {@link #labelRes} and {@link #icon}
+ * resources will be loaded from this package, rather than the one
+ * containing the resolved component.
+ */
+ public String resolvePackageName;
+
+ /**
+ * If not equal to UserHandle.USER_CURRENT, then the intent will be forwarded to this user.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int targetUserId;
+
+ /**
+ * Set to true if the icon cannot be obtained by resource ids alone.
+ * It is set to true for ResolveInfos from the managed profile: They need to
+ * have their icon badged, so it cannot be obtained by resource ids alone.
+ * @hide
+ */
+ public boolean noResourceId;
+
+ /**
+ * Same as {@link #icon} but it will always correspond to "icon" attribute
+ * regardless of {@link #noResourceId} value.
+ * @hide
+ */
+ public int iconResourceId;
+
+ /**
+ * @hide Target comes from system process?
+ */
+ @UnsupportedAppUsage
+ public boolean system;
+
+ /**
+ * Will be set to {@code true} if the {@link IntentFilter} responsible for intent
+ * resolution is classified as a "browser".
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean handleAllWebDataURI;
+
+ /**
+ * Whether the resolved {@link IntentFilter} declares {@link Intent#CATEGORY_BROWSABLE} and is
+ * thus allowed to automatically resolve an {@link Intent} as it's assumed the action is safe
+ * for the user.
+ *
+ * Note that the above doesn't apply when this is the only result is returned in the candidate
+ * set, as the system will not prompt before opening the result. It only applies when there are
+ * multiple candidates.
+ */
+ private final boolean mAutoResolutionAllowed;
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ComponentInfo getComponentInfo() {
+ if (activityInfo != null) return activityInfo;
+ if (serviceInfo != null) return serviceInfo;
+ if (providerInfo != null) return providerInfo;
+ throw new IllegalStateException("Missing ComponentInfo!");
+ }
+
+ /**
+ * Retrieve the current textual label associated with this resolution. This
+ * will call back on the given PackageManager to load the label from
+ * the application.
+ *
+ * @param pm A PackageManager from which the label can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a CharSequence containing the resolutions's label. If the
+ * item does not have a label, its name is returned.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ if (nonLocalizedLabel != null) {
+ return nonLocalizedLabel;
+ }
+ CharSequence label;
+ if (resolvePackageName != null && labelRes != 0) {
+ label = pm.getText(resolvePackageName, labelRes, null);
+ if (label != null) {
+ return label.toString().trim();
+ }
+ }
+ ComponentInfo ci = getComponentInfo();
+ ApplicationInfo ai = ci.applicationInfo;
+ if (labelRes != 0) {
+ label = pm.getText(ci.packageName, labelRes, ai);
+ if (label != null) {
+ return label.toString().trim();
+ }
+ }
+
+ CharSequence data = ci.loadLabel(pm);
+ // Make the data safe
+ if (data != null) data = data.toString().trim();
+ return data;
+ }
+
+ /**
+ * @return The resource that would be used when loading
+ * the label for this resolve info.
+ *
+ * @hide
+ */
+ public int resolveLabelResId() {
+ if (labelRes != 0) {
+ return labelRes;
+ }
+ final ComponentInfo componentInfo = getComponentInfo();
+ if (componentInfo.labelRes != 0) {
+ return componentInfo.labelRes;
+ }
+ return componentInfo.applicationInfo.labelRes;
+ }
+
+ /**
+ * @return The resource that would be used when loading
+ * the icon for this resolve info.
+ *
+ * @hide
+ */
+ public int resolveIconResId() {
+ if (icon != 0) {
+ return icon;
+ }
+ final ComponentInfo componentInfo = getComponentInfo();
+ if (componentInfo.icon != 0) {
+ return componentInfo.icon;
+ }
+ return componentInfo.applicationInfo.icon;
+ }
+
+ /**
+ * Retrieve the current graphical icon associated with this resolution. This
+ * will call back on the given PackageManager to load the icon from
+ * the application.
+ *
+ * @param pm A PackageManager from which the icon can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the resolution's icon. If the
+ * item does not have an icon, the default activity icon is returned.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ Drawable dr = null;
+ if (resolvePackageName != null && iconResourceId != 0) {
+ dr = pm.getDrawable(resolvePackageName, iconResourceId, null);
+ }
+ ComponentInfo ci = getComponentInfo();
+ if (dr == null && iconResourceId != 0) {
+ ApplicationInfo ai = ci.applicationInfo;
+ dr = pm.getDrawable(ci.packageName, iconResourceId, ai);
+ }
+ if (dr != null) {
+ return pm.getUserBadgedIcon(dr, new UserHandle(pm.getUserId()));
+ }
+ return ci.loadIcon(pm);
+ }
+
+ /**
+ * Return the icon resource identifier to use for this match. If the
+ * match defines an icon, that is used; else if the activity defines
+ * an icon, that is used; else, the application icon is used.
+ * This function does not check noResourceId flag.
+ *
+ * @return The icon associated with this match.
+ */
+ final int getIconResourceInternal() {
+ if (iconResourceId != 0) return iconResourceId;
+ final ComponentInfo ci = getComponentInfo();
+ if (ci != null) {
+ return ci.getIconResource();
+ }
+ return 0;
+ }
+
+ /**
+ * Return the icon resource identifier to use for this match. If the
+ * match defines an icon, that is used; else if the activity defines
+ * an icon, that is used; else, the application icon is used.
+ *
+ * @return The icon associated with this match.
+ */
+ public final int getIconResource() {
+ if (noResourceId) return 0;
+ return getIconResourceInternal();
+ }
+
+ public void dump(Printer pw, String prefix) {
+ dump(pw, prefix, PackageItemInfo.DUMP_FLAG_ALL);
+ }
+
+ /** @hide */
+ public void dump(Printer pw, String prefix, int dumpFlags) {
+ if (filter != null) {
+ pw.println(prefix + "Filter:");
+ filter.dump(pw, prefix + " ");
+ }
+ pw.println(prefix + "priority=" + priority
+ + " preferredOrder=" + preferredOrder
+ + " match=0x" + Integer.toHexString(match)
+ + " specificIndex=" + specificIndex
+ + " isDefault=" + isDefault);
+ if (resolvePackageName != null) {
+ pw.println(prefix + "resolvePackageName=" + resolvePackageName);
+ }
+ if (labelRes != 0 || nonLocalizedLabel != null || icon != 0) {
+ pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
+ + " nonLocalizedLabel=" + nonLocalizedLabel
+ + " icon=0x" + Integer.toHexString(icon));
+ }
+ if (activityInfo != null) {
+ pw.println(prefix + "ActivityInfo:");
+ activityInfo.dump(pw, prefix + " ", dumpFlags);
+ } else if (serviceInfo != null) {
+ pw.println(prefix + "ServiceInfo:");
+ serviceInfo.dump(pw, prefix + " ", dumpFlags);
+ } else if (providerInfo != null) {
+ pw.println(prefix + "ProviderInfo:");
+ providerInfo.dump(pw, prefix + " ", dumpFlags);
+ }
+ }
+
+ /**
+ * Returns whether this resolution represents the intent forwarder activity.
+ *
+ * @return whether this resolution represents the intent forwarder activity
+ */
+ public boolean isCrossProfileIntentForwarderActivity() {
+ return activityInfo != null
+ && INTENT_FORWARDER_ACTIVITY.equals(activityInfo.targetActivity);
+ }
+
+ /**
+ * @see #mAutoResolutionAllowed
+ * @hide
+ */
+ public boolean isAutoResolutionAllowed() {
+ return mAutoResolutionAllowed;
+ }
+
+ public ResolveInfo() {
+ targetUserId = UserHandle.USER_CURRENT;
+
+ // It's safer to assume that an unaware caller that constructs a ResolveInfo doesn't
+ // accidentally mark a result as auto resolveable.
+ mAutoResolutionAllowed = false;
+ }
+
+ /** @hide */
+ public ResolveInfo(boolean autoResolutionAllowed) {
+ targetUserId = UserHandle.USER_CURRENT;
+ mAutoResolutionAllowed = autoResolutionAllowed;
+ }
+
+ public ResolveInfo(ResolveInfo orig) {
+ activityInfo = orig.activityInfo;
+ serviceInfo = orig.serviceInfo;
+ providerInfo = orig.providerInfo;
+ filter = orig.filter;
+ priority = orig.priority;
+ preferredOrder = orig.preferredOrder;
+ match = orig.match;
+ specificIndex = orig.specificIndex;
+ labelRes = orig.labelRes;
+ nonLocalizedLabel = orig.nonLocalizedLabel;
+ icon = orig.icon;
+ resolvePackageName = orig.resolvePackageName;
+ noResourceId = orig.noResourceId;
+ iconResourceId = orig.iconResourceId;
+ system = orig.system;
+ targetUserId = orig.targetUserId;
+ handleAllWebDataURI = orig.handleAllWebDataURI;
+ mAutoResolutionAllowed = orig.mAutoResolutionAllowed;
+ isInstantAppAvailable = orig.isInstantAppAvailable;
+ }
+
+ public String toString() {
+ final ComponentInfo ci = getComponentInfo();
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ResolveInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, ci.packageName, ci.name);
+ if (priority != 0) {
+ sb.append(" p=");
+ sb.append(priority);
+ }
+ if (preferredOrder != 0) {
+ sb.append(" o=");
+ sb.append(preferredOrder);
+ }
+ sb.append(" m=0x");
+ sb.append(Integer.toHexString(match));
+ if (targetUserId != UserHandle.USER_CURRENT) {
+ sb.append(" targetUserId=");
+ sb.append(targetUserId);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ if (activityInfo != null) {
+ dest.writeInt(1);
+ activityInfo.writeToParcel(dest, parcelableFlags);
+ } else if (serviceInfo != null) {
+ dest.writeInt(2);
+ serviceInfo.writeToParcel(dest, parcelableFlags);
+ } else if (providerInfo != null) {
+ dest.writeInt(3);
+ providerInfo.writeToParcel(dest, parcelableFlags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (filter != null) {
+ dest.writeInt(1);
+ filter.writeToParcel(dest, parcelableFlags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(priority);
+ dest.writeInt(preferredOrder);
+ dest.writeInt(match);
+ dest.writeInt(specificIndex);
+ dest.writeInt(labelRes);
+ TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
+ dest.writeInt(icon);
+ dest.writeString8(resolvePackageName);
+ dest.writeInt(targetUserId);
+ dest.writeInt(system ? 1 : 0);
+ dest.writeInt(noResourceId ? 1 : 0);
+ dest.writeInt(iconResourceId);
+ dest.writeInt(handleAllWebDataURI ? 1 : 0);
+ dest.writeInt(mAutoResolutionAllowed ? 1 : 0);
+ dest.writeInt(isInstantAppAvailable ? 1 : 0);
+ }
+
+ public static final @android.annotation.NonNull Creator<ResolveInfo> CREATOR
+ = new Creator<ResolveInfo>() {
+ public ResolveInfo createFromParcel(Parcel source) {
+ return new ResolveInfo(source);
+ }
+ public ResolveInfo[] newArray(int size) {
+ return new ResolveInfo[size];
+ }
+ };
+
+ private ResolveInfo(Parcel source) {
+ activityInfo = null;
+ serviceInfo = null;
+ providerInfo = null;
+ switch (source.readInt()) {
+ case 1:
+ activityInfo = ActivityInfo.CREATOR.createFromParcel(source);
+ break;
+ case 2:
+ serviceInfo = ServiceInfo.CREATOR.createFromParcel(source);
+ break;
+ case 3:
+ providerInfo = ProviderInfo.CREATOR.createFromParcel(source);
+ break;
+ default:
+ Slog.w(TAG, "Missing ComponentInfo!");
+ break;
+ }
+ if (source.readInt() != 0) {
+ filter = IntentFilter.CREATOR.createFromParcel(source);
+ }
+ priority = source.readInt();
+ preferredOrder = source.readInt();
+ match = source.readInt();
+ specificIndex = source.readInt();
+ labelRes = source.readInt();
+ nonLocalizedLabel
+ = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ icon = source.readInt();
+ resolvePackageName = source.readString8();
+ targetUserId = source.readInt();
+ system = source.readInt() != 0;
+ noResourceId = source.readInt() != 0;
+ iconResourceId = source.readInt();
+ handleAllWebDataURI = source.readInt() != 0;
+ mAutoResolutionAllowed = source.readInt() != 0;
+ isInstantAppAvailable = source.readInt() != 0;
+ }
+
+ public static class DisplayNameComparator
+ implements Comparator<ResolveInfo> {
+ public DisplayNameComparator(PackageManager pm) {
+ mPM = pm;
+ mCollator.setStrength(Collator.PRIMARY);
+ }
+
+ public final int compare(ResolveInfo a, ResolveInfo b) {
+ // We want to put the one targeted to another user at the end of the dialog.
+ if (a.targetUserId != UserHandle.USER_CURRENT) {
+ return 1;
+ }
+ if (b.targetUserId != UserHandle.USER_CURRENT) {
+ return -1;
+ }
+ CharSequence sa = a.loadLabel(mPM);
+ if (sa == null) sa = a.activityInfo.name;
+ CharSequence sb = b.loadLabel(mPM);
+ if (sb == null) sb = b.activityInfo.name;
+
+ return mCollator.compare(sa.toString(), sb.toString());
+ }
+
+ private final Collator mCollator = Collator.getInstance();
+ private PackageManager mPM;
+ }
+}
diff --git a/android/content/pm/SELinuxUtil.java b/android/content/pm/SELinuxUtil.java
new file mode 100644
index 0000000..025c0fe
--- /dev/null
+++ b/android/content/pm/SELinuxUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.pm;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Utility methods that need to be used in application space.
+ * @hide
+ */
+public final class SELinuxUtil {
+
+ /** Append to existing seinfo label for instant apps @hide */
+ private static final String INSTANT_APP_STR = ":ephemeralapp";
+
+ /** Append to existing seinfo when modifications are complete @hide */
+ public static final String COMPLETE_STR = ":complete";
+
+ /** @hide */
+ public static String assignSeinfoUser(PackageUserState userState) {
+ if (userState.instantApp) {
+ return INSTANT_APP_STR + COMPLETE_STR;
+ }
+ return COMPLETE_STR;
+ }
+
+}
diff --git a/android/content/pm/ServiceInfo.java b/android/content/pm/ServiceInfo.java
new file mode 100644
index 0000000..7fc242c
--- /dev/null
+++ b/android/content/pm/ServiceInfo.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2007 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.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information you can retrieve about a particular application
+ * service. This corresponds to information collected from the
+ * AndroidManifest.xml's <service> tags.
+ */
+public class ServiceInfo extends ComponentInfo
+ implements Parcelable {
+ /**
+ * Optional name of a permission required to be able to access this
+ * Service. From the "permission" attribute.
+ */
+ public String permission;
+
+ /**
+ * Bit in {@link #flags}: If set, the service will automatically be
+ * stopped by the system if the user removes a task that is rooted
+ * in one of the application's activities. Set from the
+ * {@link android.R.attr#stopWithTask} attribute.
+ */
+ public static final int FLAG_STOP_WITH_TASK = 0x0001;
+
+ /**
+ * Bit in {@link #flags}: If set, the service will run in its own
+ * isolated process. Set from the
+ * {@link android.R.attr#isolatedProcess} attribute.
+ */
+ public static final int FLAG_ISOLATED_PROCESS = 0x0002;
+
+ /**
+ * Bit in {@link #flags}: If set, the service can be bound and run in the
+ * calling application's package, rather than the package in which it is
+ * declared. Set from {@link android.R.attr#externalService} attribute.
+ */
+ public static final int FLAG_EXTERNAL_SERVICE = 0x0004;
+
+ /**
+ * Bit in {@link #flags}: If set, the service (which must be isolated)
+ * will be spawned from an Application Zygote, instead of the regular Zygote.
+ * The Application Zygote will pre-initialize the application's class loader,
+ * and call a static callback into the application to allow it to perform
+ * application-specific preloads (such as loading a shared library). Therefore,
+ * spawning from the Application Zygote will typically reduce the service
+ * launch time and reduce its memory usage. The downside of using this flag
+ * is that you will have an additional process (the app zygote itself) that
+ * is taking up memory. Whether actual memory usage is improved therefore
+ * strongly depends on the number of isolated services that an application
+ * starts, and how much memory those services save by preloading. Therefore,
+ * it is recommended to measure memory usage under typical workloads to
+ * determine whether it makes sense to use this flag.
+ */
+ public static final int FLAG_USE_APP_ZYGOTE = 0x0008;
+
+ /**
+ * Bit in {@link #flags} indicating if the service is visible to ephemeral applications.
+ * @hide
+ */
+ public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
+
+ /**
+ * Bit in {@link #flags}: If set, a single instance of the service will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+
+ /**
+ * Options that have been set in the service declaration in the
+ * manifest.
+ * These include:
+ * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS},
+ * {@link #FLAG_SINGLE_USER}.
+ */
+ public int flags;
+
+ /**
+ * The default foreground service type if not been set in manifest file.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_NONE = 0;
+
+ /**
+ * Constant corresponding to <code>dataSync</code> in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
+ * transfer over network between device and cloud.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
+
+ /**
+ * Constant corresponding to <code>mediaPlayback</code> in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Music, video, news or other media playback.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 1 << 1;
+
+ /**
+ * Constant corresponding to <code>phoneCall</code> in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Ongoing operations related to phone calls, video conferencing,
+ * or similar interactive communication.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 1 << 2;
+
+ /**
+ * Constant corresponding to <code>location</code> in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * GPS, map, navigation location update.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 1 << 3;
+
+ /**
+ * Constant corresponding to <code>connectedDevice</code> in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 1 << 4;
+
+ /**
+ * Constant corresponding to {@code mediaProjection} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Managing a media projection session, e.g for screen recording or taking screenshots.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5;
+
+ /**
+ * Constant corresponding to {@code camera} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Use the camera device or record video.
+ * For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} and
+ * above, a foreground service will not be able to access the camera if this type is not
+ * specified in the manifest and in
+ * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6;
+
+ /**
+ * Constant corresponding to {@code microphone} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Use the microphone device or record audio.
+ * For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} and
+ * above, a foreground service will not be able to access the microphone if this type is not
+ * specified in the manifest and in
+ * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
+
+ /**
+ * A special value indicates to use all types set in manifest file.
+ */
+ public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1;
+
+ /**
+ * The set of flags for foreground service type.
+ * The foreground service type is set in {@link android.R.attr#foregroundServiceType}
+ * attribute.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FOREGROUND_SERVICE_TYPE_" }, value = {
+ FOREGROUND_SERVICE_TYPE_MANIFEST,
+ FOREGROUND_SERVICE_TYPE_NONE,
+ FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+ FOREGROUND_SERVICE_TYPE_LOCATION,
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+ FOREGROUND_SERVICE_TYPE_CAMERA,
+ FOREGROUND_SERVICE_TYPE_MICROPHONE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForegroundServiceType {}
+
+ /**
+ * The type of foreground service, set in
+ * {@link android.R.attr#foregroundServiceType} attribute by ORing flags in
+ * {@link ForegroundServiceType}
+ * @hide
+ */
+ public @ForegroundServiceType int mForegroundServiceType = FOREGROUND_SERVICE_TYPE_NONE;
+
+ public ServiceInfo() {
+ }
+
+ public ServiceInfo(ServiceInfo orig) {
+ super(orig);
+ permission = orig.permission;
+ flags = orig.flags;
+ mForegroundServiceType = orig.mForegroundServiceType;
+ }
+
+ /**
+ * Return foreground service type specified in the manifest..
+ * @return foreground service type specified in the manifest.
+ */
+ public @ForegroundServiceType int getForegroundServiceType() {
+ return mForegroundServiceType;
+ }
+
+ public void dump(Printer pw, String prefix) {
+ dump(pw, prefix, DUMP_FLAG_ALL);
+ }
+
+ /** @hide */
+ void dump(Printer pw, String prefix, int dumpFlags) {
+ super.dumpFront(pw, prefix);
+ pw.println(prefix + "permission=" + permission);
+ pw.println(prefix + "flags=0x" + Integer.toHexString(flags));
+ super.dumpBack(pw, prefix, dumpFlags);
+ }
+
+ public String toString() {
+ return "ServiceInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeString8(permission);
+ dest.writeInt(flags);
+ dest.writeInt(mForegroundServiceType);
+ }
+
+ public static final @android.annotation.NonNull Creator<ServiceInfo> CREATOR =
+ new Creator<ServiceInfo>() {
+ public ServiceInfo createFromParcel(Parcel source) {
+ return new ServiceInfo(source);
+ }
+ public ServiceInfo[] newArray(int size) {
+ return new ServiceInfo[size];
+ }
+ };
+
+ private ServiceInfo(Parcel source) {
+ super(source);
+ permission = source.readString8();
+ flags = source.readInt();
+ mForegroundServiceType = source.readInt();
+ }
+}
diff --git a/android/content/pm/SharedLibraryInfo.java b/android/content/pm/SharedLibraryInfo.java
new file mode 100644
index 0000000..7abb694
--- /dev/null
+++ b/android/content/pm/SharedLibraryInfo.java
@@ -0,0 +1,370 @@
+/*
+ * 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.pm;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * This class provides information for a shared library. There are
+ * three types of shared libraries: builtin - non-updatable part of
+ * the OS; dynamic - updatable backwards-compatible dynamically linked;
+ * static - non backwards-compatible emulating static linking.
+ */
+public final class SharedLibraryInfo implements Parcelable {
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ TYPE_BUILTIN,
+ TYPE_DYNAMIC,
+ TYPE_STATIC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Type{}
+
+ /**
+ * Shared library type: this library is a part of the OS
+ * and cannot be updated or uninstalled.
+ */
+ public static final int TYPE_BUILTIN = 0;
+
+ /**
+ * Shared library type: this library is backwards-compatible, can
+ * be updated, and updates can be uninstalled. Clients link against
+ * the latest version of the library.
+ */
+ public static final int TYPE_DYNAMIC = 1;
+
+ /**
+ * Shared library type: this library is <strong>not</strong> backwards
+ * -compatible, can be updated and updates can be uninstalled. Clients
+ * link against a specific version of the library.
+ */
+ public static final int TYPE_STATIC = 2;
+
+ /**
+ * Constant for referring to an undefined version.
+ */
+ public static final int VERSION_UNDEFINED = -1;
+
+ private final String mPath;
+ private final String mPackageName;
+ private final String mName;
+ private final List<String> mCodePaths;
+
+ private final long mVersion;
+ private final @Type int mType;
+ private final boolean mIsNative;
+ private final VersionedPackage mDeclaringPackage;
+ private final List<VersionedPackage> mDependentPackages;
+ private List<SharedLibraryInfo> mDependencies;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param codePaths For a non {@link #TYPE_BUILTIN builtin} library, the locations of jars of
+ * this shared library. Null for builtin library.
+ * @param name The lib name.
+ * @param version The lib version if not builtin.
+ * @param type The lib type.
+ * @param declaringPackage The package that declares the library.
+ * @param dependentPackages The packages that depend on the library.
+ * @param isNative indicate if this shared lib is a native lib or not (i.e. java)
+ *
+ * @hide
+ */
+ public SharedLibraryInfo(String path, String packageName, List<String> codePaths,
+ String name, long version, int type,
+ VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages,
+ List<SharedLibraryInfo> dependencies, boolean isNative) {
+ mPath = path;
+ mPackageName = packageName;
+ mCodePaths = codePaths;
+ mName = name;
+ mVersion = version;
+ mType = type;
+ mDeclaringPackage = declaringPackage;
+ mDependentPackages = dependentPackages;
+ mDependencies = dependencies;
+ mIsNative = isNative;
+ }
+
+ private SharedLibraryInfo(Parcel parcel) {
+ mPath = parcel.readString8();
+ mPackageName = parcel.readString8();
+ if (parcel.readInt() != 0) {
+ mCodePaths = Arrays.asList(parcel.createString8Array());
+ } else {
+ mCodePaths = null;
+ }
+ mName = parcel.readString8();
+ mVersion = parcel.readLong();
+ mType = parcel.readInt();
+ mDeclaringPackage = parcel.readParcelable(null);
+ mDependentPackages = parcel.readArrayList(null);
+ mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
+ mIsNative = parcel.readBoolean();
+ }
+
+ /**
+ * Gets the type of this library.
+ *
+ * @return The library type.
+ */
+ public @Type int getType() {
+ return mType;
+ }
+
+ /**
+ * Tells whether this library is a native shared library or not.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isNative() {
+ return mIsNative;
+ }
+
+ /**
+ * Gets the library name an app defines in its manifest
+ * to depend on the library.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * If the shared library is a jar file, returns the path of that jar. Null otherwise.
+ * Only libraries with TYPE_BUILTIN are in jar files.
+ *
+ * @return The path.
+ *
+ * @hide
+ */
+ public @Nullable String getPath() {
+ return mPath;
+ }
+
+ /**
+ * If the shared library is an apk, returns the package name. Null otherwise.
+ * Only libraries with TYPE_DYNAMIC or TYPE_STATIC are in apks.
+ *
+ * @return The package name.
+ *
+ * @hide
+ */
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Get all code paths for that library.
+ *
+ * @return All code paths.
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull List<String> getAllCodePaths() {
+ if (getPath() != null) {
+ // Builtin library.
+ ArrayList<String> list = new ArrayList<>();
+ list.add(getPath());
+ return list;
+ } else {
+ // Static or dynamic library.
+ return Objects.requireNonNull(mCodePaths);
+ }
+ }
+
+ /**
+ * Add a library dependency to that library. Note that this
+ * should be called under the package manager lock.
+ *
+ * @hide
+ */
+ public void addDependency(@Nullable SharedLibraryInfo info) {
+ if (info == null) {
+ // For convenience of the caller, allow null to be passed.
+ // This can happen when we create the dependencies of builtin
+ // libraries.
+ return;
+ }
+ if (mDependencies == null) {
+ mDependencies = new ArrayList<>();
+ }
+ mDependencies.add(info);
+ }
+
+ /**
+ * Clear all dependencies.
+ *
+ * @hide
+ */
+ public void clearDependencies() {
+ mDependencies = null;
+ }
+
+ /**
+ * Gets the libraries this library directly depends on. Note that
+ * the package manager prevents recursive dependencies when installing
+ * a package.
+ *
+ * @return The dependencies.
+ *
+ * @hide
+ */
+ public @Nullable List<SharedLibraryInfo> getDependencies() {
+ return mDependencies;
+ }
+
+ /**
+ * @deprecated Use {@link #getLongVersion()} instead.
+ */
+ @Deprecated
+ public @IntRange(from = -1) int getVersion() {
+ return mVersion < 0 ? (int) mVersion : (int) (mVersion & 0x7fffffff);
+ }
+
+ /**
+ * Gets the version of the library. For {@link #TYPE_STATIC static} libraries
+ * this is the declared version and for {@link #TYPE_DYNAMIC dynamic} and
+ * {@link #TYPE_BUILTIN builtin} it is {@link #VERSION_UNDEFINED} as these
+ * are not versioned.
+ *
+ * @return The version.
+ */
+ public @IntRange(from = -1) long getLongVersion() {
+ return mVersion;
+ }
+
+ /**
+ * @removed
+ */
+ public boolean isBuiltin() {
+ return mType == TYPE_BUILTIN;
+ }
+
+ /**
+ * @removed
+ */
+ public boolean isDynamic() {
+ return mType == TYPE_DYNAMIC;
+ }
+
+ /**
+ * @removed
+ */
+ public boolean isStatic() {
+ return mType == TYPE_STATIC;
+ }
+
+ /**
+ * Gets the package that declares the library.
+ *
+ * @return The package declaring the library.
+ */
+ public @NonNull VersionedPackage getDeclaringPackage() {
+ return mDeclaringPackage;
+ }
+
+ /**
+ * Gets the packages that depend on the library.
+ *
+ * @return The dependent packages.
+ */
+ public @NonNull List<VersionedPackage> getDependentPackages() {
+ if (mDependentPackages == null) {
+ return Collections.emptyList();
+ }
+ return mDependentPackages;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SharedLibraryInfo{name:" + mName + ", type:" + typeToString(mType)
+ + ", version:" + mVersion + (!getDependentPackages().isEmpty()
+ ? " has dependents" : "") + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString8(mPath);
+ parcel.writeString8(mPackageName);
+ if (mCodePaths != null) {
+ parcel.writeInt(1);
+ parcel.writeString8Array(mCodePaths.toArray(new String[mCodePaths.size()]));
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeString8(mName);
+ parcel.writeLong(mVersion);
+ parcel.writeInt(mType);
+ parcel.writeParcelable(mDeclaringPackage, flags);
+ parcel.writeList(mDependentPackages);
+ parcel.writeTypedList(mDependencies);
+ parcel.writeBoolean(mIsNative);
+ }
+
+ private static String typeToString(int type) {
+ switch (type) {
+ case TYPE_BUILTIN: {
+ return "builtin";
+ }
+ case TYPE_DYNAMIC: {
+ return "dynamic";
+ }
+ case TYPE_STATIC: {
+ return "static";
+ }
+ default: {
+ return "unknown";
+ }
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<SharedLibraryInfo> CREATOR =
+ new Parcelable.Creator<SharedLibraryInfo>() {
+ public SharedLibraryInfo createFromParcel(Parcel source) {
+ return new SharedLibraryInfo(source);
+ }
+
+ public SharedLibraryInfo[] newArray(int size) {
+ return new SharedLibraryInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
new file mode 100644
index 0000000..a264beb
--- /dev/null
+++ b/android/content/pm/ShortcutInfo.java
@@ -0,0 +1,2519 @@
+/*
+ * 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.Notification;
+import android.app.Person;
+import android.app.TaskStackBuilder;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.LocusId;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureContext;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represents a shortcut that can be published via {@link ShortcutManager}.
+ *
+ * @see ShortcutManager
+ */
+public final class ShortcutInfo implements Parcelable {
+ static final String TAG = "Shortcut";
+
+ private static final String RES_TYPE_STRING = "string";
+
+ private static final String ANDROID_PACKAGE_NAME = "android";
+
+ private static final int IMPLICIT_RANK_MASK = 0x7fffffff;
+
+ /** @hide */
+ public static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK;
+
+ /** @hide */
+ public static final int RANK_NOT_SET = Integer.MAX_VALUE;
+
+ /** @hide */
+ public static final int FLAG_DYNAMIC = 1 << 0;
+
+ /** @hide */
+ public static final int FLAG_PINNED = 1 << 1;
+
+ /** @hide */
+ public static final int FLAG_HAS_ICON_RES = 1 << 2;
+
+ /** @hide */
+ public static final int FLAG_HAS_ICON_FILE = 1 << 3;
+
+ /** @hide */
+ public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
+
+ /** @hide */
+ public static final int FLAG_MANIFEST = 1 << 5;
+
+ /** @hide */
+ public static final int FLAG_DISABLED = 1 << 6;
+
+ /** @hide */
+ public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
+
+ /** @hide */
+ public static final int FLAG_IMMUTABLE = 1 << 8;
+
+ /** @hide */
+ public static final int FLAG_ADAPTIVE_BITMAP = 1 << 9;
+
+ /** @hide */
+ public static final int FLAG_RETURNED_BY_SERVICE = 1 << 10;
+
+ /** @hide When this is set, the bitmap icon is waiting to be saved. */
+ public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
+
+ /**
+ * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been
+ * installed yet.
+ * @hide
+ */
+ public static final int FLAG_SHADOW = 1 << 12;
+
+ /** @hide */
+ public static final int FLAG_LONG_LIVED = 1 << 13;
+
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_NOTIFICATIONS = 1 << 14;
+
+ /** @hide */
+ public static final int FLAG_HAS_ICON_URI = 1 << 15;
+
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_PEOPLE_TILE = 1 << 29;
+
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_BUBBLES = 1 << 30;
+
+ /** @hide */
+ public static final int FLAG_CACHED_ALL =
+ FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;
+
+ /**
+ * Bitmask-based flags indicating different states associated with the shortcut. Note that if
+ * new value is added here, consider adding also the corresponding string representation and
+ * queries in {@link AppSearchShortcutInfo}.
+ *
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_DYNAMIC,
+ FLAG_PINNED,
+ FLAG_HAS_ICON_RES,
+ FLAG_HAS_ICON_FILE,
+ FLAG_KEY_FIELDS_ONLY,
+ FLAG_MANIFEST,
+ FLAG_DISABLED,
+ FLAG_STRINGS_RESOLVED,
+ FLAG_IMMUTABLE,
+ FLAG_ADAPTIVE_BITMAP,
+ FLAG_RETURNED_BY_SERVICE,
+ FLAG_ICON_FILE_PENDING_SAVE,
+ FLAG_SHADOW,
+ FLAG_LONG_LIVED,
+ FLAG_HAS_ICON_URI,
+ FLAG_CACHED_NOTIFICATIONS,
+ FLAG_CACHED_BUBBLES,
+ FLAG_CACHED_PEOPLE_TILE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShortcutFlags {}
+
+ // Cloning options.
+
+ /** @hide */
+ private static final int CLONE_REMOVE_ICON = 1 << 0;
+
+ /** @hide */
+ private static final int CLONE_REMOVE_INTENT = 1 << 1;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_RES_NAMES = 1 << 3;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_PERSON = 1 << 4;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT
+ | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT
+ | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_FOR_APP_PREDICTION = CLONE_REMOVE_ICON
+ | CLONE_REMOVE_RES_NAMES;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "CLONE_" }, value = {
+ CLONE_REMOVE_ICON,
+ CLONE_REMOVE_INTENT,
+ CLONE_REMOVE_NON_KEY_INFO,
+ CLONE_REMOVE_RES_NAMES,
+ CLONE_REMOVE_PERSON,
+ CLONE_REMOVE_FOR_CREATOR,
+ CLONE_REMOVE_FOR_LAUNCHER,
+ CLONE_REMOVE_FOR_LAUNCHER_APPROVAL,
+ CLONE_REMOVE_FOR_APP_PREDICTION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CloneFlags {}
+
+ /**
+ * Shortcut is not disabled.
+ */
+ public static final int DISABLED_REASON_NOT_DISABLED = 0;
+
+ /**
+ * Shortcut has been disabled by the publisher app with the
+ * {@link ShortcutManager#disableShortcuts(List)} API.
+ */
+ public static final int DISABLED_REASON_BY_APP = 1;
+
+ /**
+ * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut
+ * no longer exists.)
+ */
+ public static final int DISABLED_REASON_APP_CHANGED = 2;
+
+ /**
+ * Shortcut is disabled for an unknown reason.
+ */
+ public static final int DISABLED_REASON_UNKNOWN = 3;
+
+ /**
+ * A disabled reason that's equal to or bigger than this is due to backup and restore issue.
+ * A shortcut with such a reason wil be visible to the launcher, but not to the publisher.
+ * ({@link #isVisibleToPublisher()} will be false.)
+ */
+ private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100;
+
+ /**
+ * Shortcut has been restored from the previous device, but the publisher app on the current
+ * device is of a lower version. The shortcut will not be usable until the app is upgraded to
+ * the same version or higher.
+ */
+ public static final int DISABLED_REASON_VERSION_LOWER = 100;
+
+ /**
+ * Shortcut has not been restored because the publisher app does not support backup and restore.
+ */
+ public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101;
+
+ /**
+ * Shortcut has not been restored because the publisher app's signature has changed.
+ */
+ public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102;
+
+ /**
+ * Shortcut has not been restored for unknown reason.
+ */
+ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+
+ /** @hide */
+ @IntDef(prefix = { "DISABLED_REASON_" }, value = {
+ DISABLED_REASON_NOT_DISABLED,
+ DISABLED_REASON_BY_APP,
+ DISABLED_REASON_APP_CHANGED,
+ DISABLED_REASON_UNKNOWN,
+ DISABLED_REASON_VERSION_LOWER,
+ DISABLED_REASON_BACKUP_NOT_SUPPORTED,
+ DISABLED_REASON_SIGNATURE_MISMATCH,
+ DISABLED_REASON_OTHER_RESTORE_ISSUE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisabledReason{}
+
+ /**
+ * Return a label for disabled reasons, which are *not* supposed to be shown to the user.
+ * @hide
+ */
+ public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) {
+ switch (disabledReason) {
+ case DISABLED_REASON_NOT_DISABLED:
+ return "[Not disabled]";
+ case DISABLED_REASON_BY_APP:
+ return "[Disabled: by app]";
+ case DISABLED_REASON_APP_CHANGED:
+ return "[Disabled: app changed]";
+ case DISABLED_REASON_VERSION_LOWER:
+ return "[Disabled: lower version]";
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return "[Disabled: backup not supported]";
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return "[Disabled: signature mismatch]";
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return "[Disabled: unknown restore issue]";
+ }
+ return "[Disabled: unknown reason:" + disabledReason + "]";
+ }
+
+ /**
+ * Return a label for a disabled reason for shortcuts that are disabled due to a backup and
+ * restore issue. If the reason is not due to backup & restore, then it'll return null.
+ *
+ * This method returns localized, user-facing strings, which will be returned by
+ * {@link #getDisabledMessage()}.
+ *
+ * @hide
+ */
+ public static String getDisabledReasonForRestoreIssue(Context context,
+ @DisabledReason int disabledReason) {
+ final Resources res = context.getResources();
+
+ switch (disabledReason) {
+ case DISABLED_REASON_VERSION_LOWER:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restored_on_lower_version);
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_not_supported);
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_signature_mismatch);
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_unknown_issue);
+ case DISABLED_REASON_UNKNOWN:
+ return res.getString(
+ com.android.internal.R.string.shortcut_disabled_reason_unknown);
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) {
+ return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
+ }
+
+ /**
+ * Shortcut category for messaging related actions, such as chat.
+ */
+ public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+
+ private final String mId;
+
+ @NonNull
+ private final String mPackageName;
+
+ @Nullable
+ private ComponentName mActivity;
+
+ @Nullable
+ private Icon mIcon;
+
+ private int mTitleResId;
+
+ private String mTitleResName;
+
+ @Nullable
+ private CharSequence mTitle;
+
+ private int mTextResId;
+
+ private String mTextResName;
+
+ @Nullable
+ private CharSequence mText;
+
+ private int mDisabledMessageResId;
+
+ private String mDisabledMessageResName;
+
+ @Nullable
+ private CharSequence mDisabledMessage;
+
+ @Nullable
+ private ArraySet<String> mCategories;
+
+ /**
+ * Intents *with extras removed*.
+ */
+ @Nullable
+ private Intent[] mIntents;
+
+ /**
+ * Extras for the intents.
+ */
+ @Nullable
+ private PersistableBundle[] mIntentPersistableExtrases;
+
+ @Nullable
+ private Person[] mPersons;
+
+ @Nullable
+ private LocusId mLocusId;
+
+ private int mRank;
+
+ /**
+ * Internally used for auto-rank-adjustment.
+ *
+ * RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing.
+ * The rest of the bits are used to denote the order in which shortcuts are passed to
+ * APIs, which is used to preserve the argument order when ranks are tie.
+ */
+ private int mImplicitRank;
+
+ @Nullable
+ private PersistableBundle mExtras;
+
+ private long mLastChangedTimestamp;
+
+ // Internal use only.
+ @ShortcutFlags
+ private int mFlags;
+
+ // Internal use only.
+ private int mIconResId;
+
+ private String mIconResName;
+
+ // Internal use only.
+ private String mIconUri;
+
+ // Internal use only.
+ @Nullable
+ private String mBitmapPath;
+
+ private final int mUserId;
+
+ /** @hide */
+ public static final int VERSION_CODE_UNKNOWN = -1;
+
+ private int mDisabledReason;
+
+ @Nullable private String mStartingThemeResName;
+
+ private ShortcutInfo(Builder b) {
+ mUserId = b.mContext.getUserId();
+
+ mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
+
+ // Note we can't do other null checks here because SM.updateShortcuts() takes partial
+ // information.
+ mPackageName = b.mContext.getPackageName();
+ mActivity = b.mActivity;
+ mIcon = b.mIcon;
+ mTitle = b.mTitle;
+ mTitleResId = b.mTitleResId;
+ mText = b.mText;
+ mTextResId = b.mTextResId;
+ mDisabledMessage = b.mDisabledMessage;
+ mDisabledMessageResId = b.mDisabledMessageResId;
+ mCategories = cloneCategories(b.mCategories);
+ mIntents = cloneIntents(b.mIntents);
+ fixUpIntentExtras();
+ mPersons = clonePersons(b.mPersons);
+ if (b.mIsLongLived) {
+ setLongLived();
+ }
+ mRank = b.mRank;
+ mExtras = b.mExtras;
+ mLocusId = b.mLocusId;
+
+ mStartingThemeResName = b.mStartingThemeResId != 0
+ ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null;
+ updateTimestamp();
+ }
+
+ /**
+ * Extract extras from {@link #mIntents} and set them to {@link #mIntentPersistableExtrases}
+ * as {@link PersistableBundle}, and remove extras from the original intents.
+ */
+ private void fixUpIntentExtras() {
+ if (mIntents == null) {
+ mIntentPersistableExtrases = null;
+ return;
+ }
+ mIntentPersistableExtrases = new PersistableBundle[mIntents.length];
+ for (int i = 0; i < mIntents.length; i++) {
+ final Intent intent = mIntents[i];
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ mIntentPersistableExtrases[i] = null;
+ } else {
+ mIntentPersistableExtrases[i] = new PersistableBundle(extras);
+ intent.replaceExtras((Bundle) null);
+ }
+ }
+ }
+
+ private static ArraySet<String> cloneCategories(Set<String> source) {
+ if (source == null) {
+ return null;
+ }
+ final ArraySet<String> ret = new ArraySet<>(source.size());
+ for (CharSequence s : source) {
+ if (!TextUtils.isEmpty(s)) {
+ ret.add(s.toString().intern());
+ }
+ }
+ return ret;
+ }
+
+ private static Intent[] cloneIntents(Intent[] intents) {
+ if (intents == null) {
+ return null;
+ }
+ final Intent[] ret = new Intent[intents.length];
+ for (int i = 0; i < ret.length; i++) {
+ if (intents[i] != null) {
+ ret[i] = new Intent(intents[i]);
+ }
+ }
+ return ret;
+ }
+
+ private static PersistableBundle[] clonePersistableBundle(PersistableBundle[] bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final PersistableBundle[] ret = new PersistableBundle[bundle.length];
+ for (int i = 0; i < ret.length; i++) {
+ if (bundle[i] != null) {
+ ret[i] = new PersistableBundle(bundle[i]);
+ }
+ }
+ return ret;
+ }
+
+ private static Person[] clonePersons(Person[] persons) {
+ if (persons == null) {
+ return null;
+ }
+ final Person[] ret = new Person[persons.length];
+ for (int i = 0; i < ret.length; i++) {
+ if (persons[i] != null) {
+ // Don't need to keep the icon, remove it to save space
+ ret[i] = persons[i].toBuilder().setIcon(null).build();
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Throws if any of the mandatory fields is not set.
+ *
+ * @hide
+ */
+ public void enforceMandatoryFields(boolean forPinned) {
+ Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
+ if (!forPinned) {
+ Objects.requireNonNull(mActivity, "Activity must be provided");
+ }
+ if (mTitle == null && mTitleResId == 0) {
+ throw new IllegalArgumentException("Short label must be provided");
+ }
+ Objects.requireNonNull(mIntents, "Shortcut Intent must be provided");
+ Preconditions.checkArgument(mIntents.length > 0, "Shortcut Intent must be provided");
+ }
+
+ /**
+ * Copy constructor.
+ */
+ private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) {
+ mUserId = source.mUserId;
+ mId = source.mId;
+ mPackageName = source.mPackageName;
+ mActivity = source.mActivity;
+ mFlags = source.mFlags;
+ mLastChangedTimestamp = source.mLastChangedTimestamp;
+ mDisabledReason = source.mDisabledReason;
+ mLocusId = source.mLocusId;
+
+ // Just always keep it since it's cheep.
+ mIconResId = source.mIconResId;
+
+ if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
+
+ if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
+ mIcon = source.mIcon;
+ mBitmapPath = source.mBitmapPath;
+ mIconUri = source.mIconUri;
+ }
+
+ mTitle = source.mTitle;
+ mTitleResId = source.mTitleResId;
+ mText = source.mText;
+ mTextResId = source.mTextResId;
+ mDisabledMessage = source.mDisabledMessage;
+ mDisabledMessageResId = source.mDisabledMessageResId;
+ mCategories = cloneCategories(source.mCategories);
+ if ((cloneFlags & CLONE_REMOVE_PERSON) == 0) {
+ mPersons = clonePersons(source.mPersons);
+ }
+ if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
+ mIntents = cloneIntents(source.mIntents);
+ mIntentPersistableExtrases =
+ clonePersistableBundle(source.mIntentPersistableExtrases);
+ }
+ mRank = source.mRank;
+ mExtras = source.mExtras;
+
+ if ((cloneFlags & CLONE_REMOVE_RES_NAMES) == 0) {
+ mTitleResName = source.mTitleResName;
+ mTextResName = source.mTextResName;
+ mDisabledMessageResName = source.mDisabledMessageResName;
+ mIconResName = source.mIconResName;
+ }
+ } else {
+ // Set this bit.
+ mFlags |= FLAG_KEY_FIELDS_ONLY;
+ }
+ mStartingThemeResName = source.mStartingThemeResName;
+ }
+
+ /**
+ * Load a string resource from the publisher app.
+ *
+ * @param resId resource ID
+ * @param defValue default value to be returned when the specified resource isn't found.
+ */
+ private CharSequence getResourceString(Resources res, int resId, CharSequence defValue) {
+ try {
+ return res.getString(resId);
+ } catch (NotFoundException e) {
+ Log.e(TAG, "Resource for ID=" + resId + " not found in package " + mPackageName);
+ return defValue;
+ }
+ }
+
+ /**
+ * Load the string resources for the text fields and set them to the actual value fields.
+ * This will set {@link #FLAG_STRINGS_RESOLVED}.
+ *
+ * @param res {@link Resources} for the publisher. Must have been loaded with
+ * {@link PackageManager#getResourcesForApplication(String)}.
+ *
+ * @hide
+ */
+ public void resolveResourceStrings(@NonNull Resources res) {
+ mFlags |= FLAG_STRINGS_RESOLVED;
+
+ if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) {
+ return; // Bail early.
+ }
+
+ if (mTitleResId != 0) {
+ mTitle = getResourceString(res, mTitleResId, mTitle);
+ }
+ if (mTextResId != 0) {
+ mText = getResourceString(res, mTextResId, mText);
+ }
+ if (mDisabledMessageResId != 0) {
+ mDisabledMessage = getResourceString(res, mDisabledMessageResId, mDisabledMessage);
+ }
+ }
+
+ /**
+ * Look up resource name for a given resource ID.
+ *
+ * @return a simple resource name (e.g. "text_1") when {@code withType} is false, or with the
+ * type (e.g. "string/text_1").
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static String lookUpResourceName(@NonNull Resources res, int resId, boolean withType,
+ @NonNull String packageName) {
+ if (resId == 0) {
+ return null;
+ }
+ try {
+ final String fullName = res.getResourceName(resId);
+
+ if (ANDROID_PACKAGE_NAME.equals(getResourcePackageName(fullName))) {
+ // If it's a framework resource, the value won't change, so just return the ID
+ // value as a string.
+ return String.valueOf(resId);
+ }
+ return withType ? getResourceTypeAndEntryName(fullName)
+ : getResourceEntryName(fullName);
+ } catch (NotFoundException e) {
+ Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+ + ". Resource IDs may change when the application is upgraded, and the system"
+ + " may not be able to find the correct resource.");
+ return null;
+ }
+ }
+
+ /**
+ * Extract the package name from a fully-donated resource name.
+ * e.g. "com.android.app1:drawable/icon1" -> "com.android.app1"
+ * @hide
+ */
+ @VisibleForTesting
+ public static String getResourcePackageName(@NonNull String fullResourceName) {
+ final int p1 = fullResourceName.indexOf(':');
+ if (p1 < 0) {
+ return null;
+ }
+ return fullResourceName.substring(0, p1);
+ }
+
+ /**
+ * Extract the type name from a fully-donated resource name.
+ * e.g. "com.android.app1:drawable/icon1" -> "drawable"
+ * @hide
+ */
+ @VisibleForTesting
+ public static String getResourceTypeName(@NonNull String fullResourceName) {
+ final int p1 = fullResourceName.indexOf(':');
+ if (p1 < 0) {
+ return null;
+ }
+ final int p2 = fullResourceName.indexOf('/', p1 + 1);
+ if (p2 < 0) {
+ return null;
+ }
+ return fullResourceName.substring(p1 + 1, p2);
+ }
+
+ /**
+ * Extract the type name + the entry name from a fully-donated resource name.
+ * e.g. "com.android.app1:drawable/icon1" -> "drawable/icon1"
+ * @hide
+ */
+ @VisibleForTesting
+ public static String getResourceTypeAndEntryName(@NonNull String fullResourceName) {
+ final int p1 = fullResourceName.indexOf(':');
+ if (p1 < 0) {
+ return null;
+ }
+ return fullResourceName.substring(p1 + 1);
+ }
+
+ /**
+ * Extract the entry name from a fully-donated resource name.
+ * e.g. "com.android.app1:drawable/icon1" -> "icon1"
+ * @hide
+ */
+ @VisibleForTesting
+ public static String getResourceEntryName(@NonNull String fullResourceName) {
+ final int p1 = fullResourceName.indexOf('/');
+ if (p1 < 0) {
+ return null;
+ }
+ return fullResourceName.substring(p1 + 1);
+ }
+
+ /**
+ * Return the resource ID for a given resource ID.
+ *
+ * Basically its' a wrapper over {@link Resources#getIdentifier(String, String, String)}, except
+ * if {@code resourceName} is an integer then it'll just return its value. (Which also the
+ * aforementioned method would do internally, but not documented, so doing here explicitly.)
+ *
+ * @param res {@link Resources} for the publisher. Must have been loaded with
+ * {@link PackageManager#getResourcesForApplication(String)}.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static int lookUpResourceId(@NonNull Resources res, @Nullable String resourceName,
+ @Nullable String resourceType, String packageName) {
+ if (resourceName == null) {
+ return 0;
+ }
+ try {
+ try {
+ // It the name can be parsed as an integer, just use it.
+ return Integer.parseInt(resourceName);
+ } catch (NumberFormatException ignore) {
+ }
+
+ return res.getIdentifier(resourceName, resourceType, packageName);
+ } catch (NotFoundException e) {
+ Log.e(TAG, "Resource ID for name=" + resourceName + " not found in package "
+ + packageName);
+ return 0;
+ }
+ }
+
+ /**
+ * Look up resource names from the resource IDs for the icon res and the text fields, and fill
+ * in the resource name fields.
+ *
+ * @param res {@link Resources} for the publisher. Must have been loaded with
+ * {@link PackageManager#getResourcesForApplication(String)}.
+ *
+ * @hide
+ */
+ public void lookupAndFillInResourceNames(@NonNull Resources res) {
+ if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)
+ && (mIconResId == 0)) {
+ return; // Bail early.
+ }
+
+ // We don't need types for strings because their types are always "string".
+ mTitleResName = lookUpResourceName(res, mTitleResId, /*withType=*/ false, mPackageName);
+ mTextResName = lookUpResourceName(res, mTextResId, /*withType=*/ false, mPackageName);
+ mDisabledMessageResName = lookUpResourceName(res, mDisabledMessageResId,
+ /*withType=*/ false, mPackageName);
+
+ // But icons have multiple possible types, so include the type.
+ mIconResName = lookUpResourceName(res, mIconResId, /*withType=*/ true, mPackageName);
+ }
+
+ /**
+ * Look up resource IDs from the resource names for the icon res and the text fields, and fill
+ * in the resource ID fields.
+ *
+ * This is called when an app is updated.
+ *
+ * @hide
+ */
+ public void lookupAndFillInResourceIds(@NonNull Resources res) {
+ if ((mTitleResName == null) && (mTextResName == null) && (mDisabledMessageResName == null)
+ && (mIconResName == null)) {
+ return; // Bail early.
+ }
+
+ mTitleResId = lookUpResourceId(res, mTitleResName, RES_TYPE_STRING, mPackageName);
+ mTextResId = lookUpResourceId(res, mTextResName, RES_TYPE_STRING, mPackageName);
+ mDisabledMessageResId = lookUpResourceId(res, mDisabledMessageResName, RES_TYPE_STRING,
+ mPackageName);
+
+ // mIconResName already contains the type, so the third argument is not needed.
+ mIconResId = lookUpResourceId(res, mIconResName, null, mPackageName);
+ }
+
+ /**
+ * Copy a {@link ShortcutInfo}, optionally removing fields.
+ * @hide
+ */
+ public ShortcutInfo clone(@CloneFlags int cloneFlags) {
+ return new ShortcutInfo(this, cloneFlags);
+ }
+
+ /**
+ * @hide
+ *
+ * @isUpdating set true if it's "update", as opposed to "replace".
+ */
+ public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
+ if (isUpdating) {
+ Preconditions.checkState(isVisibleToPublisher(),
+ "[Framework BUG] Invisible shortcuts can't be updated");
+ }
+ Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
+ Preconditions.checkState(mId.equals(source.mId), "ID must match");
+ Preconditions.checkState(mPackageName.equals(source.mPackageName),
+ "Package name must match");
+
+ if (isVisibleToPublisher()) {
+ // Don't do this check for restore-blocked shortcuts.
+ Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+ }
+ }
+
+ /**
+ * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information
+ * will be overwritten. The timestamp will *not* be updated to be consistent with other
+ * setters (and also the clock is not injectable in this file).
+ *
+ * - Flags will not change
+ * - mBitmapPath will not change
+ * - Current time will be set to timestamp
+ *
+ * @throws IllegalStateException if source is not compatible.
+ *
+ * @hide
+ */
+ public void copyNonNullFieldsFrom(ShortcutInfo source) {
+ ensureUpdatableWith(source, /*isUpdating=*/ true);
+
+ if (source.mActivity != null) {
+ mActivity = source.mActivity;
+ }
+
+ if (source.mIcon != null) {
+ mIcon = source.mIcon;
+
+ mIconResId = 0;
+ mIconResName = null;
+ mBitmapPath = null;
+ mIconUri = null;
+ }
+ if (source.mTitle != null) {
+ mTitle = source.mTitle;
+ mTitleResId = 0;
+ mTitleResName = null;
+ } else if (source.mTitleResId != 0) {
+ mTitle = null;
+ mTitleResId = source.mTitleResId;
+ mTitleResName = null;
+ }
+
+ if (source.mText != null) {
+ mText = source.mText;
+ mTextResId = 0;
+ mTextResName = null;
+ } else if (source.mTextResId != 0) {
+ mText = null;
+ mTextResId = source.mTextResId;
+ mTextResName = null;
+ }
+ if (source.mDisabledMessage != null) {
+ mDisabledMessage = source.mDisabledMessage;
+ mDisabledMessageResId = 0;
+ mDisabledMessageResName = null;
+ } else if (source.mDisabledMessageResId != 0) {
+ mDisabledMessage = null;
+ mDisabledMessageResId = source.mDisabledMessageResId;
+ mDisabledMessageResName = null;
+ }
+ if (source.mCategories != null) {
+ mCategories = cloneCategories(source.mCategories);
+ }
+ if (source.mPersons != null) {
+ mPersons = clonePersons(source.mPersons);
+ }
+ if (source.mIntents != null) {
+ mIntents = cloneIntents(source.mIntents);
+ mIntentPersistableExtrases =
+ clonePersistableBundle(source.mIntentPersistableExtrases);
+ }
+ if (source.mRank != RANK_NOT_SET) {
+ mRank = source.mRank;
+ }
+ if (source.mExtras != null) {
+ mExtras = source.mExtras;
+ }
+
+ if (source.mLocusId != null) {
+ mLocusId = source.mLocusId;
+ }
+ if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) {
+ mStartingThemeResName = source.mStartingThemeResName;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static Icon validateIcon(Icon icon) {
+ switch (icon.getType()) {
+ case Icon.TYPE_RESOURCE:
+ case Icon.TYPE_BITMAP:
+ case Icon.TYPE_ADAPTIVE_BITMAP:
+ case Icon.TYPE_URI:
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
+ break; // OK
+ default:
+ throw getInvalidIconException();
+ }
+ if (icon.hasTint()) {
+ throw new IllegalArgumentException("Icons with tints are not supported");
+ }
+
+ return icon;
+ }
+
+ /** @hide */
+ public static IllegalArgumentException getInvalidIconException() {
+ return new IllegalArgumentException("Unsupported icon type:"
+ +" only the bitmap and resource types are supported");
+ }
+
+ /**
+ * Builder class for {@link ShortcutInfo} objects.
+ *
+ * @see ShortcutManager
+ */
+ public static class Builder {
+ private final Context mContext;
+
+ private String mId;
+
+ private ComponentName mActivity;
+
+ private Icon mIcon;
+
+ private int mTitleResId;
+
+ private CharSequence mTitle;
+
+ private int mTextResId;
+
+ private CharSequence mText;
+
+ private int mDisabledMessageResId;
+
+ private CharSequence mDisabledMessage;
+
+ private Set<String> mCategories;
+
+ private Intent[] mIntents;
+
+ private Person[] mPersons;
+
+ private boolean mIsLongLived;
+
+ private int mRank = RANK_NOT_SET;
+
+ private PersistableBundle mExtras;
+
+ private LocusId mLocusId;
+
+ private int mStartingThemeResId;
+
+ /**
+ * Old style constructor.
+ * @hide
+ */
+ @Deprecated
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Used with the old style constructor, kept for unit tests.
+ * @hide
+ */
+ @NonNull
+ @Deprecated
+ public Builder setId(@NonNull String id) {
+ mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
+ return this;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context Client context.
+ * @param id ID of the shortcut.
+ */
+ public Builder(Context context, String id) {
+ mContext = context;
+ mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
+ }
+
+ /**
+ * Sets the {@link LocusId} associated with this shortcut.
+ *
+ * <p>This method should be called when the {@link LocusId} is used in other places (such
+ * as {@link Notification} and {@link ContentCaptureContext}) so the device's intelligence
+ * services can correlate them.
+ */
+ @NonNull
+ public Builder setLocusId(@NonNull LocusId locusId) {
+ mLocusId = Objects.requireNonNull(locusId, "locusId cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the target activity. A shortcut will be shown along with this activity's icon
+ * on the launcher.
+ *
+ * When selecting a target activity, keep the following in mind:
+ * <ul>
+ * <li>All dynamic shortcuts must have a target activity. When a shortcut with no target
+ * activity is published using
+ * {@link ShortcutManager#addDynamicShortcuts(List)} or
+ * {@link ShortcutManager#setDynamicShortcuts(List)},
+ * the first main activity defined in the app's <code>AndroidManifest.xml</code>
+ * file is used.
+ *
+ * <li>Only "main" activities—ones that define the {@link Intent#ACTION_MAIN}
+ * and {@link Intent#CATEGORY_LAUNCHER} intent filters—can be target
+ * activities.
+ *
+ * <li>By default, the first main activity defined in the app's manifest is
+ * the target activity.
+ *
+ * <li>A target activity must belong to the publisher app.
+ * </ul>
+ *
+ * @see ShortcutInfo#getActivity()
+ */
+ @NonNull
+ public Builder setActivity(@NonNull ComponentName activity) {
+ mActivity = Objects.requireNonNull(activity, "activity cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets an icon of a shortcut.
+ *
+ * <p>Icons are not available on {@link ShortcutInfo} instances
+ * returned by {@link ShortcutManager} or {@link LauncherApps}. The default launcher
+ * app can use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}
+ * or {@link LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int)} to fetch
+ * shortcut icons.
+ *
+ * <p>Tints set with {@link Icon#setTint} or {@link Icon#setTintList} are not supported
+ * and will be ignored.
+ *
+ * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)},
+ * {@link Icon#createWithAdaptiveBitmap(Bitmap)}
+ * and {@link Icon#createWithResource} are supported.
+ * Other types, such as URI-based icons, are not supported.
+ *
+ * @see LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)
+ * @see LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int)
+ */
+ @NonNull
+ public Builder setIcon(Icon icon) {
+ mIcon = validateIcon(icon);
+ return this;
+ }
+
+ /**
+ * Sets a theme resource id for the splash screen.
+ */
+ @NonNull
+ public Builder setStartingTheme(int themeResId) {
+ mStartingThemeResId = themeResId;
+ return this;
+ }
+
+ /**
+ * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests
+ * use it.)
+ */
+ @Deprecated
+ public Builder setShortLabelResId(int shortLabelResId) {
+ Preconditions.checkState(mTitle == null, "shortLabel already set");
+ mTitleResId = shortLabelResId;
+ return this;
+ }
+
+ /**
+ * Sets the short title of a shortcut.
+ *
+ * <p>This is a mandatory field when publishing a new shortcut with
+ * {@link ShortcutManager#addDynamicShortcuts(List)} or
+ * {@link ShortcutManager#setDynamicShortcuts(List)}.
+ *
+ * <p>This field is intended to be a concise description of a shortcut.
+ *
+ * <p>The recommended maximum length is 10 characters.
+ *
+ * @see ShortcutInfo#getShortLabel()
+ */
+ @NonNull
+ public Builder setShortLabel(@NonNull CharSequence shortLabel) {
+ Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
+ mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty");
+ return this;
+ }
+
+ /**
+ * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests
+ * use it.)
+ */
+ @Deprecated
+ public Builder setLongLabelResId(int longLabelResId) {
+ Preconditions.checkState(mText == null, "longLabel already set");
+ mTextResId = longLabelResId;
+ return this;
+ }
+
+ /**
+ * Sets the text of a shortcut.
+ *
+ * <p>This field is intended to be more descriptive than the shortcut title. The launcher
+ * shows this instead of the short title when it has enough space.
+ *
+ * <p>The recommend maximum length is 25 characters.
+ *
+ * @see ShortcutInfo#getLongLabel()
+ */
+ @NonNull
+ public Builder setLongLabel(@NonNull CharSequence longLabel) {
+ Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
+ mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty");
+ return this;
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Deprecated
+ public Builder setTitle(@NonNull CharSequence value) {
+ return setShortLabel(value);
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Deprecated
+ public Builder setTitleResId(int value) {
+ return setShortLabelResId(value);
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Deprecated
+ public Builder setText(@NonNull CharSequence value) {
+ return setLongLabel(value);
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Deprecated
+ public Builder setTextResId(int value) {
+ return setLongLabelResId(value);
+ }
+
+ /**
+ * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests
+ * use it.)
+ */
+ @Deprecated
+ public Builder setDisabledMessageResId(int disabledMessageResId) {
+ Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
+ mDisabledMessageResId = disabledMessageResId;
+ return this;
+ }
+
+ /**
+ * Sets the message that should be shown when the user attempts to start a shortcut that
+ * is disabled.
+ *
+ * @see ShortcutInfo#getDisabledMessage()
+ */
+ @NonNull
+ public Builder setDisabledMessage(@NonNull CharSequence disabledMessage) {
+ Preconditions.checkState(
+ mDisabledMessageResId == 0, "disabledMessageResId already set");
+ mDisabledMessage =
+ Preconditions.checkStringNotEmpty(disabledMessage,
+ "disabledMessage cannot be empty");
+ return this;
+ }
+
+ /**
+ * Sets categories for a shortcut.
+ * <ul>
+ * <li>Launcher apps may use this information to categorize shortcuts
+ * <li> Used by the system to associate a published Sharing Shortcut with supported
+ * mimeTypes. Required for published Sharing Shortcuts with a matching category
+ * declared in share targets, defined in the app's manifest linked shortcuts xml file.
+ * </ul>
+ *
+ * @see #SHORTCUT_CATEGORY_CONVERSATION
+ * @see ShortcutInfo#getCategories()
+ */
+ @NonNull
+ public Builder setCategories(Set<String> categories) {
+ mCategories = categories;
+ return this;
+ }
+
+ /**
+ * Sets the intent of a shortcut. Alternatively, {@link #setIntents(Intent[])} can be used
+ * to launch an activity with other activities in the back stack.
+ *
+ * <p>This is a mandatory field when publishing a new shortcut with
+ * {@link ShortcutManager#addDynamicShortcuts(List)} or
+ * {@link ShortcutManager#setDynamicShortcuts(List)}.
+ *
+ * <p>A shortcut can launch any intent that the publisher app has permission to
+ * launch. For example, a shortcut can launch an unexported activity within the publisher
+ * app. A shortcut intent doesn't have to point at the target activity.
+ *
+ * <p>The given {@code intent} can contain extras, but these extras must contain values
+ * of primitive types in order for the system to persist these values.
+ *
+ * @see ShortcutInfo#getIntent()
+ * @see #setIntents(Intent[])
+ */
+ @NonNull
+ public Builder setIntent(@NonNull Intent intent) {
+ return setIntents(new Intent[]{intent});
+ }
+
+ /**
+ * Sets multiple intents instead of a single intent, in order to launch an activity with
+ * other activities in back stack. Use {@link TaskStackBuilder} to build intents. The
+ * last element in the list represents the only intent that doesn't place an activity on
+ * the back stack.
+ * See the {@link ShortcutManager} javadoc for details.
+ *
+ * @see Builder#setIntent(Intent)
+ * @see ShortcutInfo#getIntents()
+ * @see Context#startActivities(Intent[])
+ * @see TaskStackBuilder
+ */
+ @NonNull
+ public Builder setIntents(@NonNull Intent[] intents) {
+ Objects.requireNonNull(intents, "intents cannot be null");
+ Objects.requireNonNull(intents.length, "intents cannot be empty");
+ for (Intent intent : intents) {
+ Objects.requireNonNull(intent, "intents cannot contain null");
+ Objects.requireNonNull(intent.getAction(), "intent's action must be set");
+ }
+ // Make sure always clone incoming intents.
+ mIntents = cloneIntents(intents);
+ return this;
+ }
+
+ /**
+ * Add a person that is relevant to this shortcut. Alternatively,
+ * {@link #setPersons(Person[])} can be used to add multiple persons to a shortcut.
+ *
+ * <p> This is an optional field, but the addition of person may cause this shortcut to
+ * appear more prominently in the user interface (e.g. ShareSheet).
+ *
+ * <p> A person should usually contain a uri in order to benefit from the ranking boost.
+ * However, even if no uri is provided, it's beneficial to provide people in the shortcut,
+ * such that listeners and voice only devices can announce and handle them properly.
+ *
+ * @see Person
+ * @see #setPersons(Person[])
+ */
+ @NonNull
+ public Builder setPerson(@NonNull Person person) {
+ return setPersons(new Person[]{person});
+ }
+
+ /**
+ * Sets multiple persons instead of a single person.
+ *
+ * @see Person
+ * @see #setPerson(Person)
+ */
+ @NonNull
+ public Builder setPersons(@NonNull Person[] persons) {
+ Objects.requireNonNull(persons, "persons cannot be null");
+ Objects.requireNonNull(persons.length, "persons cannot be empty");
+ for (Person person : persons) {
+ Objects.requireNonNull(person, "persons cannot contain null");
+ }
+ mPersons = clonePersons(persons);
+ return this;
+ }
+
+ /**
+ * Sets if a shortcut would be valid even if it has been unpublished/invisible by the app
+ * (as a dynamic or pinned shortcut). If it is long lived, it can be cached by various
+ * system services even after it has been unpublished as a dynamic shortcut.
+ */
+ @NonNull
+ public Builder setLongLived(boolean longLived) {
+ mIsLongLived = longLived;
+ return this;
+ }
+
+ /**
+ * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app
+ * to sort shortcuts.
+ *
+ * See {@link ShortcutInfo#getRank()} for details.
+ */
+ @NonNull
+ public Builder setRank(int rank) {
+ Preconditions.checkArgument((0 <= rank),
+ "Rank cannot be negative or bigger than MAX_RANK");
+ mRank = rank;
+ return this;
+ }
+
+ /**
+ * Extras that the app can set for any purpose.
+ *
+ * <p>Apps can store arbitrary shortcut metadata in extras and retrieve the
+ * metadata later using {@link ShortcutInfo#getExtras()}.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull PersistableBundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Creates a {@link ShortcutInfo} instance.
+ */
+ @NonNull
+ public ShortcutInfo build() {
+ return new ShortcutInfo(this);
+ }
+ }
+
+ /**
+ * Returns the ID of a shortcut.
+ *
+ * <p>Shortcut IDs are unique within each publisher app and must be stable across
+ * devices so that shortcuts will still be valid when restored on a different device.
+ * See {@link ShortcutManager} for details.
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Gets the {@link LocusId} associated with this shortcut.
+ *
+ * <p>Used by the device's intelligence services to correlate objects (such as
+ * {@link Notification} and {@link ContentCaptureContext}) that are correlated.
+ */
+ @Nullable
+ public LocusId getLocusId() {
+ return mLocusId;
+ }
+
+ /**
+ * Return the package name of the publisher app.
+ */
+ @NonNull
+ public String getPackage() {
+ return mPackageName;
+ }
+
+ /**
+ * Return the target activity.
+ *
+ * <p>This has nothing to do with the activity that this shortcut will launch.
+ * Launcher apps should show the launcher icon for the returned activity alongside
+ * this shortcut.
+ *
+ * @see Builder#setActivity
+ */
+ @Nullable
+ public ComponentName getActivity() {
+ return mActivity;
+ }
+
+ /** @hide */
+ public void setActivity(ComponentName activity) {
+ mActivity = activity;
+ }
+
+ /**
+ * Returns the shortcut icon.
+ *
+ * @hide
+ */
+ @Nullable
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns the theme resource name used for the splash screen.
+ * @hide
+ */
+ @Nullable
+ public String getStartingThemeResName() {
+ return mStartingThemeResName;
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Nullable
+ @Deprecated
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Deprecated
+ public int getTitleResId() {
+ return mTitleResId;
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Nullable
+ @Deprecated
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /** @hide -- old signature, the internal code still uses it. */
+ @Deprecated
+ public int getTextResId() {
+ return mTextResId;
+ }
+
+ /**
+ * Return the short description of a shortcut.
+ *
+ * @see Builder#setShortLabel(CharSequence)
+ */
+ @Nullable
+ public CharSequence getShortLabel() {
+ return mTitle;
+ }
+
+ /** @hide */
+ public int getShortLabelResourceId() {
+ return mTitleResId;
+ }
+
+ /**
+ * Return the long description of a shortcut.
+ *
+ * @see Builder#setLongLabel(CharSequence)
+ */
+ @Nullable
+ public CharSequence getLongLabel() {
+ return mText;
+ }
+
+ /**
+ * Returns the {@link #getLongLabel()} if it's populated, and if not, the
+ * {@link #getShortLabel()}.
+ * @hide
+ */
+ @Nullable
+ public CharSequence getLabel() {
+ CharSequence label = getLongLabel();
+ if (TextUtils.isEmpty(label)) {
+ label = getShortLabel();
+ }
+
+ return label;
+ }
+
+ /** @hide */
+ public int getLongLabelResourceId() {
+ return mTextResId;
+ }
+
+ /**
+ * Return the message that should be shown when the user attempts to start a shortcut
+ * that is disabled.
+ *
+ * @see Builder#setDisabledMessage(CharSequence)
+ */
+ @Nullable
+ public CharSequence getDisabledMessage() {
+ return mDisabledMessage;
+ }
+
+ /** @hide */
+ public int getDisabledMessageResourceId() {
+ return mDisabledMessageResId;
+ }
+
+ /** @hide */
+ public void setDisabledReason(@DisabledReason int reason) {
+ mDisabledReason = reason;
+ }
+
+ /**
+ * Returns why a shortcut has been disabled.
+ */
+ @DisabledReason
+ public int getDisabledReason() {
+ return mDisabledReason;
+ }
+
+ /**
+ * Return the shortcut's categories.
+ *
+ * @see Builder#setCategories(Set)
+ */
+ @Nullable
+ public Set<String> getCategories() {
+ return mCategories;
+ }
+
+ /**
+ * Returns the intent that is executed when the user selects this shortcut.
+ * If setIntents() was used, then return the last intent in the array.
+ *
+ * <p>Launcher apps <b>cannot</b> see the intent. If a {@link ShortcutInfo} is
+ * obtained via {@link LauncherApps}, then this method will always return null.
+ * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}.
+ *
+ * @see Builder#setIntent(Intent)
+ */
+ @Nullable
+ public Intent getIntent() {
+ if (mIntents == null || mIntents.length == 0) {
+ return null;
+ }
+ final int last = mIntents.length - 1;
+ final Intent intent = new Intent(mIntents[last]);
+ return setIntentExtras(intent, mIntentPersistableExtrases[last]);
+ }
+
+ /**
+ * Return the intent set with {@link Builder#setIntents(Intent[])}.
+ *
+ * <p>Launcher apps <b>cannot</b> see the intents. If a {@link ShortcutInfo} is
+ * obtained via {@link LauncherApps}, then this method will always return null.
+ * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}.
+ *
+ * @see Builder#setIntents(Intent[])
+ */
+ @Nullable
+ public Intent[] getIntents() {
+ if (mIntents == null) {
+ return null;
+ }
+ final Intent[] ret = new Intent[mIntents.length];
+
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = new Intent(mIntents[i]);
+ setIntentExtras(ret[i], mIntentPersistableExtrases[i]);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Return "raw" intents, which is the original intents without the extras.
+ * @hide
+ */
+ @Nullable
+ public Intent[] getIntentsNoExtras() {
+ return mIntents;
+ }
+
+ /**
+ * Return the Persons set with {@link Builder#setPersons(Person[])}.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public Person[] getPersons() {
+ return clonePersons(mPersons);
+ }
+
+ /**
+ * The extras in the intents. We convert extras into {@link PersistableBundle} so we can
+ * persist them.
+ * @hide
+ */
+ @Nullable
+ public PersistableBundle[] getIntentPersistableExtrases() {
+ return mIntentPersistableExtrases;
+ }
+
+ /**
+ * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
+ * {@link #getActivity} for each of the two types of shortcuts (static and dynamic).
+ *
+ * <p><em>Floating shortcuts</em>, or shortcuts that are neither static nor dynamic, will all
+ * have rank 0, because they aren't sorted.
+ *
+ * See the {@link ShortcutManager}'s class javadoc for details.
+ *
+ * @see Builder#setRank(int)
+ */
+ public int getRank() {
+ return mRank;
+ }
+
+ /** @hide */
+ public boolean hasRank() {
+ return mRank != RANK_NOT_SET;
+ }
+
+ /** @hide */
+ public void setRank(int rank) {
+ mRank = rank;
+ }
+
+ /** @hide */
+ public void clearImplicitRankAndRankChangedFlag() {
+ mImplicitRank = 0;
+ }
+
+ /** @hide */
+ public void setImplicitRank(int rank) {
+ // Make sure to keep RANK_CHANGED_BIT.
+ mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK);
+ }
+
+ /** @hide */
+ public int getImplicitRank() {
+ return mImplicitRank & IMPLICIT_RANK_MASK;
+ }
+
+ /** @hide */
+ public void setRankChanged() {
+ mImplicitRank |= RANK_CHANGED_BIT;
+ }
+
+ /** @hide */
+ public boolean isRankChanged() {
+ return (mImplicitRank & RANK_CHANGED_BIT) != 0;
+ }
+
+ /**
+ * Extras that the app can set for any purpose.
+ *
+ * @see Builder#setExtras(PersistableBundle)
+ */
+ @Nullable
+ public PersistableBundle getExtras() {
+ return mExtras;
+ }
+
+ /** @hide */
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /**
+ * {@link UserHandle} on which the publisher created this shortcut.
+ */
+ public UserHandle getUserHandle() {
+ return UserHandle.of(mUserId);
+ }
+
+ /**
+ * Last time when any of the fields was updated.
+ */
+ public long getLastChangedTimestamp() {
+ return mLastChangedTimestamp;
+ }
+
+ /** @hide */
+ @ShortcutFlags
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide*/
+ public void replaceFlags(@ShortcutFlags int flags) {
+ mFlags = flags;
+ }
+
+ /** @hide*/
+ public void addFlags(@ShortcutFlags int flags) {
+ mFlags |= flags;
+ }
+
+ /** @hide*/
+ public void clearFlags(@ShortcutFlags int flags) {
+ mFlags &= ~flags;
+ }
+
+ /** @hide*/
+ public boolean hasFlags(@ShortcutFlags int flags) {
+ return (mFlags & flags) == flags;
+ }
+
+ /** @hide */
+ public boolean isReturnedByServer() {
+ return hasFlags(FLAG_RETURNED_BY_SERVICE);
+ }
+
+ /** @hide */
+ public void setReturnedByServer() {
+ addFlags(FLAG_RETURNED_BY_SERVICE);
+ }
+
+ /** @hide */
+ public boolean isLongLived() {
+ return hasFlags(FLAG_LONG_LIVED);
+ }
+
+ /** @hide */
+ public void setLongLived() {
+ addFlags(FLAG_LONG_LIVED);
+ }
+
+ /** @hide */
+ public void setCached(@ShortcutFlags int cacheFlag) {
+ addFlags(cacheFlag);
+ }
+
+ /** Return whether a shortcut is cached. */
+ public boolean isCached() {
+ return (getFlags() & FLAG_CACHED_ALL) != 0;
+ }
+
+ /** Return whether a shortcut is dynamic. */
+ public boolean isDynamic() {
+ return hasFlags(FLAG_DYNAMIC);
+ }
+
+ /** Return whether a shortcut is pinned. */
+ public boolean isPinned() {
+ return hasFlags(FLAG_PINNED);
+ }
+
+ /**
+ * Return whether a shortcut is static; that is, whether a shortcut is
+ * published from AndroidManifest.xml. If {@code true}, the shortcut is
+ * also {@link #isImmutable()}.
+ *
+ * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml,
+ * this will be set to {@code false}. If the shortcut is not pinned, then it'll disappear.
+ * However, if it's pinned, it will still be visible, {@link #isEnabled()} will be
+ * {@code false} and {@link #isImmutable()} will be {@code true}.
+ */
+ public boolean isDeclaredInManifest() {
+ return hasFlags(FLAG_MANIFEST);
+ }
+
+ /** @hide kept for unit tests */
+ @Deprecated
+ public boolean isManifestShortcut() {
+ return isDeclaredInManifest();
+ }
+
+ /**
+ * @return true if pinned or cached, but neither static nor dynamic.
+ * @hide
+ */
+ public boolean isFloating() {
+ return (isPinned() || isCached()) && !(isDynamic() || isManifestShortcut());
+ }
+
+ /** @hide */
+ public boolean isOriginallyFromManifest() {
+ return hasFlags(FLAG_IMMUTABLE);
+ }
+
+ /** @hide */
+ public boolean isDynamicVisible() {
+ return isDynamic() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isPinnedVisible() {
+ return isPinned() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isManifestVisible() {
+ return isDeclaredInManifest() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isNonManifestVisible() {
+ return !isDeclaredInManifest() && isVisibleToPublisher()
+ && (isPinned() || isCached() || isDynamic());
+ }
+
+ /**
+ * Return if a shortcut is immutable, in which case it cannot be modified with any of
+ * {@link ShortcutManager} APIs.
+ *
+ * <p>All static shortcuts are immutable. When a static shortcut is pinned and is then
+ * disabled because it doesn't appear in AndroidManifest.xml for a newer version of the
+ * app, {@link #isDeclaredInManifest()} returns {@code false}, but the shortcut
+ * is still immutable.
+ *
+ * <p>All shortcuts originally published via the {@link ShortcutManager} APIs
+ * are all mutable.
+ */
+ public boolean isImmutable() {
+ return hasFlags(FLAG_IMMUTABLE);
+ }
+
+ /**
+ * Returns {@code false} if a shortcut is disabled with
+ * {@link ShortcutManager#disableShortcuts}.
+ */
+ public boolean isEnabled() {
+ return !hasFlags(FLAG_DISABLED);
+ }
+
+ /** @hide */
+ public boolean isAlive() {
+ return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST)
+ || isCached();
+ }
+
+ /** @hide */
+ public boolean usesQuota() {
+ return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
+ }
+
+ /**
+ * Return whether a shortcut's icon is a resource in the owning package.
+ *
+ * @hide internal/unit tests only
+ */
+ public boolean hasIconResource() {
+ return hasFlags(FLAG_HAS_ICON_RES);
+ }
+
+ /**
+ * Return whether a shortcut's icon is provided via a URI.
+ *
+ * @hide internal/unit tests only
+ */
+ public boolean hasIconUri() {
+ return hasFlags(FLAG_HAS_ICON_URI);
+ }
+
+ /** @hide */
+ public boolean hasStringResources() {
+ return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0);
+ }
+
+ /** @hide */
+ public boolean hasAnyResources() {
+ return hasIconResource() || hasStringResources();
+ }
+
+ /**
+ * Return whether a shortcut's icon is stored as a file.
+ *
+ * @hide internal/unit tests only
+ */
+ public boolean hasIconFile() {
+ return hasFlags(FLAG_HAS_ICON_FILE);
+ }
+
+ /**
+ * Return whether a shortcut's icon is adaptive bitmap following design guideline
+ * defined in {@link android.graphics.drawable.AdaptiveIconDrawable}.
+ *
+ * @hide internal/unit tests only
+ */
+ public boolean hasAdaptiveBitmap() {
+ return hasFlags(FLAG_ADAPTIVE_BITMAP);
+ }
+
+ /** @hide */
+ public boolean isIconPendingSave() {
+ return hasFlags(FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
+ /** @hide */
+ public void setIconPendingSave() {
+ addFlags(FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
+ /** @hide */
+ public void clearIconPendingSave() {
+ clearFlags(FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
+ /**
+ * When the system wasn't able to restore a shortcut, it'll still be registered to the system
+ * but disabled, and such shortcuts will not be visible to the publisher. They're still visible
+ * to launchers though.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleToPublisher() {
+ return !isDisabledForRestoreIssue(mDisabledReason);
+ }
+
+ /**
+ * Return whether a shortcut only contains "key" information only or not. If true, only the
+ * following fields are available.
+ * <ul>
+ * <li>{@link #getId()}
+ * <li>{@link #getPackage()}
+ * <li>{@link #getActivity()}
+ * <li>{@link #getLastChangedTimestamp()}
+ * <li>{@link #isDynamic()}
+ * <li>{@link #isPinned()}
+ * <li>{@link #isDeclaredInManifest()}
+ * <li>{@link #isImmutable()}
+ * <li>{@link #isEnabled()}
+ * <li>{@link #getUserHandle()}
+ * </ul>
+ *
+ * <p>For performance reasons, shortcuts passed to
+ * {@link LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle)} as well as those
+ * returned from {@link LauncherApps#getShortcuts(ShortcutQuery, UserHandle)}
+ * while using the {@link ShortcutQuery#FLAG_GET_KEY_FIELDS_ONLY} option contain only key
+ * information.
+ */
+ public boolean hasKeyFieldsOnly() {
+ return hasFlags(FLAG_KEY_FIELDS_ONLY);
+ }
+
+ /** @hide */
+ public boolean hasStringResourcesResolved() {
+ return hasFlags(FLAG_STRINGS_RESOLVED);
+ }
+
+ /** @hide */
+ public void updateTimestamp() {
+ mLastChangedTimestamp = System.currentTimeMillis();
+ }
+
+ /** @hide */
+ // VisibleForTesting
+ public void setTimestamp(long value) {
+ mLastChangedTimestamp = value;
+ }
+
+ /** @hide */
+ public void clearIcon() {
+ mIcon = null;
+ }
+
+ /** @hide */
+ public void setIconResourceId(int iconResourceId) {
+ if (mIconResId != iconResourceId) {
+ mIconResName = null;
+ }
+ mIconResId = iconResourceId;
+ }
+
+ /**
+ * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true.
+ * @hide internal / tests only.
+ */
+ public int getIconResourceId() {
+ return mIconResId;
+ }
+
+ /** @hide */
+ public void setIconUri(String iconUri) {
+ mIconUri = iconUri;
+ }
+
+ /**
+ * Get the Uri for the icon, valid only when {@link #hasIconUri()} } is true.
+ * @hide internal / tests only.
+ */
+ public String getIconUri() {
+ return mIconUri;
+ }
+
+ /**
+ * Bitmap path. Note this will be null even if {@link #hasIconFile()} is set when the save
+ * is pending. Use {@link #isIconPendingSave()} to check it.
+ *
+ * @hide
+ */
+ public String getBitmapPath() {
+ return mBitmapPath;
+ }
+
+ /** @hide */
+ public void setBitmapPath(String bitmapPath) {
+ mBitmapPath = bitmapPath;
+ }
+
+ /** @hide */
+ public void setDisabledMessageResId(int disabledMessageResId) {
+ if (mDisabledMessageResId != disabledMessageResId) {
+ mDisabledMessageResName = null;
+ }
+ mDisabledMessageResId = disabledMessageResId;
+ mDisabledMessage = null;
+ }
+
+ /** @hide */
+ public void setDisabledMessage(String disabledMessage) {
+ mDisabledMessage = disabledMessage;
+ mDisabledMessageResId = 0;
+ mDisabledMessageResName = null;
+ }
+
+ /** @hide */
+ public String getTitleResName() {
+ return mTitleResName;
+ }
+
+ /** @hide */
+ public void setTitleResName(String titleResName) {
+ mTitleResName = titleResName;
+ }
+
+ /** @hide */
+ public String getTextResName() {
+ return mTextResName;
+ }
+
+ /** @hide */
+ public void setTextResName(String textResName) {
+ mTextResName = textResName;
+ }
+
+ /** @hide */
+ public String getDisabledMessageResName() {
+ return mDisabledMessageResName;
+ }
+
+ /** @hide */
+ public void setDisabledMessageResName(String disabledMessageResName) {
+ mDisabledMessageResName = disabledMessageResName;
+ }
+
+ /** @hide */
+ public String getIconResName() {
+ return mIconResName;
+ }
+
+ /** @hide */
+ public void setIconResName(String iconResName) {
+ mIconResName = iconResName;
+ }
+
+ /**
+ * Replaces the intent.
+ *
+ * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}.
+ *
+ * @hide
+ */
+ public void setIntents(Intent[] intents) throws IllegalArgumentException {
+ Objects.requireNonNull(intents);
+ Preconditions.checkArgument(intents.length > 0);
+
+ mIntents = cloneIntents(intents);
+ fixUpIntentExtras();
+ }
+
+ /** @hide */
+ public static Intent setIntentExtras(Intent intent, PersistableBundle extras) {
+ if (extras == null) {
+ intent.replaceExtras((Bundle) null);
+ } else {
+ intent.replaceExtras(new Bundle(extras));
+ }
+ return intent;
+ }
+
+ /**
+ * Replaces the categories.
+ *
+ * @hide
+ */
+ public void setCategories(Set<String> categories) {
+ mCategories = cloneCategories(categories);
+ }
+
+ private ShortcutInfo(Parcel source) {
+ final ClassLoader cl = getClass().getClassLoader();
+
+ mUserId = source.readInt();
+ mId = source.readString8();
+ mPackageName = source.readString8();
+ mActivity = source.readParcelable(cl);
+ mFlags = source.readInt();
+ mIconResId = source.readInt();
+ mLastChangedTimestamp = source.readLong();
+ mDisabledReason = source.readInt();
+
+ if (source.readInt() == 0) {
+ return; // key information only.
+ }
+
+ mIcon = source.readParcelable(cl);
+ mTitle = source.readCharSequence();
+ mTitleResId = source.readInt();
+ mText = source.readCharSequence();
+ mTextResId = source.readInt();
+ mDisabledMessage = source.readCharSequence();
+ mDisabledMessageResId = source.readInt();
+ mIntents = source.readParcelableArray(cl, Intent.class);
+ mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class);
+ mRank = source.readInt();
+ mExtras = source.readParcelable(cl);
+ mBitmapPath = source.readString8();
+
+ mIconResName = source.readString8();
+ mTitleResName = source.readString8();
+ mTextResName = source.readString8();
+ mDisabledMessageResName = source.readString8();
+
+ int N = source.readInt();
+ if (N == 0) {
+ mCategories = null;
+ } else {
+ mCategories = new ArraySet<>(N);
+ for (int i = 0; i < N; i++) {
+ mCategories.add(source.readString8().intern());
+ }
+ }
+
+ mPersons = source.readParcelableArray(cl, Person.class);
+ mLocusId = source.readParcelable(cl);
+ mIconUri = source.readString8();
+ mStartingThemeResName = source.readString8();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mUserId);
+ dest.writeString8(mId);
+ dest.writeString8(mPackageName);
+ dest.writeParcelable(mActivity, flags);
+ dest.writeInt(mFlags);
+ dest.writeInt(mIconResId);
+ dest.writeLong(mLastChangedTimestamp);
+ dest.writeInt(mDisabledReason);
+
+ if (hasKeyFieldsOnly()) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(1);
+
+ dest.writeParcelable(mIcon, flags);
+ dest.writeCharSequence(mTitle);
+ dest.writeInt(mTitleResId);
+ dest.writeCharSequence(mText);
+ dest.writeInt(mTextResId);
+ dest.writeCharSequence(mDisabledMessage);
+ dest.writeInt(mDisabledMessageResId);
+
+ dest.writeParcelableArray(mIntents, flags);
+ dest.writeParcelableArray(mIntentPersistableExtrases, flags);
+ dest.writeInt(mRank);
+ dest.writeParcelable(mExtras, flags);
+ dest.writeString8(mBitmapPath);
+
+ dest.writeString8(mIconResName);
+ dest.writeString8(mTitleResName);
+ dest.writeString8(mTextResName);
+ dest.writeString8(mDisabledMessageResName);
+
+ if (mCategories != null) {
+ final int N = mCategories.size();
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ dest.writeString8(mCategories.valueAt(i));
+ }
+ } else {
+ dest.writeInt(0);
+ }
+
+ dest.writeParcelableArray(mPersons, flags);
+ dest.writeParcelable(mLocusId, flags);
+ dest.writeString8(mIconUri);
+ dest.writeString8(mStartingThemeResName);
+ }
+
+ public static final @NonNull Creator<ShortcutInfo> CREATOR =
+ new Creator<ShortcutInfo>() {
+ public ShortcutInfo createFromParcel(Parcel source) {
+ return new ShortcutInfo(source);
+ }
+ public ShortcutInfo[] newArray(int size) {
+ return new ShortcutInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+
+ /**
+ * Return a string representation, intended for logging. Some fields will be retracted.
+ */
+ @Override
+ public String toString() {
+ return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false,
+ /*indent=*/ null);
+ }
+
+ /** @hide */
+ public String toInsecureString() {
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true,
+ /*indent=*/ null);
+ }
+
+ /** @hide */
+ public String toDumpString(String indent) {
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
+ }
+
+ private void addIndentOrComma(StringBuilder sb, String indent) {
+ if (indent != null) {
+ sb.append("\n ");
+ sb.append(indent);
+ } else {
+ sb.append(", ");
+ }
+ }
+
+ private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
+ final StringBuilder sb = new StringBuilder();
+
+ if (indent != null) {
+ sb.append(indent);
+ }
+
+ sb.append("ShortcutInfo {");
+
+ sb.append("id=");
+ sb.append(secure ? "***" : mId);
+
+ sb.append(", flags=0x");
+ sb.append(Integer.toHexString(mFlags));
+ sb.append(" [");
+ if ((mFlags & FLAG_SHADOW) != 0) {
+ // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
+ // we don't have an isXxx for this.
+ sb.append("Sdw");
+ }
+ if (!isEnabled()) {
+ sb.append("Dis");
+ }
+ if (isImmutable()) {
+ sb.append("Im");
+ }
+ if (isManifestShortcut()) {
+ sb.append("Man");
+ }
+ if (isDynamic()) {
+ sb.append("Dyn");
+ }
+ if (isPinned()) {
+ sb.append("Pin");
+ }
+ if (hasIconFile()) {
+ sb.append("Ic-f");
+ }
+ if (isIconPendingSave()) {
+ sb.append("Pens");
+ }
+ if (hasIconResource()) {
+ sb.append("Ic-r");
+ }
+ if (hasIconUri()) {
+ sb.append("Ic-u");
+ }
+ if (hasAdaptiveBitmap()) {
+ sb.append("Ic-a");
+ }
+ if (hasKeyFieldsOnly()) {
+ sb.append("Key");
+ }
+ if (hasStringResourcesResolved()) {
+ sb.append("Str");
+ }
+ if (isReturnedByServer()) {
+ sb.append("Rets");
+ }
+ if (isLongLived()) {
+ sb.append("Liv");
+ }
+ sb.append("]");
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("packageName=");
+ sb.append(mPackageName);
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("activity=");
+ sb.append(mActivity);
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("shortLabel=");
+ sb.append(secure ? "***" : mTitle);
+ sb.append(", resId=");
+ sb.append(mTitleResId);
+ sb.append("[");
+ sb.append(mTitleResName);
+ sb.append("]");
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("longLabel=");
+ sb.append(secure ? "***" : mText);
+ sb.append(", resId=");
+ sb.append(mTextResId);
+ sb.append("[");
+ sb.append(mTextResName);
+ sb.append("]");
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("disabledMessage=");
+ sb.append(secure ? "***" : mDisabledMessage);
+ sb.append(", resId=");
+ sb.append(mDisabledMessageResId);
+ sb.append("[");
+ sb.append(mDisabledMessageResName);
+ sb.append("]");
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("disabledReason=");
+ sb.append(getDisabledReasonDebugString(mDisabledReason));
+
+ if (mStartingThemeResName != null && !mStartingThemeResName.isEmpty()) {
+ addIndentOrComma(sb, indent);
+ sb.append("SplashScreenThemeResName=");
+ sb.append(mStartingThemeResName);
+ }
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("categories=");
+ sb.append(mCategories);
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("persons=");
+ sb.append(mPersons);
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("icon=");
+ sb.append(mIcon);
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("rank=");
+ sb.append(mRank);
+
+ sb.append(", timestamp=");
+ sb.append(mLastChangedTimestamp);
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("intents=");
+ if (mIntents == null) {
+ sb.append("null");
+ } else {
+ if (secure) {
+ sb.append("size:");
+ sb.append(mIntents.length);
+ } else {
+ final int size = mIntents.length;
+ sb.append("[");
+ String sep = "";
+ for (int i = 0; i < size; i++) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append(mIntents[i]);
+ sb.append("/");
+ sb.append(mIntentPersistableExtrases[i]);
+ }
+ sb.append("]");
+ }
+ }
+
+ addIndentOrComma(sb, indent);
+
+ sb.append("extras=");
+ sb.append(mExtras);
+
+ if (includeInternalData) {
+ addIndentOrComma(sb, indent);
+
+ sb.append("iconRes=");
+ sb.append(mIconResId);
+ sb.append("[");
+ sb.append(mIconResName);
+ sb.append("]");
+
+ sb.append(", bitmapPath=");
+ sb.append(mBitmapPath);
+
+ sb.append(", iconUri=");
+ sb.append(mIconUri);
+ }
+
+ if (mLocusId != null) {
+ sb.append("locusId="); sb.append(mLocusId); // LocusId.toString() is PII-safe.
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /** @hide */
+ public ShortcutInfo(
+ @UserIdInt int userId, String id, String packageName, ComponentName activity,
+ Icon icon, CharSequence title, int titleResId, String titleResName,
+ CharSequence text, int textResId, String textResName,
+ CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
+ Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
+ long lastChangedTimestamp,
+ int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
+ int disabledReason, Person[] persons, LocusId locusId,
+ @Nullable String startingThemeResName) {
+ mUserId = userId;
+ mId = id;
+ mPackageName = packageName;
+ mActivity = activity;
+ mIcon = icon;
+ mTitle = title;
+ mTitleResId = titleResId;
+ mTitleResName = titleResName;
+ mText = text;
+ mTextResId = textResId;
+ mTextResName = textResName;
+ mDisabledMessage = disabledMessage;
+ mDisabledMessageResId = disabledMessageResId;
+ mDisabledMessageResName = disabledMessageResName;
+ mCategories = cloneCategories(categories);
+ mIntents = cloneIntents(intentsWithExtras);
+ fixUpIntentExtras();
+ mRank = rank;
+ mExtras = extras;
+ mLastChangedTimestamp = lastChangedTimestamp;
+ mFlags = flags;
+ mIconResId = iconResId;
+ mIconResName = iconResName;
+ mBitmapPath = bitmapPath;
+ mIconUri = iconUri;
+ mDisabledReason = disabledReason;
+ mPersons = persons;
+ mLocusId = locusId;
+ mStartingThemeResName = startingThemeResName;
+ }
+}
diff --git a/android/content/pm/ShortcutManager.java b/android/content/pm/ShortcutManager.java
new file mode 100644
index 0000000..d77fa91
--- /dev/null
+++ b/android/content/pm/ShortcutManager.java
@@ -0,0 +1,808 @@
+/*
+ * 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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.app.Notification;
+import android.app.usage.UsageStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * <p><code>ShortcutManager</code> executes operations on an app's set of <i>shortcuts</i>, which
+ * represent specific tasks and actions that users can perform within your app. This page lists
+ * components of the <code>ShortcutManager</code> class that you can use to create and manage
+ * sets of shortcuts.
+ *
+ * <p>To learn about methods that retrieve information about a single shortcut—including
+ * identifiers, type, and status—read the <code>
+ * <a href="/reference/android/content/pm/ShortcutInfo.html">ShortcutInfo</a></code> reference.
+ *
+ * <p>For guidance about using shortcuts, see
+ * <a href="/guide/topics/ui/shortcuts/index.html">App shortcuts</a>.
+ *
+ * <h3>Retrieving class instances</h3>
+ * <!-- Provides a heading for the content filled in by the @SystemService annotation below -->
+ */
+@SystemService(Context.SHORTCUT_SERVICE)
+public class ShortcutManager {
+ private static final String TAG = "ShortcutManager";
+
+ /**
+ * Include manifest shortcuts in the result.
+ *
+ * @see #getShortcuts(int)
+ */
+ public static final int FLAG_MATCH_MANIFEST = 1 << 0;
+
+ /**
+ * Include dynamic shortcuts in the result.
+ *
+ * @see #getShortcuts(int)
+ */
+ public static final int FLAG_MATCH_DYNAMIC = 1 << 1;
+
+ /**
+ * Include pinned shortcuts in the result.
+ *
+ * @see #getShortcuts(int)
+ */
+ public static final int FLAG_MATCH_PINNED = 1 << 2;
+
+ /**
+ * Include cached shortcuts in the result.
+ *
+ * @see #getShortcuts(int)
+ */
+ public static final int FLAG_MATCH_CACHED = 1 << 3;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_MATCH_" }, value = {
+ FLAG_MATCH_MANIFEST,
+ FLAG_MATCH_DYNAMIC,
+ FLAG_MATCH_PINNED,
+ FLAG_MATCH_CACHED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShortcutMatchFlags {}
+
+ private final Context mContext;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final IShortcutService mService;
+
+ /**
+ * @hide
+ */
+ public ShortcutManager(Context context, IShortcutService service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public ShortcutManager(Context context) {
+ this(context, IShortcutService.Stub.asInterface(
+ ServiceManager.getService(Context.SHORTCUT_SERVICE)));
+ }
+
+ /**
+ * Publish the list of shortcuts. All existing dynamic shortcuts from the caller app
+ * will be replaced. If there are already pinned shortcuts with the same IDs,
+ * the mutable pinned shortcuts are updated.
+ *
+ * <p>This API will be rate-limited.
+ *
+ * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
+ *
+ * @throws IllegalArgumentException if {@link #getMaxShortcutCountPerActivity()} is exceeded,
+ * or when trying to update immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+ try {
+ return ((boolean) getFutureOrThrow(mService.setDynamicShortcuts(
+ mContext.getPackageName(), new ParceledListSlice(
+ shortcutInfoList), injectMyUserId())));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all dynamic shortcuts from the caller app.
+ *
+ * <p>This API is intended to be used for examining what shortcuts are currently published.
+ * Re-publishing returned {@link ShortcutInfo}s via APIs such as
+ * {@link #setDynamicShortcuts(List)} may cause loss of information such as icons.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ @NonNull
+ public List<ShortcutInfo> getDynamicShortcuts() {
+ try {
+ return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(),
+ FLAG_MATCH_DYNAMIC, injectMyUserId())).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all static (manifest) shortcuts from the caller app.
+ *
+ * <p>This API is intended to be used for examining what shortcuts are currently published.
+ * Re-publishing returned {@link ShortcutInfo}s via APIs such as
+ * {@link #setDynamicShortcuts(List)} may cause loss of information such as icons.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ @NonNull
+ public List<ShortcutInfo> getManifestShortcuts() {
+ try {
+ return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(),
+ FLAG_MATCH_MANIFEST, injectMyUserId())).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@link ShortcutInfo}s that match {@code matchFlags}.
+ *
+ * @param matchFlags result includes shortcuts matching this flags. Any combination of:
+ * <ul>
+ * <li>{@link #FLAG_MATCH_MANIFEST}
+ * <li>{@link #FLAG_MATCH_DYNAMIC}
+ * <li>{@link #FLAG_MATCH_PINNED}
+ * <li>{@link #FLAG_MATCH_CACHED}
+ * </ul>
+
+ * @return list of {@link ShortcutInfo}s that match the flag.
+ *
+ * <p>At least one of the {@code MATCH} flags should be set. Otherwise no shortcuts will be
+ * returned.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ @NonNull
+ public List<ShortcutInfo> getShortcuts(@ShortcutMatchFlags int matchFlags) {
+ try {
+ return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(), matchFlags,
+ injectMyUserId())).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Publish the list of dynamic shortcuts. If there are already dynamic or pinned shortcuts with
+ * the same IDs, each mutable shortcut is updated.
+ *
+ * <p>This API will be rate-limited.
+ *
+ * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
+ *
+ * @throws IllegalArgumentException if {@link #getMaxShortcutCountPerActivity()} is exceeded,
+ * or when trying to update immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+ try {
+ return (boolean) getFutureOrThrow(mService.addDynamicShortcuts(
+ mContext.getPackageName(), new ParceledListSlice(shortcutInfoList),
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Delete dynamic shortcuts by ID.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
+ try {
+ getFutureOrThrow(mService.removeDynamicShortcuts(mContext.getPackageName(), shortcutIds,
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Delete all dynamic shortcuts from the caller app.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void removeAllDynamicShortcuts() {
+ try {
+ getFutureOrThrow(mService.removeAllDynamicShortcuts(mContext.getPackageName(),
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Delete long lived shortcuts by ID.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void removeLongLivedShortcuts(@NonNull List<String> shortcutIds) {
+ try {
+ getFutureOrThrow(mService.removeLongLivedShortcuts(mContext.getPackageName(),
+ shortcutIds, injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all pinned shortcuts from the caller app.
+ *
+ * <p>This API is intended to be used for examining what shortcuts are currently published.
+ * Re-publishing returned {@link ShortcutInfo}s via APIs such as
+ * {@link #setDynamicShortcuts(List)} may cause loss of information such as icons.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ @NonNull
+ public List<ShortcutInfo> getPinnedShortcuts() {
+ try {
+ return getFutureOrThrow(mService.getShortcuts(mContext.getPackageName(),
+ FLAG_MATCH_PINNED, injectMyUserId())).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Update all existing shortcuts with the same IDs. Target shortcuts may be pinned and/or
+ * dynamic, but they must not be immutable.
+ *
+ * <p>This API will be rate-limited.
+ *
+ * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
+ *
+ * @throws IllegalArgumentException If trying to update immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ @WorkerThread
+ public boolean updateShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+ try {
+ return (boolean) getFutureOrThrow(mService.updateShortcuts(mContext.getPackageName(),
+ new ParceledListSlice(shortcutInfoList), injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Disable pinned shortcuts. For more details, read
+ * <a href="/guide/topics/ui/shortcuts/managing-shortcuts.html#disable-shortcuts">
+ * Disable shortcuts</a>.
+ *
+ * @throws IllegalArgumentException If trying to disable immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds) {
+ try {
+ getFutureOrThrow(mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+ /* disabledMessage =*/ null, /* disabledMessageResId =*/ 0,
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide old signature, kept for unit testing.
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds, int disabledMessageResId) {
+ try {
+ getFutureOrThrow(mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+ /* disabledMessage =*/ null, disabledMessageResId,
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide old signature, kept for unit testing.
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds, String disabledMessage) {
+ disableShortcuts(shortcutIds, (CharSequence) disabledMessage);
+ }
+
+ /**
+ * Disable pinned shortcuts, showing the user a custom error message when they try to select
+ * the disabled shortcuts.
+ * For more details, read
+ * <a href="/guide/topics/ui/shortcuts/managing-shortcuts.html#disable-shortcuts">
+ * Disable shortcuts</a>.
+ *
+ * @throws IllegalArgumentException If trying to disable immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds, CharSequence disabledMessage) {
+ try {
+ getFutureOrThrow(mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+ disabledMessage, /* disabledMessageResId =*/ 0,
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Re-enable pinned shortcuts that were previously disabled. If the target shortcuts
+ * are already enabled, this method does nothing.
+ *
+ * @throws IllegalArgumentException If trying to enable immutable shortcuts.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void enableShortcuts(@NonNull List<String> shortcutIds) {
+ try {
+ getFutureOrThrow(mService.enableShortcuts(
+ mContext.getPackageName(), shortcutIds, injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * @hide old signature, kept for unit testing.
+ */
+ public int getMaxShortcutCountForActivity() {
+ return getMaxShortcutCountPerActivity();
+ }
+
+ /**
+ * Return the maximum number of static and dynamic shortcuts that each launcher icon
+ * can have at a time.
+ */
+ public int getMaxShortcutCountPerActivity() {
+ try {
+ return mService.getMaxShortcutCountPerActivity(
+ mContext.getPackageName(), injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the number of times the caller app can call the rate-limited APIs
+ * before the rate limit counter is reset.
+ *
+ * @see #getRateLimitResetTime()
+ *
+ * @hide
+ */
+ public int getRemainingCallCount() {
+ try {
+ return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return when the rate limit count will be reset next time, in milliseconds since the epoch.
+ *
+ * @see #getRemainingCallCount()
+ * @see System#currentTimeMillis()
+ *
+ * @hide
+ */
+ public long getRateLimitResetTime() {
+ try {
+ return mService.getRateLimitResetTime(mContext.getPackageName(), injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return {@code true} when rate-limiting is active for the caller app.
+ *
+ * <p>For details, see <a href="/guide/topics/ui/shortcuts/managing-shortcuts#rate-limiting">
+ * Rate limiting</a>.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public boolean isRateLimitingActive() {
+ try {
+ return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId())
+ == 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the max width for icons, in pixels.
+ *
+ * <p> Note that this method returns max width of icon's visible part. Hence, it does not take
+ * into account the inset introduced by {@link AdaptiveIconDrawable}. To calculate bitmap image
+ * to function as {@link AdaptiveIconDrawable}, multiply
+ * 1 + 2 * {@link AdaptiveIconDrawable#getExtraInsetFraction()} to the returned size.
+ */
+ public int getIconMaxWidth() {
+ try {
+ // TODO Implement it properly using xdpi.
+ return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the max height for icons, in pixels.
+ */
+ public int getIconMaxHeight() {
+ try {
+ // TODO Implement it properly using ydpi.
+ return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Apps that publish shortcuts should call this method whenever the user
+ * selects the shortcut containing the given ID or when the user completes
+ * an action in the app that is equivalent to selecting the shortcut.
+ * For more details, read about
+ * <a href="/guide/topics/ui/shortcuts/managing-shortcuts.html#track-usage">
+ * tracking shortcut usage</a>.
+ *
+ * <p>The information is accessible via {@link UsageStatsManager#queryEvents}
+ * Typically, launcher apps use this information to build a prediction model
+ * so that they can promote the shortcuts that are likely to be used at the moment.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void reportShortcutUsed(String shortcutId) {
+ try {
+ getFutureOrThrow(mService.reportShortcutUsed(mContext.getPackageName(), shortcutId,
+ injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return {@code TRUE} if the app is running on a device whose default launcher supports
+ * {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
+ *
+ * <p>The return value may change in subsequent calls if the user changes the default launcher
+ * app.
+ *
+ * <p><b>Note:</b> See also the support library counterpart
+ * {@link android.support.v4.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported(
+ * Context)}, which supports Android versions lower than {@link VERSION_CODES#O} using the
+ * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
+ *
+ * @see #requestPinShortcut(ShortcutInfo, IntentSender)
+ */
+ public boolean isRequestPinShortcutSupported() {
+ try {
+ return mService.isRequestPinItemSupported(injectMyUserId(),
+ LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request to create a pinned shortcut. The default launcher will receive this request and
+ * ask the user for approval. If the user approves it, the shortcut will be created, and
+ * {@code resultIntent} will be sent. If a request is denied by the user, however, no response
+ * will be sent to the caller.
+ *
+ * <p>Only apps with a foreground activity or a foreground service can call this method.
+ * Otherwise, it'll throw {@link IllegalStateException}.
+ *
+ * <p>It's up to the launcher to decide how to handle previous pending requests when the same
+ * package calls this API multiple times in a row. One possible strategy is to ignore any
+ * previous requests.
+ *
+ * <p><b>Note:</b> See also the support library counterpart
+ * {@link android.support.v4.content.pm.ShortcutManagerCompat#requestPinShortcut(
+ * Context, ShortcutInfoCompat, IntentSender)},
+ * which supports Android versions lower than {@link VERSION_CODES#O} using the
+ * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
+ *
+ * @param shortcut Shortcut to pin. If an app wants to pin an existing (either static
+ * or dynamic) shortcut, then it only needs to have an ID. Although other fields don't have
+ * to be set, the target shortcut must be enabled.
+ *
+ * <p>If it's a new shortcut, all the mandatory fields, such as a short label, must be
+ * set.
+ * @param resultIntent If not null, this intent will be sent when the shortcut is pinned.
+ * Use {@link android.app.PendingIntent#getIntentSender()} to create an {@link IntentSender}.
+ * To avoid background execution limits, use an unexported, manifest-declared receiver.
+ * For more details, see
+ * <a href="/guide/topics/ui/shortcuts/creating-shortcuts.html#pinned">
+ * Creating pinned shortcuts</a>.
+ *
+ * @return {@code TRUE} if the launcher supports this feature. Note the API will return without
+ * waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
+ * the shortcut was pinned successfully. {@code FALSE} if the launcher doesn't support this
+ * feature.
+ *
+ * @see #isRequestPinShortcutSupported()
+ * @see IntentSender
+ * @see android.app.PendingIntent#getIntentSender()
+ *
+ * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
+ * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
+ * service, or the device is locked.
+ */
+ @WorkerThread
+ public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut,
+ @Nullable IntentSender resultIntent) {
+ try {
+ return (boolean) getFutureOrThrow(mService.requestPinShortcut(mContext.getPackageName(),
+ shortcut, resultIntent, injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an Intent which can be used by the default launcher to pin a shortcut containing the
+ * given {@link ShortcutInfo}. This method should be used by an Activity to set a result in
+ * response to {@link Intent#ACTION_CREATE_SHORTCUT}.
+ *
+ * @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic
+ * or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
+ * be set, in which case, the target shortcut must be enabled.
+ * If it's a new shortcut, all the mandatory fields, such as a short label, must be
+ * set.
+ * @return The intent that should be set as the result for the calling activity, or
+ * <code>null</code> if the current launcher doesn't support shortcuts.
+ *
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ *
+ * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
+ */
+ @WorkerThread
+ public Intent createShortcutResultIntent(@NonNull ShortcutInfo shortcut) {
+ try {
+ return getFutureOrThrow(mService.createShortcutResultIntent(mContext.getPackageName(),
+ shortcut, injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called internally when an app is considered to have come to the foreground
+ * even when technically it's not. This method resets the throttling for this package.
+ * For example, when the user sends an "inline reply" on a notification, the system UI will
+ * call it.
+ *
+ * @hide
+ */
+ public void onApplicationActive(@NonNull String packageName, @UserIdInt int userId) {
+ try {
+ getFutureOrThrow(mService.onApplicationActive(packageName, userId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide injection point */
+ @VisibleForTesting
+ protected int injectMyUserId() {
+ return mContext.getUserId();
+ }
+
+ /**
+ * Used by framework's ShareSheet (ChooserActivity.java) to retrieve all of the direct share
+ * targets that match the given IntentFilter.
+ *
+ * @param filter IntentFilter that will be used to retrieve the matching {@link ShortcutInfo}s.
+ * @return List of {@link ShareShortcutInfo}s that match the given IntentFilter.
+ * @hide
+ */
+ @WorkerThread
+ @NonNull
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_APP_PREDICTIONS)
+ public List<ShareShortcutInfo> getShareTargets(@NonNull IntentFilter filter) {
+ try {
+ return getFutureOrThrow(mService.getShareTargets(mContext.getPackageName(), filter,
+ injectMyUserId())).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Represents the result of a query return by {@link #getShareTargets(IntentFilter)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class ShareShortcutInfo implements Parcelable {
+ private final ShortcutInfo mShortcutInfo;
+ private final ComponentName mTargetComponent;
+
+ /**
+ * @hide
+ */
+ public ShareShortcutInfo(@NonNull ShortcutInfo shortcutInfo,
+ @NonNull ComponentName targetComponent) {
+ if (shortcutInfo == null) {
+ throw new NullPointerException("shortcut info is null");
+ }
+ if (targetComponent == null) {
+ throw new NullPointerException("target component is null");
+ }
+
+ mShortcutInfo = shortcutInfo;
+ mTargetComponent = targetComponent;
+ }
+
+ private ShareShortcutInfo(@NonNull Parcel in) {
+ mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+ mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader());
+ }
+
+ @NonNull
+ public ShortcutInfo getShortcutInfo() {
+ return mShortcutInfo;
+ }
+
+ @NonNull
+ public ComponentName getTargetComponent() {
+ return mTargetComponent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mShortcutInfo, flags);
+ dest.writeParcelable(mTargetComponent, flags);
+ }
+
+ public static final @NonNull Parcelable.Creator<ShareShortcutInfo> CREATOR =
+ new Parcelable.Creator<ShareShortcutInfo>() {
+ public ShareShortcutInfo createFromParcel(Parcel in) {
+ return new ShareShortcutInfo(in);
+ }
+
+ public ShareShortcutInfo[] newArray(int size) {
+ return new ShareShortcutInfo[size];
+ }
+ };
+ }
+
+ /**
+ * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share
+ * target definitions in it's resources.
+ *
+ * @param packageName Package to check for share targets.
+ * @return True if the package has any share target definitions, False otherwise.
+ * @hide
+ */
+ @SystemApi
+ public boolean hasShareTargets(@NonNull String packageName) {
+ try {
+ return mService.hasShareTargets(mContext.getPackageName(), packageName,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Publish a single dynamic shortcut. If there are already dynamic or pinned shortcuts with the
+ * same ID, each mutable shortcut is updated.
+ *
+ * <p>This method is useful when posting notifications which are tagged with shortcut IDs; In
+ * order to make sure shortcuts exist and are up-to-date, without the need to explicitly handle
+ * the shortcut count limit.
+ * @see android.app.NotificationManager#notify(int, Notification)
+ * @see Notification.Builder#setShortcutId(String)
+ *
+ * <p>If {@link #getMaxShortcutCountPerActivity()} is already reached, an existing shortcut with
+ * the lowest rank will be removed to add space for the new shortcut.
+ *
+ * <p>If the rank of the shortcut is not explicitly set, it will be set to zero, and shortcut
+ * will be added to the top of the list.
+ *
+ * @throws IllegalArgumentException if trying to update an immutable shortcut.
+ *
+ * @throws IllegalStateException when the user is locked.
+ */
+ public void pushDynamicShortcut(@NonNull ShortcutInfo shortcut) {
+ try {
+ getFutureOrThrow(mService.pushDynamicShortcut(
+ mContext.getPackageName(), shortcut, injectMyUserId()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) {
+ try {
+ return future.get();
+ } catch (Throwable e) {
+ if (e instanceof ExecutionException) {
+ e = e.getCause();
+ }
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ if (e instanceof Error) {
+ throw (Error) e;
+ }
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/android/content/pm/ShortcutQueryWrapper.java b/android/content/pm/ShortcutQueryWrapper.java
new file mode 100644
index 0000000..c613441
--- /dev/null
+++ b/android/content/pm/ShortcutQueryWrapper.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 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.content.ComponentName;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@DataClass(genParcelable = true, genToString = true)
+public final class ShortcutQueryWrapper extends LauncherApps.ShortcutQuery implements Parcelable {
+
+ public ShortcutQueryWrapper(LauncherApps.ShortcutQuery query) {
+ this();
+ mChangedSince = query.mChangedSince;
+ mPackage = query.mPackage;
+ mLocusIds = query.mLocusIds;
+ mShortcutIds = query.mShortcutIds;
+ mActivity = query.mActivity;
+ mQueryFlags = query.mQueryFlags;
+ }
+
+ public long getChangedSince() {
+ return mChangedSince;
+ }
+
+ @Nullable
+ public String getPackage() {
+ return mPackage;
+ }
+
+ @Nullable
+ public List<LocusId> getLocusIds() {
+ return mLocusIds;
+ }
+
+ @Nullable
+ public List<String> getShortcutIds() {
+ return mShortcutIds;
+ }
+
+ @Nullable
+ public ComponentName getActivity() {
+ return mActivity;
+ }
+
+ public int getQueryFlags() {
+ return mQueryFlags;
+ }
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ShortcutQueryWrapper() {
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ShortcutQueryWrapper { " +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mPackage != null) flg |= 0x2;
+ if (mShortcutIds != null) flg |= 0x4;
+ if (mLocusIds != null) flg |= 0x8;
+ if (mActivity != null) flg |= 0x10;
+ dest.writeByte(flg);
+ dest.writeLong(mChangedSince);
+ if (mPackage != null) dest.writeString(mPackage);
+ if (mShortcutIds != null) dest.writeStringList(mShortcutIds);
+ if (mLocusIds != null) dest.writeParcelableList(mLocusIds, flags);
+ if (mActivity != null) dest.writeTypedObject(mActivity, flags);
+ dest.writeInt(mQueryFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ShortcutQueryWrapper(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ long changedSince = in.readLong();
+ String pkg = (flg & 0x2) == 0 ? null : in.readString();
+ List<String> shortcutIds = null;
+ if ((flg & 0x4) != 0) {
+ shortcutIds = new ArrayList<>();
+ in.readStringList(shortcutIds);
+ }
+ List<LocusId> locusIds = null;
+ if ((flg & 0x8) != 0) {
+ locusIds = new ArrayList<>();
+ in.readParcelableList(locusIds, LocusId.class.getClassLoader());
+ }
+ ComponentName activity = (flg & 0x10) == 0 ? null
+ : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
+ int queryFlags = in.readInt();
+
+ this.mChangedSince = changedSince;
+ this.mPackage = pkg;
+ this.mShortcutIds = shortcutIds;
+ this.mLocusIds = locusIds;
+ this.mActivity = activity;
+ this.mQueryFlags = queryFlags;
+ com.android.internal.util.AnnotationValidations.validate(
+ QueryFlags.class, null, mQueryFlags);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ShortcutQueryWrapper> CREATOR
+ = new Parcelable.Creator<ShortcutQueryWrapper>() {
+ @Override
+ public ShortcutQueryWrapper[] newArray(int size) {
+ return new ShortcutQueryWrapper[size];
+ }
+
+ @Override
+ public ShortcutQueryWrapper createFromParcel(@NonNull Parcel in) {
+ return new ShortcutQueryWrapper(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1582049937960L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java",
+ inputSignatures = "public long getChangedSince()\npublic @android.annotation.Nullable java.lang.String getPackage()\npublic @android.annotation.Nullable java.util.List<android.content.LocusId> getLocusIds()\npublic @android.annotation.Nullable java.util.List<java.lang.String> getShortcutIds()\npublic @android.annotation.Nullable android.content.ComponentName getActivity()\npublic int getQueryFlags()\nclass ShortcutQueryWrapper extends android.content.pm.LauncherApps.ShortcutQuery implements [android.os.Parcelable]\[email protected](genParcelable=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
new file mode 100644
index 0000000..3ed5c64
--- /dev/null
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -0,0 +1,123 @@
+/*
+ * 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.UserIdInt;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.LocusId;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+import java.util.List;
+
+/**
+ * Entry points used by {@link LauncherApps}.
+ *
+ * <p>No permission / argument checks will be performed inside.
+ * Callers must check the calling app permission and the calling package name.
+ * @hide
+ */
+public abstract class ShortcutServiceInternal {
+ public interface ShortcutChangeListener {
+ void onShortcutChanged(@NonNull String packageName, @UserIdInt int userId);
+ }
+
+ public abstract List<ShortcutInfo>
+ getShortcuts(int launcherUserId,
+ @NonNull String callingPackage, long changedSince,
+ @Nullable String packageName, @Nullable List<String> shortcutIds,
+ @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
+ @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid);
+
+ public abstract boolean
+ isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String id, int userId);
+
+ public abstract void pinShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId);
+
+ public abstract Intent[] createShortcutIntents(
+ int launcherUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ int callingPid, int callingUid);
+
+ public abstract void addListener(@NonNull ShortcutChangeListener listener);
+
+ public abstract void addShortcutChangeCallback(
+ @NonNull LauncherApps.ShortcutChangeCallback callback);
+
+ public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId);
+
+ /**
+ * Get the theme res ID of the starting window, it can be 0 if not specified.
+ */
+ public abstract @Nullable String getShortcutStartingThemeResName(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId,
+ int userId);
+
+ public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
+ @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId);
+
+ public abstract boolean hasShortcutHostPermission(int launcherUserId,
+ @NonNull String callingPackage, int callingPid, int callingUid);
+
+ public abstract void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+ int userId);
+
+ public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
+ @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
+ @Nullable IntentSender resultIntent, int userId);
+
+ public abstract boolean isRequestPinItemSupported(int callingUserId, int requestType);
+
+ public abstract boolean isForegroundDefaultLauncher(@NonNull String callingPackage,
+ int callingUid);
+
+ public abstract void cacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags);
+ public abstract void uncacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags);
+
+ /**
+ * Retrieves all of the direct share targets that match the given IntentFilter for the specified
+ * user.
+ */
+ public abstract List<ShortcutManager.ShareShortcutInfo> getShareTargets(
+ @NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId);
+
+ /**
+ * Returns the icon Uri of the shortcut, and grants Uri read permission to the caller.
+ */
+ public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId);
+
+ public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ @NonNull IntentFilter filter);
+}
diff --git a/android/content/pm/Signature.java b/android/content/pm/Signature.java
new file mode 100644
index 0000000..bce4b87
--- /dev/null
+++ b/android/content/pm/Signature.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2008 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+/**
+ * Opaque, immutable representation of a signing certificate associated with an
+ * application package.
+ * <p>
+ * This class name is slightly misleading, since it's not actually a signature.
+ */
+public class Signature implements Parcelable {
+ private final byte[] mSignature;
+ private int mHashCode;
+ private boolean mHaveHashCode;
+ private SoftReference<String> mStringRef;
+ private Certificate[] mCertificateChain;
+ /**
+ * 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 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.
+ */
+ private int mFlags;
+
+ /**
+ * Create Signature from an existing raw byte array.
+ */
+ public Signature(byte[] signature) {
+ mSignature = signature.clone();
+ mCertificateChain = null;
+ }
+
+ /**
+ * Create signature from a certificate chain. Used for backward
+ * compatibility.
+ *
+ * @throws CertificateEncodingException
+ * @hide
+ */
+ public Signature(Certificate[] certificateChain) throws CertificateEncodingException {
+ mSignature = certificateChain[0].getEncoded();
+ if (certificateChain.length > 1) {
+ mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length);
+ }
+ }
+
+ private static final int parseHexDigit(int nibble) {
+ if ('0' <= nibble && nibble <= '9') {
+ return nibble - '0';
+ } else if ('a' <= nibble && nibble <= 'f') {
+ return nibble - 'a' + 10;
+ } else if ('A' <= nibble && nibble <= 'F') {
+ return nibble - 'A' + 10;
+ } else {
+ throw new IllegalArgumentException("Invalid character " + nibble + " in hex string");
+ }
+ }
+
+ /**
+ * Create Signature from a text representation previously returned by
+ * {@link #toChars} or {@link #toCharsString()}. Signatures are expected to
+ * be a hex-encoded ASCII string.
+ *
+ * @param text hex-encoded string representing the signature
+ * @throws IllegalArgumentException when signature is odd-length
+ */
+ public Signature(String text) {
+ final byte[] input = text.getBytes();
+ final int N = input.length;
+
+ if (N % 2 != 0) {
+ throw new IllegalArgumentException("text size " + N + " is not even");
+ }
+
+ final byte[] sig = new byte[N / 2];
+ int sigIndex = 0;
+
+ for (int i = 0; i < N;) {
+ final int hi = parseHexDigit(input[i++]);
+ final int lo = parseHexDigit(input[i++]);
+ sig[sigIndex++] = (byte) ((hi << 4) | lo);
+ }
+
+ mSignature = sig;
+ }
+
+ /**
+ * Copy constructor that creates a new instance from the provided {@code other} Signature.
+ *
+ * @hide
+ */
+ public Signature(Signature other) {
+ mSignature = other.mSignature.clone();
+ Certificate[] otherCertificateChain = other.mCertificateChain;
+ if (otherCertificateChain != null && otherCertificateChain.length > 1) {
+ mCertificateChain = Arrays.copyOfRange(otherCertificateChain, 1,
+ otherCertificateChain.length);
+ }
+ mFlags = other.mFlags;
+ }
+
+ /**
+ * Sets the flags representing the capabilities of the past signing certificate.
+ * @hide
+ */
+ public void setFlags(int flags) {
+ this.mFlags = flags;
+ }
+
+ /**
+ * Returns the flags representing the capabilities of the past signing certificate.
+ * @hide
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Encode the Signature as ASCII text.
+ */
+ public char[] toChars() {
+ return toChars(null, null);
+ }
+
+ /**
+ * Encode the Signature as ASCII text in to an existing array.
+ *
+ * @param existingArray Existing char array or null.
+ * @param outLen Output parameter for the number of characters written in
+ * to the array.
+ * @return Returns either <var>existingArray</var> if it was large enough
+ * to hold the ASCII representation, or a newly created char[] array if
+ * needed.
+ */
+ public char[] toChars(char[] existingArray, int[] outLen) {
+ byte[] sig = mSignature;
+ final int N = sig.length;
+ final int N2 = N*2;
+ char[] text = existingArray == null || N2 > existingArray.length
+ ? new char[N2] : existingArray;
+ for (int j=0; j<N; j++) {
+ byte v = sig[j];
+ int d = (v>>4)&0xf;
+ text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
+ d = v&0xf;
+ text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
+ }
+ if (outLen != null) outLen[0] = N;
+ return text;
+ }
+
+ /**
+ * Return the result of {@link #toChars()} as a String.
+ */
+ public String toCharsString() {
+ String str = mStringRef == null ? null : mStringRef.get();
+ if (str != null) {
+ return str;
+ }
+ str = new String(toChars());
+ mStringRef = new SoftReference<String>(str);
+ return str;
+ }
+
+ /**
+ * @return the contents of this signature as a byte array.
+ */
+ public byte[] toByteArray() {
+ byte[] bytes = new byte[mSignature.length];
+ System.arraycopy(mSignature, 0, bytes, 0, mSignature.length);
+ return bytes;
+ }
+
+ /**
+ * Returns the public key for this signature.
+ *
+ * @throws CertificateException when Signature isn't a valid X.509
+ * certificate; shouldn't happen.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public PublicKey getPublicKey() throws CertificateException {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);
+ final Certificate cert = certFactory.generateCertificate(bais);
+ return cert.getPublicKey();
+ }
+
+ /**
+ * Used for compatibility code that needs to check the certificate chain
+ * during upgrades.
+ *
+ * @throws CertificateEncodingException
+ * @hide
+ */
+ public Signature[] getChainSignatures() throws CertificateEncodingException {
+ if (mCertificateChain == null) {
+ return new Signature[] { this };
+ }
+
+ Signature[] chain = new Signature[1 + mCertificateChain.length];
+ chain[0] = this;
+
+ int i = 1;
+ for (Certificate c : mCertificateChain) {
+ chain[i++] = new Signature(c.getEncoded());
+ }
+
+ return chain;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ try {
+ if (obj != null) {
+ Signature other = (Signature)obj;
+ // Note, some classes, such as PackageParser.SigningDetails, rely on equals
+ // only comparing the mSignature arrays without the flags.
+ return this == other || Arrays.equals(mSignature, other.mSignature);
+ }
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mHaveHashCode) {
+ return mHashCode;
+ }
+ // Note, similar to equals some classes rely on the hash code not including
+ // the flags for Set membership checks.
+ mHashCode = Arrays.hashCode(mSignature);
+ mHaveHashCode = true;
+ return mHashCode;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeByteArray(mSignature);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<Signature> CREATOR
+ = new Parcelable.Creator<Signature>() {
+ public Signature createFromParcel(Parcel source) {
+ return new Signature(source);
+ }
+
+ public Signature[] newArray(int size) {
+ return new Signature[size];
+ }
+ };
+
+ /** {@hide} */
+ public void writeToXmlAttributeBytesHex(@NonNull TypedXmlSerializer out,
+ @Nullable String namespace, @NonNull String name) throws IOException {
+ out.attributeBytesHex(namespace, name, mSignature);
+ }
+
+ private Signature(Parcel source) {
+ mSignature = source.createByteArray();
+ }
+
+ /**
+ * Test if given {@link Signature} sets are exactly equal.
+ *
+ * @hide
+ */
+ public static boolean areExactMatch(Signature[] a, Signature[] b) {
+ return (a.length == b.length) && ArrayUtils.containsAll(a, b)
+ && ArrayUtils.containsAll(b, a);
+ }
+
+ /**
+ * Test if given {@link Signature} sets 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 = new Signature[a.length];
+ for (int i = 0; i < a.length; i++) {
+ aPrime[i] = bounce(cf, a[i]);
+ }
+ final Signature[] bPrime = new Signature[b.length];
+ for (int i = 0; i < b.length; i++) {
+ bPrime[i] = bounce(cf, b[i]);
+ }
+
+ return areExactMatch(aPrime, bPrime);
+ }
+
+ /**
+ * 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
+ * substantially, usually a signal of something fishy going on.
+ * @hide
+ */
+ public static Signature bounce(CertificateFactory cf, Signature s) throws CertificateException {
+ final InputStream is = new ByteArrayInputStream(s.mSignature);
+ final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+ final Signature sPrime = new Signature(cert.getEncoded());
+
+ if (Math.abs(sPrime.mSignature.length - s.mSignature.length) > 2) {
+ throw new CertificateException("Bounced cert length looks fishy; before "
+ + s.mSignature.length + ", after " + sPrime.mSignature.length);
+ }
+
+ return sPrime;
+ }
+}
\ No newline at end of file
diff --git a/android/content/pm/SigningInfo.java b/android/content/pm/SigningInfo.java
new file mode 100644
index 0000000..d14be9c
--- /dev/null
+++ b/android/content/pm/SigningInfo.java
@@ -0,0 +1,139 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information pertaining to the signing certificates used to sign a package.
+ */
+public final class SigningInfo implements Parcelable {
+
+ @NonNull
+ private final PackageParser.SigningDetails mSigningDetails;
+
+ public SigningInfo() {
+ mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+ }
+
+ /**
+ * @hide only packagemanager should be populating this
+ */
+ public SigningInfo(PackageParser.SigningDetails signingDetails) {
+ mSigningDetails = new PackageParser.SigningDetails(signingDetails);
+ }
+
+ public SigningInfo(SigningInfo orig) {
+ mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails);
+ }
+
+ private SigningInfo(Parcel source) {
+ mSigningDetails = PackageParser.SigningDetails.CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Although relatively uncommon, packages may be signed by more than one signer, in which case
+ * their identity is viewed as being the set of all signers, not just any one.
+ */
+ public boolean hasMultipleSigners() {
+ return mSigningDetails.signatures != null && mSigningDetails.signatures.length > 1;
+ }
+
+ /**
+ * APK Signature Scheme v3 enables packages to provide a proof-of-rotation record that the
+ * platform verifies, and uses, to allow the use of new signing certificates. This is only
+ * available to packages that are not signed by multiple signers. In the event of a change to a
+ * new signing certificate, the package's past signing certificates are presented as well. Any
+ * check of a package's signing certificate should also include a search through its entire
+ * signing history, since it could change to a new signing certificate at any time.
+ */
+ public boolean hasPastSigningCertificates() {
+ return mSigningDetails.signatures != null
+ && mSigningDetails.pastSigningCertificates != null;
+ }
+
+ /**
+ * Returns the signing certificates this package has proven it is authorized to use. This
+ * includes both the signing certificate associated with the signer of the package and the past
+ * signing certificates it included as its proof of signing certificate rotation. This method
+ * is the preferred replacement for the {@code GET_SIGNATURES} flag used with {@link
+ * PackageManager#getPackageInfo(String, int)}. When determining if a package is signed by a
+ * desired certificate, the returned array should be checked to determine if it is one of the
+ * entries.
+ *
+ * <note>
+ * This method returns null if the package is signed by multiple signing certificates, as
+ * opposed to being signed by one current signer and also providing the history of past
+ * signing certificates. {@link #hasMultipleSigners()} may be used to determine if this
+ * package is signed by multiple signers. Packages which are signed by multiple signers
+ * cannot change their signing certificates and their {@code Signature} array should be
+ * checked to make sure that every entry matches the looked-for signing certificates.
+ * </note>
+ */
+ public Signature[] getSigningCertificateHistory() {
+ if (hasMultipleSigners()) {
+ return null;
+ } else if (!hasPastSigningCertificates()) {
+
+ // this package is only signed by one signer with no history, return it
+ return mSigningDetails.signatures;
+ } else {
+
+ // this package has provided proof of past signing certificates, include them
+ return mSigningDetails.pastSigningCertificates;
+ }
+ }
+
+ /**
+ * Returns the signing certificates used to sign the APK contents of this application. Not
+ * including any past signing certificates the package proved it is authorized to use.
+ * <note>
+ * This method should not be used unless {@link #hasMultipleSigners()} returns true,
+ * indicating that {@link #getSigningCertificateHistory()} cannot be used, otherwise {@link
+ * #getSigningCertificateHistory()} should be preferred.
+ * </note>
+ */
+ public Signature[] getApkContentsSigners() {
+ return mSigningDetails.signatures;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ mSigningDetails.writeToParcel(dest, parcelableFlags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
+ new Parcelable.Creator<SigningInfo>() {
+ @Override
+ public SigningInfo createFromParcel(Parcel source) {
+ return new SigningInfo(source);
+ }
+
+ @Override
+ public SigningInfo[] newArray(int size) {
+ return new SigningInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/StringParceledListSlice.java b/android/content/pm/StringParceledListSlice.java
new file mode 100644
index 0000000..9540744
--- /dev/null
+++ b/android/content/pm/StringParceledListSlice.java
@@ -0,0 +1,83 @@
+/*
+ * 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.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * @see BaseParceledListSlice
+ *
+ * @hide
+ */
+public class StringParceledListSlice extends BaseParceledListSlice<String> {
+ public StringParceledListSlice(List<String> list) {
+ super(list);
+ }
+
+ private StringParceledListSlice(Parcel in, ClassLoader loader) {
+ super(in, loader);
+ }
+
+ public static StringParceledListSlice emptyList() {
+ return new StringParceledListSlice(Collections.<String> emptyList());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ protected void writeElement(String parcelable, Parcel reply, int callFlags) {
+ reply.writeString(parcelable);
+ }
+
+ @Override
+ protected void writeParcelableCreator(String parcelable, Parcel dest) {
+ return;
+ }
+
+ @Override
+ protected Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
+ return Parcel.STRING_CREATOR;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static final Parcelable.ClassLoaderCreator<StringParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<StringParceledListSlice>() {
+ public StringParceledListSlice createFromParcel(Parcel in) {
+ return new StringParceledListSlice(in, null);
+ }
+
+ @Override
+ public StringParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new StringParceledListSlice(in, loader);
+ }
+
+ @Override
+ public StringParceledListSlice[] newArray(int size) {
+ return new StringParceledListSlice[size];
+ }
+ };
+}
diff --git a/android/content/pm/SuspendDialogInfo.java b/android/content/pm/SuspendDialogInfo.java
new file mode 100644
index 0000000..23945ee
--- /dev/null
+++ b/android/content/pm/SuspendDialogInfo.java
@@ -0,0 +1,540 @@
+/*
+ * 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.res.Resources.ID_NULL;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.SystemApi;
+import android.content.res.ResourceId;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * A container to describe the dialog to be shown when the user tries to launch a suspended
+ * application. The suspending app can customize the dialog's following attributes:
+ * <ul>
+ * <li>The dialog icon, by providing a resource id.
+ * <li>The title text, by providing a resource id.
+ * <li>The text of the dialog's body, by providing a resource id or a string.
+ * <li>The text on the neutral button by providing a resource id.
+ * <li>The action performed on tapping the neutral button. Only {@link #BUTTON_ACTION_UNSUSPEND}
+ * and {@link #BUTTON_ACTION_MORE_DETAILS} are currently supported.
+ * </ul>
+ * System defaults are used whenever any of these are not provided, or any of the provided resource
+ * ids cannot be resolved at the time of displaying the dialog.
+ *
+ * @hide
+ * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo)
+ * @see Builder
+ */
+@SystemApi
+public final class SuspendDialogInfo implements Parcelable {
+ private static final String TAG = SuspendDialogInfo.class.getSimpleName();
+ private static final String XML_ATTR_ICON_RES_ID = "iconResId";
+ private static final String XML_ATTR_TITLE_RES_ID = "titleResId";
+ private static final String XML_ATTR_TITLE = "title";
+ private static final String XML_ATTR_DIALOG_MESSAGE_RES_ID = "dialogMessageResId";
+ private static final String XML_ATTR_DIALOG_MESSAGE = "dialogMessage";
+ private static final String XML_ATTR_BUTTON_TEXT_RES_ID = "buttonTextResId";
+ private static final String XML_ATTR_BUTTON_TEXT = "buttonText";
+ private static final String XML_ATTR_BUTTON_ACTION = "buttonAction";
+
+ private final int mIconResId;
+ private final int mTitleResId;
+ private final String mTitle;
+ private final int mDialogMessageResId;
+ private final String mDialogMessage;
+ private final int mNeutralButtonTextResId;
+ private final String mNeutralButtonText;
+ private final int mNeutralButtonAction;
+
+ /**
+ * Used with {@link Builder#setNeutralButtonAction(int)} to create a neutral button that
+ * starts the {@link android.content.Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} activity.
+ * @see Builder#setNeutralButtonAction(int)
+ */
+ public static final int BUTTON_ACTION_MORE_DETAILS = 0;
+
+ /**
+ * Used with {@link Builder#setNeutralButtonAction(int)} to create a neutral button that
+ * unsuspends the app that the user was trying to launch and continues with the launch. The
+ * system also sends the broadcast
+ * {@link android.content.Intent#ACTION_PACKAGE_UNSUSPENDED_MANUALLY} to the suspending app
+ * when this happens.
+ * @see Builder#setNeutralButtonAction(int)
+ * @see android.content.Intent#ACTION_PACKAGE_UNSUSPENDED_MANUALLY
+ */
+ public static final int BUTTON_ACTION_UNSUSPEND = 1;
+
+ /**
+ * Button actions to specify what happens when the user taps on the neutral button.
+ * To be used with {@link Builder#setNeutralButtonAction(int)}.
+ *
+ * @hide
+ * @see Builder#setNeutralButtonAction(int)
+ */
+ @IntDef(flag = true, prefix = {"BUTTON_ACTION_"}, value = {
+ BUTTON_ACTION_MORE_DETAILS,
+ BUTTON_ACTION_UNSUSPEND
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ButtonAction {
+ }
+
+ /**
+ * @return the resource id of the icon to be used with the dialog
+ * @hide
+ */
+ @DrawableRes
+ public int getIconResId() {
+ return mIconResId;
+ }
+
+ /**
+ * @return the resource id of the title to be used with the dialog
+ * @hide
+ */
+ @StringRes
+ public int getTitleResId() {
+ return mTitleResId;
+ }
+
+ /**
+ * @return the title to be shown on the dialog. Returns {@code null} if {@link #getTitleResId()}
+ * returns a valid resource id
+ * @hide
+ */
+ @Nullable
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * @return the resource id of the text to be shown in the dialog's body
+ * @hide
+ */
+ @StringRes
+ public int getDialogMessageResId() {
+ return mDialogMessageResId;
+ }
+
+ /**
+ * @return the text to be shown in the dialog's body. Returns {@code null} if {@link
+ * #getDialogMessageResId()} returns a valid resource id
+ * @hide
+ */
+ @Nullable
+ public String getDialogMessage() {
+ return mDialogMessage;
+ }
+
+ /**
+ * @return the text to be shown on the neutral button
+ * @hide
+ */
+ @StringRes
+ public int getNeutralButtonTextResId() {
+ return mNeutralButtonTextResId;
+ }
+
+ /**
+ * @return the text to be shown on the neutral button. Returns {@code null} if
+ * {@link #getNeutralButtonTextResId()} returns a valid resource id
+ * @hide
+ */
+ @Nullable
+ public String getNeutralButtonText() {
+ return mNeutralButtonText;
+ }
+
+ /**
+ * @return The {@link ButtonAction} that happens on tapping this button
+ * @hide
+ */
+ @ButtonAction
+ public int getNeutralButtonAction() {
+ return mNeutralButtonAction;
+ }
+
+ /**
+ * @hide
+ */
+ public void saveToXml(TypedXmlSerializer out) throws IOException {
+ if (mIconResId != ID_NULL) {
+ out.attributeInt(null, XML_ATTR_ICON_RES_ID, mIconResId);
+ }
+ if (mTitleResId != ID_NULL) {
+ out.attributeInt(null, XML_ATTR_TITLE_RES_ID, mTitleResId);
+ } else {
+ XmlUtils.writeStringAttribute(out, XML_ATTR_TITLE, mTitle);
+ }
+ if (mDialogMessageResId != ID_NULL) {
+ out.attributeInt(null, XML_ATTR_DIALOG_MESSAGE_RES_ID, mDialogMessageResId);
+ } else {
+ XmlUtils.writeStringAttribute(out, XML_ATTR_DIALOG_MESSAGE, mDialogMessage);
+ }
+ if (mNeutralButtonTextResId != ID_NULL) {
+ out.attributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId);
+ } else {
+ XmlUtils.writeStringAttribute(out, XML_ATTR_BUTTON_TEXT, mNeutralButtonText);
+ }
+ out.attributeInt(null, XML_ATTR_BUTTON_ACTION, mNeutralButtonAction);
+ }
+
+ /**
+ * @hide
+ */
+ public static SuspendDialogInfo restoreFromXml(TypedXmlPullParser in) {
+ final SuspendDialogInfo.Builder dialogInfoBuilder = new SuspendDialogInfo.Builder();
+ try {
+ final int iconId = in.getAttributeInt(null, XML_ATTR_ICON_RES_ID, ID_NULL);
+ final int titleId = in.getAttributeInt(null, XML_ATTR_TITLE_RES_ID, ID_NULL);
+ final String title = XmlUtils.readStringAttribute(in, XML_ATTR_TITLE);
+ final int buttonTextId =
+ in.getAttributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, ID_NULL);
+ final String buttonText = XmlUtils.readStringAttribute(in, XML_ATTR_BUTTON_TEXT);
+ final int buttonAction =
+ in.getAttributeInt(null, XML_ATTR_BUTTON_ACTION, BUTTON_ACTION_MORE_DETAILS);
+ final int dialogMessageResId =
+ in.getAttributeInt(null, XML_ATTR_DIALOG_MESSAGE_RES_ID, ID_NULL);
+ final String dialogMessage = XmlUtils.readStringAttribute(in, XML_ATTR_DIALOG_MESSAGE);
+
+ if (iconId != ID_NULL) {
+ dialogInfoBuilder.setIcon(iconId);
+ }
+ if (titleId != ID_NULL) {
+ dialogInfoBuilder.setTitle(titleId);
+ } else if (title != null) {
+ dialogInfoBuilder.setTitle(title);
+ }
+ if (buttonTextId != ID_NULL) {
+ dialogInfoBuilder.setNeutralButtonText(buttonTextId);
+ } else if (buttonText != null) {
+ dialogInfoBuilder.setNeutralButtonText(buttonText);
+ }
+ if (dialogMessageResId != ID_NULL) {
+ dialogInfoBuilder.setMessage(dialogMessageResId);
+ } else if (dialogMessage != null) {
+ dialogInfoBuilder.setMessage(dialogMessage);
+ }
+ dialogInfoBuilder.setNeutralButtonAction(buttonAction);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception while parsing from xml. Some fields may default", e);
+ }
+ return dialogInfoBuilder.build();
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = mIconResId;
+ hashCode = 31 * hashCode + mTitleResId;
+ hashCode = 31 * hashCode + Objects.hashCode(mTitle);
+ hashCode = 31 * hashCode + mNeutralButtonTextResId;
+ hashCode = 31 * hashCode + Objects.hashCode(mNeutralButtonText);
+ hashCode = 31 * hashCode + mDialogMessageResId;
+ hashCode = 31 * hashCode + Objects.hashCode(mDialogMessage);
+ hashCode = 31 * hashCode + mNeutralButtonAction;
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SuspendDialogInfo)) {
+ return false;
+ }
+ final SuspendDialogInfo otherDialogInfo = (SuspendDialogInfo) obj;
+ return mIconResId == otherDialogInfo.mIconResId
+ && mTitleResId == otherDialogInfo.mTitleResId
+ && Objects.equals(mTitle, otherDialogInfo.mTitle)
+ && mDialogMessageResId == otherDialogInfo.mDialogMessageResId
+ && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage)
+ && mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId
+ && Objects.equals(mNeutralButtonText, otherDialogInfo.mNeutralButtonText)
+ && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("SuspendDialogInfo: {");
+ if (mIconResId != ID_NULL) {
+ builder.append("mIconId = 0x");
+ builder.append(Integer.toHexString(mIconResId));
+ builder.append(" ");
+ }
+ if (mTitleResId != ID_NULL) {
+ builder.append("mTitleResId = 0x");
+ builder.append(Integer.toHexString(mTitleResId));
+ builder.append(" ");
+ } else if (mTitle != null) {
+ builder.append("mTitle = \"");
+ builder.append(mTitle);
+ builder.append("\"");
+ }
+ if (mNeutralButtonTextResId != ID_NULL) {
+ builder.append("mNeutralButtonTextResId = 0x");
+ builder.append(Integer.toHexString(mNeutralButtonTextResId));
+ builder.append(" ");
+ } else if (mNeutralButtonText != null) {
+ builder.append("mNeutralButtonText = \"");
+ builder.append(mNeutralButtonText);
+ builder.append("\"");
+ }
+ if (mDialogMessageResId != ID_NULL) {
+ builder.append("mDialogMessageResId = 0x");
+ builder.append(Integer.toHexString(mDialogMessageResId));
+ builder.append(" ");
+ } else if (mDialogMessage != null) {
+ builder.append("mDialogMessage = \"");
+ builder.append(mDialogMessage);
+ builder.append("\" ");
+ }
+ builder.append("mNeutralButtonAction = ");
+ builder.append(mNeutralButtonAction);
+ builder.append("}");
+ return builder.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(mIconResId);
+ dest.writeInt(mTitleResId);
+ dest.writeString(mTitle);
+ dest.writeInt(mDialogMessageResId);
+ dest.writeString(mDialogMessage);
+ dest.writeInt(mNeutralButtonTextResId);
+ dest.writeString(mNeutralButtonText);
+ dest.writeInt(mNeutralButtonAction);
+ }
+
+ private SuspendDialogInfo(Parcel source) {
+ mIconResId = source.readInt();
+ mTitleResId = source.readInt();
+ mTitle = source.readString();
+ mDialogMessageResId = source.readInt();
+ mDialogMessage = source.readString();
+ mNeutralButtonTextResId = source.readInt();
+ mNeutralButtonText = source.readString();
+ mNeutralButtonAction = source.readInt();
+ }
+
+ SuspendDialogInfo(Builder b) {
+ mIconResId = b.mIconResId;
+ mTitleResId = b.mTitleResId;
+ mTitle = (mTitleResId == ID_NULL) ? b.mTitle : null;
+ mDialogMessageResId = b.mDialogMessageResId;
+ mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null;
+ mNeutralButtonTextResId = b.mNeutralButtonTextResId;
+ mNeutralButtonText = (mNeutralButtonTextResId == ID_NULL) ? b.mNeutralButtonText : null;
+ mNeutralButtonAction = b.mNeutralButtonAction;
+ }
+
+ public static final @NonNull Creator<SuspendDialogInfo> CREATOR =
+ new Creator<SuspendDialogInfo>() {
+ @Override
+ public SuspendDialogInfo createFromParcel(Parcel source) {
+ return new SuspendDialogInfo(source);
+ }
+
+ @Override
+ public SuspendDialogInfo[] newArray(int size) {
+ return new SuspendDialogInfo[size];
+ }
+ };
+
+ /**
+ * Builder to build a {@link SuspendDialogInfo} object.
+ */
+ public static final class Builder {
+ private int mDialogMessageResId = ID_NULL;
+ private String mDialogMessage;
+ private int mTitleResId = ID_NULL;
+ private String mTitle;
+ private int mIconResId = ID_NULL;
+ private int mNeutralButtonTextResId = ID_NULL;
+ private String mNeutralButtonText;
+ private int mNeutralButtonAction = BUTTON_ACTION_MORE_DETAILS;
+
+ /**
+ * Set the resource id of the icon to be used. If not provided, no icon will be shown.
+ *
+ * @param resId The resource id of the icon.
+ * @return this builder object.
+ */
+ @NonNull
+ public Builder setIcon(@DrawableRes int resId) {
+ Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
+ mIconResId = resId;
+ return this;
+ }
+
+ /**
+ * Set the resource id of the title text to be displayed. If this is not provided, the
+ * system will use a default title.
+ *
+ * @param resId The resource id of the title.
+ * @return this builder object.
+ */
+ @NonNull
+ public Builder setTitle(@StringRes int resId) {
+ Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
+ mTitleResId = resId;
+ return this;
+ }
+
+ /**
+ * Set the title text of the dialog. Ignored if a resource id is set via
+ * {@link #setTitle(int)}
+ *
+ * @param title The title of the dialog.
+ * @return this builder object.
+ * @see #setTitle(int)
+ */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ Preconditions.checkStringNotEmpty(title, "Title cannot be null or empty");
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the text to show in the body of the dialog. Ignored if a resource id is set via
+ * {@link #setMessage(int)}.
+ * <p>
+ * The system will use {@link String#format(Locale, String, Object...) String.format} to
+ * insert the suspended app name into the message, so an example format string could be
+ * {@code "The app %1$s is currently suspended"}. This is optional - if the string passed in
+ * {@code message} does not accept an argument, it will be used as is.
+ *
+ * @param message The dialog message.
+ * @return this builder object.
+ * @see #setMessage(int)
+ */
+ @NonNull
+ public Builder setMessage(@NonNull String message) {
+ Preconditions.checkStringNotEmpty(message, "Message cannot be null or empty");
+ mDialogMessage = message;
+ return this;
+ }
+
+ /**
+ * Set the resource id of the dialog message to be shown. If no dialog message is provided
+ * via either this method or {@link #setMessage(String)}, the system will use a default
+ * message.
+ * <p>
+ * The system will use {@link android.content.res.Resources#getString(int, Object...)
+ * getString} to insert the suspended app name into the message, so an example format string
+ * could be {@code "The app %1$s is currently suspended"}. This is optional - if the string
+ * referred to by {@code resId} does not accept an argument, it will be used as is.
+ *
+ * @param resId The resource id of the dialog message.
+ * @return this builder object.
+ * @see #setMessage(String)
+ */
+ @NonNull
+ public Builder setMessage(@StringRes int resId) {
+ Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
+ mDialogMessageResId = resId;
+ return this;
+ }
+
+ /**
+ * Set the resource id of text to be shown on the neutral button. Tapping this button would
+ * perform the {@link ButtonAction action} specified through
+ * {@link #setNeutralButtonAction(int)}. If this is not provided, the system will use a
+ * default text.
+ *
+ * @param resId The resource id of the button text
+ * @return this builder object.
+ */
+ @NonNull
+ public Builder setNeutralButtonText(@StringRes int resId) {
+ Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
+ mNeutralButtonTextResId = resId;
+ return this;
+ }
+
+ /**
+ * Set the text to be shown on the neutral button. Ignored if a resource id is set via
+ * {@link #setNeutralButtonText(int)}
+ *
+ * @param neutralButtonText The title of the dialog.
+ * @return this builder object.
+ * @see #setNeutralButtonText(int)
+ */
+ @NonNull
+ public Builder setNeutralButtonText(@NonNull String neutralButtonText) {
+ Preconditions.checkStringNotEmpty(neutralButtonText,
+ "Button text cannot be null or empty");
+ mNeutralButtonText = neutralButtonText;
+ return this;
+ }
+
+ /**
+ * Set the action expected to happen on neutral button tap. Defaults to
+ * {@link #BUTTON_ACTION_MORE_DETAILS} if this is not provided.
+ *
+ * @param buttonAction Either {@link #BUTTON_ACTION_MORE_DETAILS} or
+ * {@link #BUTTON_ACTION_UNSUSPEND}.
+ * @return this builder object
+ */
+ @NonNull
+ public Builder setNeutralButtonAction(@ButtonAction int buttonAction) {
+ Preconditions.checkArgument(buttonAction == BUTTON_ACTION_MORE_DETAILS
+ || buttonAction == BUTTON_ACTION_UNSUSPEND, "Invalid button action");
+ mNeutralButtonAction = buttonAction;
+ return this;
+ }
+
+ /**
+ * Build the final object based on given inputs.
+ *
+ * @return The {@link SuspendDialogInfo} object built using this builder.
+ */
+ @NonNull
+ public SuspendDialogInfo build() {
+ return new SuspendDialogInfo(this);
+ }
+ }
+}
diff --git a/android/content/pm/TestUtilityService.java b/android/content/pm/TestUtilityService.java
new file mode 100644
index 0000000..426352b
--- /dev/null
+++ b/android/content/pm/TestUtilityService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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.os.IBinder;
+
+/**
+ * Utility methods for testing and debugging.
+ */
+public interface TestUtilityService {
+ /**
+ * Verifies validity of the token passed as a parameter to holdLock(). Throws an exception if
+ * the token is invalid.
+ */
+ void verifyHoldLockToken(IBinder token);
+}
diff --git a/android/content/pm/UserInfo.java b/android/content/pm/UserInfo.java
new file mode 100644
index 0000000..5a89708
--- /dev/null
+++ b/android/content/pm/UserInfo.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2011 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.DebugUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Per-user information.
+ *
+ * <p>There are 3 base properties of users: {@link #FLAG_SYSTEM}, {@link #FLAG_FULL}, and
+ * {@link #FLAG_PROFILE}. Every user must have one of the following combination of these
+ * flags:
+ * <ul>
+ * <li>FLAG_SYSTEM (user {@link UserHandle#USER_SYSTEM} on a headless-user-0 device)</li>
+ * <li>FLAG_SYSTEM and FLAG_FULL (user {@link UserHandle#USER_SYSTEM} on a regular device)</li>
+ * <li>FLAG_FULL (non-profile secondary user)</li>
+ * <li>FLAG_PROFILE (profile users)</li>
+ * </ul>
+ * Users can have also have additional flags (such as FLAG_GUEST) as appropriate.
+ *
+ * @hide
+ */
+@TestApi
+public class UserInfo implements Parcelable {
+
+ /**
+ * *************************** NOTE ***************************
+ * These flag values CAN NOT CHANGE because they are written
+ * directly to storage.
+ */
+
+ /**
+ * Primary user. Only one user can have this flag set. It identifies the first human user
+ * on a device. This flag is not supported in headless system user mode.
+ */
+ @UnsupportedAppUsage
+ public static final int FLAG_PRIMARY = 0x00000001;
+
+ /**
+ * User with administrative privileges. Such a user can create and
+ * delete users.
+ */
+ public static final int FLAG_ADMIN = 0x00000002;
+
+ /**
+ * Indicates a guest user that may be transient.
+ * @deprecated Use {@link UserManager#USER_TYPE_FULL_GUEST} instead.
+ */
+ @Deprecated
+ public static final int FLAG_GUEST = 0x00000004;
+
+ /**
+ * Indicates the user has restrictions in privileges, in addition to those for normal users.
+ * Exact meaning TBD. For instance, maybe they can't install apps or administer WiFi access pts.
+ * @deprecated Use {@link UserManager#USER_TYPE_FULL_RESTRICTED} instead.
+ */
+ @Deprecated
+ public static final int FLAG_RESTRICTED = 0x00000008;
+
+ /**
+ * Indicates that this user has gone through its first-time initialization.
+ */
+ public static final int FLAG_INITIALIZED = 0x00000010;
+
+ /**
+ * Indicates that this user is a profile of another user, for example holding a users
+ * corporate data.
+ * @deprecated Use {@link UserManager#USER_TYPE_PROFILE_MANAGED} instead.
+ */
+ @Deprecated
+ public static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
+ /**
+ * Indicates that this user is disabled.
+ *
+ * <p>Note: If an ephemeral user is disabled, it shouldn't be later re-enabled. Ephemeral users
+ * are disabled as their removal is in progress to indicate that they shouldn't be re-entered.
+ */
+ public static final int FLAG_DISABLED = 0x00000040;
+
+ public static final int FLAG_QUIET_MODE = 0x00000080;
+
+ /**
+ * Indicates that this user is ephemeral. I.e. the user will be removed after leaving
+ * the foreground.
+ */
+ public static final int FLAG_EPHEMERAL = 0x00000100;
+
+ /**
+ * User is for demo purposes only and can be removed at any time.
+ * @deprecated Use {@link UserManager#USER_TYPE_FULL_DEMO} instead.
+ */
+ @Deprecated
+ public static final int FLAG_DEMO = 0x00000200;
+
+ /**
+ * Indicates that this user is a non-profile human user.
+ *
+ * <p>When creating a new (non-system) user, this flag will always be forced true unless the
+ * user is a {@link #FLAG_PROFILE}. If user {@link UserHandle#USER_SYSTEM} is also a
+ * human user, it must also be flagged as FULL.
+ */
+ public static final int FLAG_FULL = 0x00000400;
+
+ /**
+ * Indicates that this user is {@link UserHandle#USER_SYSTEM}. Not applicable to created users.
+ */
+ public static final int FLAG_SYSTEM = 0x00000800;
+
+ /**
+ * Indicates that this user is a profile human user, such as a managed profile.
+ * Mutually exclusive with {@link #FLAG_FULL}.
+ */
+ public static final int FLAG_PROFILE = 0x00001000;
+
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_PRIMARY,
+ FLAG_ADMIN,
+ FLAG_GUEST,
+ FLAG_RESTRICTED,
+ FLAG_INITIALIZED,
+ FLAG_MANAGED_PROFILE,
+ FLAG_DISABLED,
+ FLAG_QUIET_MODE,
+ FLAG_EPHEMERAL,
+ FLAG_DEMO,
+ FLAG_FULL,
+ FLAG_SYSTEM,
+ FLAG_PROFILE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserInfoFlag {
+ }
+
+ public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
+
+ @UnsupportedAppUsage
+ public @UserIdInt int id;
+ @UnsupportedAppUsage
+ public int serialNumber;
+ @UnsupportedAppUsage
+ public String name;
+ @UnsupportedAppUsage
+ public String iconPath;
+ @UnsupportedAppUsage
+ public @UserInfoFlag int flags;
+ @UnsupportedAppUsage
+ public long creationTime;
+ @UnsupportedAppUsage
+ public long lastLoggedInTime;
+ public String lastLoggedInFingerprint;
+
+ /**
+ * Type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}, corresponding to
+ * {@link com.android.server.pm.UserTypeDetails#getName()}.
+ */
+ public String userType;
+
+ /**
+ * If this user is a parent user, it would be its own user id.
+ * If this user is a child user, it would be its parent user id.
+ * Otherwise, it would be {@link #NO_PROFILE_GROUP_ID}.
+ */
+ @UnsupportedAppUsage
+ public int profileGroupId;
+ public int restrictedProfileParentId;
+
+ /**
+ * Index for distinguishing different profiles with the same parent and user type for the
+ * purpose of badging.
+ * It is used for determining which badge color/label to use (if applicable) from
+ * the options available for a particular user type.
+ */
+ public int profileBadge;
+
+ /** User is only partially created. */
+ @UnsupportedAppUsage
+ public boolean partial;
+ @UnsupportedAppUsage
+ public boolean guestToRemove;
+
+ /**
+ * This is used to optimize the creation of an user, i.e. OEMs might choose to pre-create a
+ * number of users at the first boot, so the actual creation later is faster.
+ *
+ * <p>A {@code preCreated} user is not a real user yet, so it should not show up on regular
+ * user operations (other than user creation per se).
+ *
+ * <p>Once the pre-created is used to create a "real" user later on, {@code preCreate} is set to
+ * {@code false}.
+ */
+ public boolean preCreated;
+
+ /**
+ * When {@code true}, it indicates this user was created by converting a {@link #preCreated}
+ * user.
+ *
+ * <p><b>NOTE: </b>only used for debugging purposes, it's not set when marshalled to a parcel.
+ */
+ public boolean convertedFromPreCreated;
+
+ /**
+ * Creates a UserInfo whose user type is determined automatically by the flags according to
+ * {@link #getDefaultUserType}; can only be used for user types handled there.
+ */
+ @UnsupportedAppUsage
+ public UserInfo(int id, String name, int flags) {
+ this(id, name, null, flags);
+ }
+
+ /**
+ * Creates a UserInfo whose user type is determined automatically by the flags according to
+ * {@link #getDefaultUserType}; can only be used for user types handled there.
+ */
+ @UnsupportedAppUsage
+ public UserInfo(int id, String name, String iconPath, int flags) {
+ this(id, name, iconPath, flags, getDefaultUserType(flags));
+ }
+
+ public UserInfo(int id, String name, String iconPath, int flags, String userType) {
+ this.id = id;
+ this.name = name;
+ this.flags = flags;
+ this.userType = userType;
+ this.iconPath = iconPath;
+ this.profileGroupId = NO_PROFILE_GROUP_ID;
+ this.restrictedProfileParentId = NO_PROFILE_GROUP_ID;
+ }
+
+ /**
+ * Get the user type (such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}) that corresponds to
+ * the given {@link UserInfoFlag}s.
+
+ * <p>The userInfoFlag can contain GUEST, RESTRICTED, MANAGED_PROFILE, DEMO, or else be
+ * interpreted as a regular "secondary" user. It cannot contain more than one of these.
+ * It can contain other UserInfoFlag properties (like EPHEMERAL), which will be ignored here.
+ *
+ * @throws IllegalArgumentException if userInfoFlag is more than one type of user or if it
+ * is a SYSTEM user.
+ *
+ * @hide
+ */
+ public static @NonNull String getDefaultUserType(@UserInfoFlag int userInfoFlag) {
+ if ((userInfoFlag & FLAG_SYSTEM) != 0) {
+ throw new IllegalArgumentException("Cannot getDefaultUserType for flags "
+ + Integer.toHexString(userInfoFlag) + " because it corresponds to a "
+ + "SYSTEM user type.");
+ }
+ final int supportedFlagTypes =
+ FLAG_GUEST | FLAG_RESTRICTED | FLAG_MANAGED_PROFILE | FLAG_DEMO;
+ switch (userInfoFlag & supportedFlagTypes) {
+ case 0 : return UserManager.USER_TYPE_FULL_SECONDARY;
+ case FLAG_GUEST: return UserManager.USER_TYPE_FULL_GUEST;
+ case FLAG_RESTRICTED: return UserManager.USER_TYPE_FULL_RESTRICTED;
+ case FLAG_MANAGED_PROFILE: return UserManager.USER_TYPE_PROFILE_MANAGED;
+ case FLAG_DEMO: return UserManager.USER_TYPE_FULL_DEMO;
+ default:
+ throw new IllegalArgumentException("Cannot getDefaultUserType for flags "
+ + Integer.toHexString(userInfoFlag) + " because it doesn't correspond to a "
+ + "valid user type.");
+ }
+ }
+
+ @UnsupportedAppUsage
+ public boolean isPrimary() {
+ return (flags & FLAG_PRIMARY) == FLAG_PRIMARY;
+ }
+
+ @UnsupportedAppUsage
+ public boolean isAdmin() {
+ return (flags & FLAG_ADMIN) == FLAG_ADMIN;
+ }
+
+ @UnsupportedAppUsage
+ public boolean isGuest() {
+ return UserManager.isUserTypeGuest(userType);
+ }
+
+ @UnsupportedAppUsage
+ public boolean isRestricted() {
+ return UserManager.isUserTypeRestricted(userType);
+ }
+
+ public boolean isProfile() {
+ return (flags & FLAG_PROFILE) != 0;
+ }
+
+ @UnsupportedAppUsage
+ public boolean isManagedProfile() {
+ return UserManager.isUserTypeManagedProfile(userType);
+ }
+
+ public boolean isCloneProfile() {
+ return UserManager.isUserTypeCloneProfile(userType);
+ }
+
+ @UnsupportedAppUsage
+ public boolean isEnabled() {
+ return (flags & FLAG_DISABLED) != FLAG_DISABLED;
+ }
+
+ public boolean isQuietModeEnabled() {
+ return (flags & FLAG_QUIET_MODE) == FLAG_QUIET_MODE;
+ }
+
+ public boolean isEphemeral() {
+ return (flags & FLAG_EPHEMERAL) == FLAG_EPHEMERAL;
+ }
+
+ public boolean isInitialized() {
+ return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
+ }
+
+ public boolean isDemo() {
+ return UserManager.isUserTypeDemo(userType);
+ }
+
+ public boolean isFull() {
+ return (flags & FLAG_FULL) == FLAG_FULL;
+ }
+
+ /**
+ * Returns true if the user is a split system user.
+ * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
+ * the method always returns false.
+ */
+ public boolean isSystemOnly() {
+ return isSystemOnly(id);
+ }
+
+ /**
+ * Returns true if the given user is a split system user.
+ * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
+ * the method always returns false.
+ */
+ public static boolean isSystemOnly(int userId) {
+ return userId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser();
+ }
+
+ /**
+ * @return true if this user can be switched to.
+ **/
+ public boolean supportsSwitchTo() {
+ if (partial || !isEnabled()) {
+ // Don't support switching to disabled or partial users, which includes users with
+ // removal in progress.
+ return false;
+ }
+ if (preCreated) {
+ // Don't support switching to pre-created users until they become "real" users.
+ return false;
+ }
+ return !isProfile();
+ }
+
+ /**
+ * @return true if this user can be switched to by end user through UI.
+ */
+ public boolean supportsSwitchToByUser() {
+ // Hide the system user when it does not represent a human user.
+ boolean hideSystemUser = UserManager.isHeadlessSystemUserMode();
+ return (!hideSystemUser || id != UserHandle.USER_SYSTEM) && supportsSwitchTo();
+ }
+
+ // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType))
+ /* @hide */
+ public boolean canHaveProfile() {
+ if (isProfile() || isGuest() || isRestricted()) {
+ return false;
+ }
+ if (UserManager.isSplitSystemUser() || UserManager.isHeadlessSystemUserMode()) {
+ return id != UserHandle.USER_SYSTEM;
+ } else {
+ return id == UserHandle.USER_SYSTEM;
+ }
+ }
+
+ // TODO(b/142482943): Get rid of this (after removing it from all tests) if feasible.
+ /**
+ * @deprecated This is dangerous since it doesn't set the mandatory fields. Use a different
+ * constructor instead.
+ */
+ @Deprecated
+ @VisibleForTesting
+ public UserInfo() {
+ }
+
+ public UserInfo(UserInfo orig) {
+ name = orig.name;
+ iconPath = orig.iconPath;
+ id = orig.id;
+ flags = orig.flags;
+ userType = orig.userType;
+ serialNumber = orig.serialNumber;
+ creationTime = orig.creationTime;
+ lastLoggedInTime = orig.lastLoggedInTime;
+ lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
+ partial = orig.partial;
+ preCreated = orig.preCreated;
+ convertedFromPreCreated = orig.convertedFromPreCreated;
+ profileGroupId = orig.profileGroupId;
+ restrictedProfileParentId = orig.restrictedProfileParentId;
+ guestToRemove = orig.guestToRemove;
+ profileBadge = orig.profileBadge;
+ }
+
+ @UnsupportedAppUsage
+ public UserHandle getUserHandle() {
+ return UserHandle.of(id);
+ }
+
+ // TODO(b/142482943): Probably include mUserType here, which means updating TestDevice, etc.
+ @Override
+ public String toString() {
+ // NOTE: do not change this string, it's used by 'pm list users', which in turn is
+ // used and parsed by TestDevice. In other words, if you change it, you'd have to change
+ // TestDevice, TestDeviceTest, and possibly others....
+ return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+ }
+
+ /** @hide */
+ public String toFullString() {
+ return "UserInfo[id=" + id
+ + ", name=" + name
+ + ", type=" + userType
+ + ", flags=" + flagsToString(flags)
+ + (preCreated ? " (pre-created)" : "")
+ + (convertedFromPreCreated ? " (converted)" : "")
+ + (partial ? " (partial)" : "")
+ + "]";
+ }
+
+ /** @hide */
+ public static String flagsToString(int flags) {
+ return DebugUtils.flagsToString(UserInfo.class, "FLAG_", flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(id);
+ dest.writeString8(name);
+ dest.writeString8(iconPath);
+ dest.writeInt(flags);
+ dest.writeString8(userType);
+ dest.writeInt(serialNumber);
+ dest.writeLong(creationTime);
+ dest.writeLong(lastLoggedInTime);
+ dest.writeString8(lastLoggedInFingerprint);
+ dest.writeBoolean(partial);
+ dest.writeBoolean(preCreated);
+ dest.writeInt(profileGroupId);
+ dest.writeBoolean(guestToRemove);
+ dest.writeInt(restrictedProfileParentId);
+ dest.writeInt(profileBadge);
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Parcelable.Creator<UserInfo> CREATOR
+ = new Parcelable.Creator<UserInfo>() {
+ public UserInfo createFromParcel(Parcel source) {
+ return new UserInfo(source);
+ }
+ public UserInfo[] newArray(int size) {
+ return new UserInfo[size];
+ }
+ };
+
+ private UserInfo(Parcel source) {
+ id = source.readInt();
+ name = source.readString8();
+ iconPath = source.readString8();
+ flags = source.readInt();
+ userType = source.readString8();
+ serialNumber = source.readInt();
+ creationTime = source.readLong();
+ lastLoggedInTime = source.readLong();
+ lastLoggedInFingerprint = source.readString8();
+ partial = source.readBoolean();
+ preCreated = source.readBoolean();
+ profileGroupId = source.readInt();
+ guestToRemove = source.readBoolean();
+ restrictedProfileParentId = source.readInt();
+ profileBadge = source.readInt();
+ }
+}
diff --git a/android/content/pm/VerifierDeviceIdentity.java b/android/content/pm/VerifierDeviceIdentity.java
new file mode 100644
index 0000000..3ce0b65
--- /dev/null
+++ b/android/content/pm/VerifierDeviceIdentity.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2011 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.Nullable;
+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;
+
+/**
+ * An identity that uniquely identifies a particular device. In this
+ * implementation, the identity is represented as a 64-bit integer encoded to a
+ * 13-character string using RFC 4648's Base32 encoding without the trailing
+ * padding. This makes it easy for users to read and write the code without
+ * confusing 'I' (letter) with '1' (one) or 'O' (letter) with '0' (zero).
+ *
+ * @hide
+ */
+public class VerifierDeviceIdentity implements Parcelable {
+ /**
+ * Encoded size of a long (64-bit) into Base32. This format will end up
+ * looking like XXXX-XXXX-XXXX-X (length ignores hyphens) when applied with
+ * the GROUP_SIZE below.
+ */
+ private static final int LONG_SIZE = 13;
+
+ /**
+ * Size of groupings when outputting as strings. This helps people read it
+ * out and keep track of where they are.
+ */
+ private static final int GROUP_SIZE = 4;
+
+ private final long mIdentity;
+
+ private final String mIdentityString;
+
+ /**
+ * Create a verifier device identity from a long.
+ *
+ * @param identity device identity in a 64-bit integer.
+ * @throws
+ */
+ public VerifierDeviceIdentity(long identity) {
+ mIdentity = identity;
+ mIdentityString = encodeBase32(identity);
+ }
+
+ private VerifierDeviceIdentity(Parcel source) {
+ final long identity = source.readLong();
+
+ mIdentity = identity;
+ mIdentityString = encodeBase32(identity);
+ }
+
+ /**
+ * Generate a new device identity.
+ *
+ * @return random uniformly-distributed device identity
+ */
+ public static VerifierDeviceIdentity generate() {
+ final SecureRandom sr = new SecureRandom();
+ return generate(sr);
+ }
+
+ /**
+ * Generate a new device identity using a provided random number generator
+ * class. This is used for testing.
+ *
+ * @param rng random number generator to retrieve the next long from
+ * @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);
+ }
+
+ private static final char ENCODE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', '2', '3', '4', '5', '6', '7',
+ };
+
+ private static final char SEPARATOR = '-';
+
+ private static final String encodeBase32(long input) {
+ final char[] alphabet = ENCODE;
+
+ /*
+ * Make a character array with room for the separators between each
+ * group.
+ */
+ final char encoded[] = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
+
+ int index = encoded.length;
+ for (int i = 0; i < LONG_SIZE; i++) {
+ /*
+ * Make sure we don't put a separator at the beginning. Since we're
+ * building from the rear of the array, we use (LONG_SIZE %
+ * GROUP_SIZE) to make the odd-size group appear at the end instead
+ * of the beginning.
+ */
+ if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
+ encoded[--index] = SEPARATOR;
+ }
+
+ /*
+ * Extract 5 bits of data, then shift it out.
+ */
+ final int group = (int) (input & 0x1F);
+ input >>>= 5;
+
+ encoded[--index] = alphabet[group];
+ }
+
+ return String.valueOf(encoded);
+ }
+
+ // TODO move this out to its own class (android.util.Base32)
+ private static final long decodeBase32(byte[] input) throws IllegalArgumentException {
+ long output = 0L;
+ int numParsed = 0;
+
+ final int N = input.length;
+ for (int i = 0; i < N; i++) {
+ final int group = input[i];
+
+ /*
+ * This essentially does the reverse of the ENCODED alphabet above
+ * without a table. A..Z are 0..25 and 2..7 are 26..31.
+ */
+ final int value;
+ if ('A' <= group && group <= 'Z') {
+ value = group - 'A';
+ } else if ('2' <= group && group <= '7') {
+ value = group - ('2' - 26);
+ } else if (group == SEPARATOR) {
+ continue;
+ } else if ('a' <= group && group <= 'z') {
+ /* Lowercase letters should be the same as uppercase for Base32 */
+ value = group - 'a';
+ } else if (group == '0') {
+ /* Be nice to users that mistake O (letter) for 0 (zero) */
+ value = 'O' - 'A';
+ } else if (group == '1') {
+ /* Be nice to users that mistake I (letter) for 1 (one) */
+ value = 'I' - 'A';
+ } else {
+ throw new IllegalArgumentException("base base-32 character: " + group);
+ }
+
+ output = (output << 5) | value;
+ numParsed++;
+
+ if (numParsed == 1) {
+ if ((value & 0xF) != value) {
+ throw new IllegalArgumentException("illegal start character; will overflow");
+ }
+ } else if (numParsed > 13) {
+ throw new IllegalArgumentException("too long; should have 13 characters");
+ }
+ }
+
+ if (numParsed != 13) {
+ throw new IllegalArgumentException("too short; should have 13 characters");
+ }
+
+ return output;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) mIdentity;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof VerifierDeviceIdentity)) {
+ return false;
+ }
+
+ final VerifierDeviceIdentity o = (VerifierDeviceIdentity) other;
+ return mIdentity == o.mIdentity;
+ }
+
+ @Override
+ public String toString() {
+ return mIdentityString;
+ }
+
+ public static VerifierDeviceIdentity parse(String deviceIdentity)
+ throws IllegalArgumentException {
+ final byte[] input;
+ try {
+ input = deviceIdentity.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("bad base-32 characters in input");
+ }
+
+ return new VerifierDeviceIdentity(decodeBase32(input));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mIdentity);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<VerifierDeviceIdentity> CREATOR
+ = new Parcelable.Creator<VerifierDeviceIdentity>() {
+ public VerifierDeviceIdentity createFromParcel(Parcel source) {
+ return new VerifierDeviceIdentity(source);
+ }
+
+ public VerifierDeviceIdentity[] newArray(int size) {
+ return new VerifierDeviceIdentity[size];
+ }
+ };
+}
diff --git a/android/content/pm/VerifierInfo.java b/android/content/pm/VerifierInfo.java
new file mode 100644
index 0000000..3e69ff5
--- /dev/null
+++ b/android/content/pm/VerifierInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.PublicKey;
+
+/**
+ * Contains information about a package verifier as used by
+ * {@code PackageManagerService} during package verification.
+ *
+ * @hide
+ */
+public class VerifierInfo implements Parcelable {
+ /** Package name of the verifier. */
+ public final String packageName;
+
+ /** Signatures used to sign the package verifier's package. */
+ public final PublicKey publicKey;
+
+ /**
+ * Creates an object that represents a verifier info object.
+ *
+ * @param packageName the package name in Java-style. Must not be {@code
+ * null} or empty.
+ * @param publicKey the public key for the signer encoded in Base64. Must
+ * not be {@code null} or empty.
+ * @throws IllegalArgumentException if either argument is null or empty.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public VerifierInfo(String packageName, PublicKey publicKey) {
+ if (packageName == null || packageName.length() == 0) {
+ throw new IllegalArgumentException("packageName must not be null or empty");
+ } else if (publicKey == null) {
+ throw new IllegalArgumentException("publicKey must not be null");
+ }
+
+ this.packageName = packageName;
+ this.publicKey = publicKey;
+ }
+
+ private VerifierInfo(Parcel source) {
+ packageName = source.readString();
+ publicKey = (PublicKey) source.readSerializable();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(packageName);
+ dest.writeSerializable(publicKey);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<VerifierInfo> CREATOR
+ = new Parcelable.Creator<VerifierInfo>() {
+ public VerifierInfo createFromParcel(Parcel source) {
+ return new VerifierInfo(source);
+ }
+
+ public VerifierInfo[] newArray(int size) {
+ return new VerifierInfo[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/android/content/pm/VersionedPackage.java b/android/content/pm/VersionedPackage.java
new file mode 100644
index 0000000..fa50fca
--- /dev/null
+++ b/android/content/pm/VersionedPackage.java
@@ -0,0 +1,135 @@
+/*
+ * 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.pm;
+
+import android.annotation.IntRange;
+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;
+
+/**
+ * Encapsulates a package and its version code.
+ */
+public final class VersionedPackage implements Parcelable {
+ private final String mPackageName;
+ private final long mVersionCode;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntRange(from = PackageManager.VERSION_CODE_HIGHEST)
+ public @interface VersionCode{}
+
+ /**
+ * Creates a new instance. Use {@link PackageManager#VERSION_CODE_HIGHEST}
+ * to refer to the highest version code of this package.
+ * @param packageName The package name.
+ * @param versionCode The version code.
+ */
+ public VersionedPackage(@NonNull String packageName,
+ @VersionCode int versionCode) {
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ /**
+ * Creates a new instance. Use {@link PackageManager#VERSION_CODE_HIGHEST}
+ * to refer to the highest version code of this package.
+ * @param packageName The package name.
+ * @param versionCode The version code.
+ */
+ public VersionedPackage(@NonNull String packageName,
+ @VersionCode long versionCode) {
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ private VersionedPackage(Parcel parcel) {
+ mPackageName = parcel.readString8();
+ mVersionCode = parcel.readLong();
+ }
+
+ /**
+ * Gets the package name.
+ *
+ * @return The package name.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * @deprecated use {@link #getLongVersionCode()} instead.
+ */
+ @Deprecated
+ public @VersionCode int getVersionCode() {
+ return (int) (mVersionCode & 0x7fffffff);
+ }
+
+ /**
+ * Gets the version code.
+ *
+ * @return The version code.
+ */
+ public @VersionCode long getLongVersionCode() {
+ return mVersionCode;
+ }
+
+ @Override
+ public String toString() {
+ return "VersionedPackage[" + mPackageName + "/" + mVersionCode + "]";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ return o instanceof VersionedPackage
+ && ((VersionedPackage) o).mPackageName.equals(mPackageName)
+ && ((VersionedPackage) o).mVersionCode == mVersionCode;
+ }
+
+ @Override
+ public int hashCode() {
+ // Roll our own hash function without using Objects#hash which incurs the overhead
+ // of autoboxing.
+ return 31 * mPackageName.hashCode() + Long.hashCode(mVersionCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString8(mPackageName);
+ parcel.writeLong(mVersionCode);
+ }
+
+ public static final @android.annotation.NonNull Creator<VersionedPackage> CREATOR = new Creator<VersionedPackage>() {
+ @Override
+ public VersionedPackage createFromParcel(Parcel source) {
+ return new VersionedPackage(source);
+ }
+
+ @Override
+ public VersionedPackage[] newArray(int size) {
+ return new VersionedPackage[size];
+ }
+ };
+}
diff --git a/android/content/pm/XmlSerializerAndParser.java b/android/content/pm/XmlSerializerAndParser.java
new file mode 100644
index 0000000..51cd6ca
--- /dev/null
+++ b/android/content/pm/XmlSerializerAndParser.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2009 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.compat.annotation.UnsupportedAppUsage;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/** @hide */
+public interface XmlSerializerAndParser<T> {
+ void writeAsXml(T item, TypedXmlSerializer out) throws IOException;
+ T createFromXml(TypedXmlPullParser parser) throws IOException, XmlPullParserException;
+
+ @UnsupportedAppUsage
+ default void writeAsXml(T item, XmlSerializer out) throws IOException {
+ writeAsXml(item, XmlUtils.makeTyped(out));
+ }
+
+ @UnsupportedAppUsage
+ default T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+ return createFromXml(XmlUtils.makeTyped(parser));
+ }
+}
diff --git a/android/content/pm/dex/ArtManager.java b/android/content/pm/dex/ArtManager.java
new file mode 100644
index 0000000..009ebb9
--- /dev/null
+++ b/android/content/pm/dex/ArtManager.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright 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.pm.dex;
+
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.Manifest.permission.READ_RUNTIME_PROFILES;
+
+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.content.Context;
+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.
+ *
+ * @hide
+ */
+@SystemApi
+public class ArtManager {
+ private static final String TAG = "ArtManager";
+
+ /** The snapshot failed because the package was not found. */
+ public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0;
+ /** The snapshot failed because the package code path does not exist. */
+ public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1;
+ /** 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 final Context mContext;
+ private final IArtManager mArtManager;
+
+ /**
+ * @hide
+ */
+ public ArtManager(@NonNull Context context, @NonNull IArtManager manager) {
+ mContext = context;
+ mArtManager = manager;
+ }
+
+ /**
+ * 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 the {@code executor} using the given {@code callback}.
+ * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
+ *
+ * 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 executor the executor which should be used to post the result
+ */
+ @RequiresPermission(allOf = { READ_RUNTIME_PROFILES, PACKAGE_USAGE_STATS })
+ 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, executor);
+ try {
+ mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * 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(allOf = { READ_RUNTIME_PROFILES, PACKAGE_USAGE_STATS })
+ public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
+ try {
+ return mArtManager.isRuntimeProfilingEnabled(profileType, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Callback used for retrieving runtime profiles.
+ */
+ public abstract static class SnapshotRuntimeProfileCallback {
+ /**
+ * Called when the profile snapshot finished with success.
+ *
+ * @param profileReadFd the file descriptor that can be used to read the profile. Note that
+ * the file might be empty (which is valid profile).
+ */
+ public abstract void onSuccess(ParcelFileDescriptor profileReadFd);
+
+ /**
+ * Called when the profile snapshot finished with an error.
+ *
+ * @param errCode the error code {@see SNAPSHOT_FAILED_PACKAGE_NOT_FOUND,
+ * SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND, SNAPSHOT_FAILED_INTERNAL_ERROR}.
+ */
+ public abstract void onError(int errCode);
+ }
+
+ private static class SnapshotRuntimeProfileCallbackDelegate
+ extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub {
+ private final ArtManager.SnapshotRuntimeProfileCallback mCallback;
+ private final Executor mExecutor;
+
+ private SnapshotRuntimeProfileCallbackDelegate(
+ ArtManager.SnapshotRuntimeProfileCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onSuccess(final ParcelFileDescriptor profileReadFd) {
+ mExecutor.execute(() -> mCallback.onSuccess(profileReadFd));
+ }
+
+ @Override
+ public void onError(int errCode) {
+ mExecutor.execute(() -> mCallback.onError(errCode));
+ }
+ }
+
+ /**
+ * Return the profile name for the given split. If {@code splitName} is null the
+ * method returns the profile name for the base apk.
+ *
+ * @hide
+ */
+ 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 path to the current profile corresponding to given package and split.
+ *
+ * @hide
+ */
+ public static String getReferenceProfilePath(String packageName, int userId, String splitName) {
+ File profileDir = Environment.getDataRefProfilesDePackageDirectory(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 0000000..23fef29
--- /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, String activityName);
+}
diff --git a/android/content/pm/dex/DexMetadataHelper.java b/android/content/pm/dex/DexMetadataHelper.java
new file mode 100644
index 0000000..8f9a0d7
--- /dev/null
+++ b/android/content/pm/dex/DexMetadataHelper.java
@@ -0,0 +1,317 @@
+/**
+ * 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 static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
+import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION;
+
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.JsonReader;
+import android.util.Log;
+import android.util.jar.StrictJarFile;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.security.VerityUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+
+/**
+ * Helper class used to compute and validate the location of dex metadata files.
+ *
+ * @hide
+ */
+public class DexMetadataHelper {
+ public static final String TAG = "DexMetadataHelper";
+ /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */
+ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */
+ private static final String PROPERTY_DM_JSON_MANIFEST_REQUIRED =
+ "pm.dexopt.dm.require_manifest";
+ /** $> adb shell 'setprop pm.dexopt.dm.require_fsverity true' */
+ private static final String PROPERTY_DM_FSVERITY_REQUIRED = "pm.dexopt.dm.require_fsverity";
+
+ private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+
+ private DexMetadataHelper() {}
+
+ /** Return true if the given file is a dex metadata file. */
+ public static boolean isDexMetadataFile(File file) {
+ return isDexMetadataPath(file.getName());
+ }
+
+ /** Return true if the given path is a dex metadata path. */
+ private static boolean isDexMetadataPath(String path) {
+ return path.endsWith(DEX_METADATA_FILE_EXTENSION);
+ }
+
+ /**
+ * Returns whether fs-verity is required to install a dex metadata
+ */
+ public static boolean isFsVerityRequired() {
+ return VerityUtils.isFsVeritySupported()
+ && SystemProperties.getBoolean(PROPERTY_DM_FSVERITY_REQUIRED, false);
+ }
+
+ /**
+ * Return the size (in bytes) of all dex metadata files associated with the given package.
+ */
+ public static long getPackageDexMetadataSize(PackageLite pkg) {
+ long sizeBytes = 0;
+ Collection<String> dexMetadataList = DexMetadataHelper.getPackageDexMetadata(pkg).values();
+ for (String dexMetadata : dexMetadataList) {
+ sizeBytes += new File(dexMetadata).length();
+ }
+ return sizeBytes;
+ }
+
+ /**
+ * Search for the dex metadata file associated with the given target file.
+ * If it exists, the method returns the dex metadata file; otherwise it returns null.
+ *
+ * Note that this performs a loose matching suitable to be used in the InstallerSession logic.
+ * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
+ * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
+ */
+ public static File findDexMetadataForFile(File targetFile) {
+ String dexMetadataPath = buildDexMetadataPathForFile(targetFile);
+ File dexMetadataFile = new File(dexMetadataPath);
+ return dexMetadataFile.exists() ? dexMetadataFile : null;
+ }
+
+ /**
+ * Return the dex metadata files for the given package as a map
+ * [code path -> dex metadata path].
+ *
+ * NOTE: involves I/O checks.
+ */
+ private static Map<String, String> getPackageDexMetadata(PackageLite pkg) {
+ return buildPackageApkToDexMetadataMap(pkg.getAllApkPaths());
+ }
+
+ /**
+ * Look up the dex metadata files for the given code paths building the map
+ * [code path -> dex metadata].
+ *
+ * For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists.
+ * If it does it adds the pair to the returned map.
+ *
+ * Note that this method will do a loose
+ * matching based on the extension ('foo.dm' will match 'foo.apk' or 'foo').
+ *
+ * This should only be used for code paths extracted from a package structure after the naming
+ * was enforced in the installer.
+ */
+ public static Map<String, String> buildPackageApkToDexMetadataMap(
+ List<String> codePaths) {
+ ArrayMap<String, String> result = new ArrayMap<>();
+ for (int i = codePaths.size() - 1; i >= 0; i--) {
+ String codePath = codePaths.get(i);
+ String dexMetadataPath = buildDexMetadataPathForFile(new File(codePath));
+
+ if (Files.exists(Paths.get(dexMetadataPath))) {
+ result.put(codePath, dexMetadataPath);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Return the dex metadata path associated with the given code path.
+ * (replaces '.apk' extension with '.dm')
+ *
+ * @throws IllegalArgumentException if the code path is not an .apk.
+ */
+ public static String buildDexMetadataPathForApk(String codePath) {
+ if (!ApkLiteParseUtils.isApkPath(codePath)) {
+ throw new IllegalStateException(
+ "Corrupted package. Code path is not an apk " + codePath);
+ }
+ return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
+ + DEX_METADATA_FILE_EXTENSION;
+ }
+
+ /**
+ * Return the dex metadata path corresponding to the given {@code targetFile} using a loose
+ * matching.
+ * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
+ * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
+ */
+ private static String buildDexMetadataPathForFile(File targetFile) {
+ return ApkLiteParseUtils.isApkFile(targetFile)
+ ? buildDexMetadataPathForApk(targetFile.getPath())
+ : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION;
+ }
+
+ /**
+ * Validate that the given file is a dex metadata archive.
+ * This is just a validation that the file is a zip archive that contains a manifest.json
+ * with the package name and version code.
+ *
+ * @throws PackageParserException if the file is not a .dm file.
+ */
+ public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode)
+ throws PackageParserException {
+ validateDexMetadataFile(dmaPath, packageName, versionCode,
+ SystemProperties.getBoolean(PROPERTY_DM_JSON_MANIFEST_REQUIRED, false));
+ }
+
+ @VisibleForTesting
+ public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode,
+ boolean requireManifest) throws PackageParserException {
+ StrictJarFile jarFile = null;
+
+ if (DEBUG) {
+ Log.v(TAG, "validateDexMetadataFile: " + dmaPath + ", " + packageName +
+ ", " + versionCode);
+ }
+
+ try {
+ jarFile = new StrictJarFile(dmaPath, false, false);
+ validateDexMetadataManifest(dmaPath, jarFile, packageName, versionCode,
+ requireManifest);
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "Error opening " + dmaPath, e);
+ } finally {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ /** Ensure that packageName and versionCode match the manifest.json in the .dm file */
+ private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile,
+ String packageName, long versionCode, boolean requireManifest)
+ throws IOException, PackageParserException {
+ if (!requireManifest) {
+ if (DEBUG) {
+ Log.v(TAG, "validateDexMetadataManifest: " + dmaPath
+ + " manifest.json check skipped");
+ }
+ return;
+ }
+
+ ZipEntry zipEntry = jarFile.findEntry("manifest.json");
+ if (zipEntry == null) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "Missing manifest.json in " + dmaPath);
+ }
+ InputStream inputStream = jarFile.getInputStream(zipEntry);
+
+ JsonReader reader;
+ try {
+ reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "Error opening manifest.json in " + dmaPath, e);
+ }
+ String jsonPackageName = null;
+ long jsonVersionCode = -1;
+
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (name.equals("packageName")) {
+ jsonPackageName = reader.nextString();
+ } else if (name.equals("versionCode")) {
+ jsonVersionCode = reader.nextLong();
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ if (jsonPackageName == null || jsonVersionCode == -1) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "manifest.json in " + dmaPath
+ + " is missing 'packageName' and/or 'versionCode'");
+ }
+
+ if (!jsonPackageName.equals(packageName)) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName
+ + ", expected: " + packageName);
+ }
+
+ if (versionCode != jsonVersionCode) {
+ throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+ "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode
+ + ", expected: " + versionCode);
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName +
+ ", " + versionCode + ": successful");
+ }
+ }
+
+ /**
+ * Validates that all dex metadata paths in the given list have a matching apk.
+ * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
+ * If that's not the case it throws {@code IllegalStateException}.
+ *
+ * This is used to perform a basic check during adb install commands.
+ * (The installer does not support stand alone .dm files)
+ */
+ public static void validateDexPaths(String[] paths) {
+ ArrayList<String> apks = new ArrayList<>();
+ for (int i = 0; i < paths.length; i++) {
+ if (ApkLiteParseUtils.isApkPath(paths[i])) {
+ apks.add(paths[i]);
+ }
+ }
+ ArrayList<String> unmatchedDmFiles = new ArrayList<>();
+ for (int i = 0; i < paths.length; i++) {
+ String dmPath = paths[i];
+ if (isDexMetadataPath(dmPath)) {
+ boolean valid = false;
+ for (int j = apks.size() - 1; j >= 0; j--) {
+ if (dmPath.equals(buildDexMetadataPathForFile(new File(apks.get(j))))) {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid) {
+ unmatchedDmFiles.add(dmPath);
+ }
+ }
+ }
+ if (!unmatchedDmFiles.isEmpty()) {
+ throw new IllegalStateException("Unmatched .dm files: " + unmatchedDmFiles);
+ }
+ }
+
+}
diff --git a/android/content/pm/dex/PackageOptimizationInfo.java b/android/content/pm/dex/PackageOptimizationInfo.java
new file mode 100644
index 0000000..7e7d29e
--- /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/overlay/OverlayPaths.java b/android/content/pm/overlay/OverlayPaths.java
new file mode 100644
index 0000000..a4db733
--- /dev/null
+++ b/android/content/pm/overlay/OverlayPaths.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 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.overlay;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
+ genEqualsHashCode = true, genToString = true)
+public class OverlayPaths {
+ /**
+ * Represents {@link android.content.pm.ApplicationInfo#resourceDirs}.
+ * Only contains paths to APKs of overlays that can have their idmap resolved from their base
+ * APK path. Currently all overlay APKs can have their idmap path resolved from their idmap
+ * path.
+ */
+ @NonNull
+ private final List<String> mResourceDirs = new ArrayList<>();
+
+ /**
+ * Represents {@link android.content.pm.ApplicationInfo#overlayPaths}.
+ * Contains the contents of {@link #getResourceDirs()} and along with paths for overlays
+ * that are not APKs.
+ */
+ @NonNull
+ private final List<String> mOverlayPaths = new ArrayList<>();
+
+ public static class Builder {
+ final OverlayPaths mPaths = new OverlayPaths();
+
+ /**
+ * Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
+ */
+ public Builder addNonApkPath(@NonNull String idmapPath) {
+ mPaths.mOverlayPaths.add(idmapPath);
+ return this;
+ }
+
+ /**
+ * Adds a overlay APK path to the contents of {@link OverlayPaths#getResourceDirs()} and
+ * {@link OverlayPaths#getOverlayPaths()}.
+ */
+ public Builder addApkPath(@NonNull String overlayPath) {
+ addUniquePath(mPaths.mResourceDirs, overlayPath);
+ addUniquePath(mPaths.mOverlayPaths, overlayPath);
+ return this;
+ }
+
+ public Builder addAll(@Nullable OverlayPaths other) {
+ if (other != null) {
+ for (final String path : other.getResourceDirs()) {
+ addUniquePath(mPaths.mResourceDirs, path);
+ }
+ for (final String path : other.getOverlayPaths()) {
+ addUniquePath(mPaths.mOverlayPaths, path);
+ }
+ }
+ return this;
+ }
+
+ public OverlayPaths build() {
+ return mPaths;
+ }
+
+ private static void addUniquePath(@NonNull List<String> paths, @NonNull String path) {
+ if (!paths.contains(path)) {
+ paths.add(path);
+ }
+ }
+ }
+
+ /**
+ * Returns whether {@link #getOverlayPaths()} and {@link #getOverlayPaths} are empty.
+ */
+ public boolean isEmpty() {
+ return mResourceDirs.isEmpty() && mOverlayPaths.isEmpty();
+ }
+
+ private OverlayPaths() {
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/overlay/OverlayPaths.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Represents {@link android.content.pm.ApplicationInfo#resourceDirs}.
+ * Only contains paths to APKs of overlays that can have their idmap resolved from their base
+ * APK path. Currently all overlay APKs can have their idmap path resolved from their idmap
+ * path.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getResourceDirs() {
+ return mResourceDirs;
+ }
+
+ /**
+ * Represents {@link android.content.pm.ApplicationInfo#overlayPaths}.
+ * Contains the contents of {@link #getResourceDirs()} and along with paths for overlays
+ * that are not APKs.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getOverlayPaths() {
+ return mOverlayPaths;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "OverlayPaths { " +
+ "resourceDirs = " + mResourceDirs + ", " +
+ "overlayPaths = " + mOverlayPaths +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(OverlayPaths other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ OverlayPaths that = (OverlayPaths) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mResourceDirs, that.mResourceDirs)
+ && Objects.equals(mOverlayPaths, that.mOverlayPaths);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mResourceDirs);
+ _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1612307813586L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/overlay/OverlayPaths.java",
+ inputSignatures = "private final @android.annotation.NonNull java.util.List<java.lang.String> mResourceDirs\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mOverlayPaths\npublic boolean isEmpty()\nclass OverlayPaths extends java.lang.Object implements []\nfinal android.content.pm.overlay.OverlayPaths mPaths\npublic android.content.pm.overlay.OverlayPaths.Builder addNonApkPath(java.lang.String)\npublic android.content.pm.overlay.OverlayPaths.Builder addApkPath(java.lang.String)\npublic android.content.pm.overlay.OverlayPaths.Builder addAll(android.content.pm.overlay.OverlayPaths)\npublic android.content.pm.overlay.OverlayPaths build()\nprivate static void addUniquePath(java.util.List<java.lang.String>,java.lang.String)\nclass Builder extends java.lang.Object implements []\[email protected](genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/parsing/ApkLite.java b/android/content/pm/parsing/ApkLite.java
new file mode 100644
index 0000000..d8ec512
--- /dev/null
+++ b/android/content/pm/parsing/ApkLite.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2021 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.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser.SigningDetails;
+import android.content.pm.VerifierInfo;
+
+import com.android.internal.util.DataClass;
+
+import java.util.List;
+
+/**
+ * Lightweight parsed details about a single APK file.
+ *
+ * @hide
+ */
+@DataClass(genConstructor = false, genConstDefs = false)
+public class ApkLite {
+ /** Name of the package as used to identify it in the system */
+ private final @NonNull String mPackageName;
+ /** Path where this APK file was found on disk */
+ private final @NonNull String mPath;
+ /** Split name of this APK */
+ private final @Nullable String mSplitName;
+ /** Dependencies of the split APK */
+ /** Name of the split APK that this APK depends on */
+ private final @Nullable String mUsesSplitName;
+ /** Name of the split APK that this APK is a configuration for */
+ private final @Nullable String mConfigForSplit;
+
+ /** Major version number of this package */
+ private final int mVersionCodeMajor;
+ /** Minor version number of this package */
+ private final int mVersionCode;
+ /** Revision code of this APK */
+ private final int mRevisionCode;
+ /**
+ * Indicate the install location of this package
+ *
+ * @see {@link PackageInfo#INSTALL_LOCATION_AUTO}
+ * @see {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}
+ * @see {@link PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL}
+ */
+ private final int mInstallLocation;
+ /** Indicate the minimum SDK version number that the app requires */
+ private final int mMinSdkVersion;
+ /** Indicate the SDK version number that the application is targeting */
+ private final int mTargetSdkVersion;
+ /** Information about a package verifiers as used during package verification */
+ private final @NonNull VerifierInfo[] mVerifiers;
+ /** Signing-related data of an application package */
+ private final @NonNull SigningDetails mSigningDetails;
+
+ /** Indicate whether this APK is a 'feature' split */
+ private final boolean mFeatureSplit;
+ /** Indicate whether each split should be load into their own Context objects */
+ private final boolean mIsolatedSplits;
+ /**
+ * Indicate whether this package requires at least one split (either feature or resource)
+ * to be present in order to function
+ */
+ private final boolean mSplitRequired;
+ /** Indicate whether this app is coreApp */
+ private final boolean mCoreApp;
+ /** Indicate whether this app can be debugged */
+ private final boolean mDebuggable;
+ /** Indicate whether this app is profileable by Shell */
+ private final boolean mProfileableByShell;
+ /** Indicate whether this app needs to be loaded into other applications' processes */
+ private final boolean mMultiArch;
+ /** Indicate whether the 32 bit version of the ABI should be used */
+ private final boolean mUse32bitAbi;
+ /** Indicate whether installer extracts native libraries */
+ private final boolean mExtractNativeLibs;
+ /**
+ * Indicate whether this package wants to run the dex within its APK but not extracted
+ * or locally compiled variants.
+ */
+ private final boolean mUseEmbeddedDex;
+
+ /** Name of the overlay-able set of elements package */
+ private final @Nullable String mTargetPackageName;
+ /** Indicate whether the overlay is static */
+ private final boolean mOverlayIsStatic;
+ /** Indicate the priority of this overlay package */
+ private final int mOverlayPriority;
+
+ /**
+ * Indicate the policy to deal with user data when rollback is committed
+ *
+ * @see {@link android.content.pm.PackageManager.RollbackDataPolicy#RESTORE}
+ * @see {@link android.content.pm.PackageManager.RollbackDataPolicy#WIPE}
+ * @see {@link android.content.pm.PackageManager.RollbackDataPolicy#RETAIN}
+ */
+ private final int mRollbackDataPolicy;
+
+ public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
+ String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
+ int versionCodeMajor, int revisionCode, int installLocation,
+ List<VerifierInfo> verifiers, SigningDetails signingDetails, boolean coreApp,
+ boolean debuggable, boolean profileableByShell, boolean multiArch, boolean use32bitAbi,
+ boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits,
+ String targetPackageName, boolean overlayIsStatic, int overlayPriority,
+ int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy) {
+ mPath = path;
+ mPackageName = packageName;
+ mSplitName = splitName;
+ mFeatureSplit = isFeatureSplit;
+ mConfigForSplit = configForSplit;
+ mUsesSplitName = usesSplitName;
+ mSplitRequired = isSplitRequired;
+ mVersionCode = versionCode;
+ mVersionCodeMajor = versionCodeMajor;
+ mRevisionCode = revisionCode;
+ mInstallLocation = installLocation;
+ mVerifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
+ mSigningDetails = signingDetails;
+ mCoreApp = coreApp;
+ mDebuggable = debuggable;
+ mProfileableByShell = profileableByShell;
+ mMultiArch = multiArch;
+ mUse32bitAbi = use32bitAbi;
+ mUseEmbeddedDex = useEmbeddedDex;
+ mExtractNativeLibs = extractNativeLibs;
+ mIsolatedSplits = isolatedSplits;
+ mTargetPackageName = targetPackageName;
+ mOverlayIsStatic = overlayIsStatic;
+ mOverlayPriority = overlayPriority;
+ mMinSdkVersion = minSdkVersion;
+ mTargetSdkVersion = targetSdkVersion;
+ mRollbackDataPolicy = rollbackDataPolicy;
+ }
+
+ /**
+ * Return {@link #mVersionCode} and {@link #mVersionCodeMajor} combined together as a
+ * single long value. The {@link #mVersionCodeMajor} is placed in the upper 32 bits.
+ */
+ public long getLongVersionCode() {
+ return PackageInfo.composeLongVersionCode(mVersionCodeMajor, mVersionCode);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/ApkLite.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Name of the package as used to identify it in the system
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Path where this APK file was found on disk
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPath() {
+ return mPath;
+ }
+
+ /**
+ * Split name of this APK
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getSplitName() {
+ return mSplitName;
+ }
+
+ /**
+ * Name of the split APK that this APK depends on
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getUsesSplitName() {
+ return mUsesSplitName;
+ }
+
+ /**
+ * Name of the split APK that this APK is a configuration for
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getConfigForSplit() {
+ return mConfigForSplit;
+ }
+
+ /**
+ * Major version number of this package
+ */
+ @DataClass.Generated.Member
+ public int getVersionCodeMajor() {
+ return mVersionCodeMajor;
+ }
+
+ /**
+ * Minor version number of this package
+ */
+ @DataClass.Generated.Member
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ /**
+ * Revision code of this APK
+ */
+ @DataClass.Generated.Member
+ public int getRevisionCode() {
+ return mRevisionCode;
+ }
+
+ /**
+ * Indicate the install location of this package
+ *
+ * @see {@link PackageInfo#INSTALL_LOCATION_AUTO}
+ * @see {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}
+ * @see {@link PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL}
+ */
+ @DataClass.Generated.Member
+ public int getInstallLocation() {
+ return mInstallLocation;
+ }
+
+ /**
+ * Indicate the minimum SDK version number that the app requires
+ */
+ @DataClass.Generated.Member
+ public int getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ /**
+ * Indicate the SDK version number that the application is targeting
+ */
+ @DataClass.Generated.Member
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ /**
+ * Information about a package verifiers as used during package verification
+ */
+ @DataClass.Generated.Member
+ public @NonNull VerifierInfo[] getVerifiers() {
+ return mVerifiers;
+ }
+
+ /**
+ * Signing-related data of an application package
+ */
+ @DataClass.Generated.Member
+ public @NonNull SigningDetails getSigningDetails() {
+ return mSigningDetails;
+ }
+
+ /**
+ * Indicate whether this APK is a 'feature' split
+ */
+ @DataClass.Generated.Member
+ public boolean isFeatureSplit() {
+ return mFeatureSplit;
+ }
+
+ /**
+ * Indicate whether each split should be load into their own Context objects
+ */
+ @DataClass.Generated.Member
+ public boolean isIsolatedSplits() {
+ return mIsolatedSplits;
+ }
+
+ /**
+ * Indicate whether this package requires at least one split (either feature or resource)
+ * to be present in order to function
+ */
+ @DataClass.Generated.Member
+ public boolean isSplitRequired() {
+ return mSplitRequired;
+ }
+
+ /**
+ * Indicate whether this app is coreApp
+ */
+ @DataClass.Generated.Member
+ public boolean isCoreApp() {
+ return mCoreApp;
+ }
+
+ /**
+ * Indicate whether this app can be debugged
+ */
+ @DataClass.Generated.Member
+ public boolean isDebuggable() {
+ return mDebuggable;
+ }
+
+ /**
+ * Indicate whether this app is profileable by Shell
+ */
+ @DataClass.Generated.Member
+ public boolean isProfileableByShell() {
+ return mProfileableByShell;
+ }
+
+ /**
+ * Indicate whether this app needs to be loaded into other applications' processes
+ */
+ @DataClass.Generated.Member
+ public boolean isMultiArch() {
+ return mMultiArch;
+ }
+
+ /**
+ * Indicate whether the 32 bit version of the ABI should be used
+ */
+ @DataClass.Generated.Member
+ public boolean isUse32bitAbi() {
+ return mUse32bitAbi;
+ }
+
+ /**
+ * Indicate whether installer extracts native libraries
+ */
+ @DataClass.Generated.Member
+ public boolean isExtractNativeLibs() {
+ return mExtractNativeLibs;
+ }
+
+ /**
+ * Indicate whether this package wants to run the dex within its APK but not extracted
+ * or locally compiled variants.
+ */
+ @DataClass.Generated.Member
+ public boolean isUseEmbeddedDex() {
+ return mUseEmbeddedDex;
+ }
+
+ /**
+ * Name of the overlay-able set of elements package
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getTargetPackageName() {
+ return mTargetPackageName;
+ }
+
+ /**
+ * Indicate whether the overlay is static
+ */
+ @DataClass.Generated.Member
+ public boolean isOverlayIsStatic() {
+ return mOverlayIsStatic;
+ }
+
+ /**
+ * Indicate the priority of this overlay package
+ */
+ @DataClass.Generated.Member
+ public int getOverlayPriority() {
+ return mOverlayPriority;
+ }
+
+ /**
+ * Indicate the policy to deal with user data when rollback is committed
+ *
+ * @see {@link android.content.pm.PackageManager.RollbackDataPolicy#RESTORE}
+ * @see {@link android.content.pm.PackageManager.RollbackDataPolicy#WIPE}
+ * @see {@link android.content.pm.PackageManager.RollbackDataPolicy#RETAIN}
+ */
+ @DataClass.Generated.Member
+ public int getRollbackDataPolicy() {
+ return mRollbackDataPolicy;
+ }
+
+ @DataClass.Generated(
+ time = 1610596637723L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.PackageParser.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final int mRollbackDataPolicy\npublic long getLongVersionCode()\nclass ApkLite extends java.lang.Object implements []\[email protected](genConstructor=false, genConstDefs=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/parsing/ApkLiteParseUtils.java b/android/content/pm/parsing/ApkLiteParseUtils.java
new file mode 100644
index 0000000..154d923
--- /dev/null
+++ b/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2019 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.parsing;
+
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+import static android.content.pm.parsing.ParsingPackageUtils.validateName;
+import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+import static android.content.pm.parsing.ParsingUtils.DEFAULT_MIN_SDK_VERSION;
+import static android.content.pm.parsing.ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.VerifierInfo;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.ApkAssets;
+import android.content.res.XmlResourceParser;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public class ApkLiteParseUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ private static final int PARSE_DEFAULT_INSTALL_LOCATION =
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+
+ private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
+
+ public static final String APK_FILE_EXTENSION = ".apk";
+
+ /**
+ * Parse only lightweight details about the package at the given location.
+ * Automatically detects if the package is a monolithic style (single APK
+ * file) or cluster style (directory of APKs).
+ * <p>
+ * This performs validity checking on cluster style packages, such as
+ * requiring identical package name and version codes, a single base APK,
+ * and unique split names.
+ *
+ * @see PackageParser#parsePackage(File, int)
+ */
+ public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
+ File packageFile, int flags) {
+ if (packageFile.isDirectory()) {
+ return parseClusterPackageLite(input, packageFile, flags);
+ } else {
+ return parseMonolithicPackageLite(input, packageFile, flags);
+ }
+ }
+
+ /**
+ * Parse lightweight details about a single APK files.
+ */
+ public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
+ File packageFile, int flags) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ try {
+ final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ final ApkLite baseApk = result.getResult();
+ final String packagePath = packageFile.getAbsolutePath();
+ return input.success(
+ new PackageLite(packagePath, baseApk.getPath(), baseApk, null,
+ null, null, null, null, null, baseApk.getTargetSdkVersion()));
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ /**
+ * Parse lightweight details about a directory of APKs.
+ */
+ public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
+ File packageDir, int flags) {
+ final File[] files = packageDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+ "No packages found in split");
+ }
+ // Apk directory is directly nested under the current directory
+ if (files.length == 1 && files[0].isDirectory()) {
+ return parseClusterPackageLite(input, files[0], flags);
+ }
+
+ String packageName = null;
+ int versionCode = 0;
+
+ final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ try {
+ for (File file : files) {
+ if (isApkFile(file)) {
+ final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ final ApkLite lite = result.getResult();
+ // Assert that all package names and version codes are
+ // consistent with the first one we encounter.
+ if (packageName == null) {
+ packageName = lite.getPackageName();
+ versionCode = lite.getVersionCode();
+ } else {
+ if (!packageName.equals(lite.getPackageName())) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent package " + lite.getPackageName() + " in " + file
+ + "; expected " + packageName);
+ }
+ if (versionCode != lite.getVersionCode()) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent version " + lite.getVersionCode() + " in " + file
+ + "; expected " + versionCode);
+ }
+ }
+
+ // Assert that each split is defined only oncuses-static-libe
+ if (apks.put(lite.getSplitName(), lite) != null) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Split name " + lite.getSplitName()
+ + " defined more than once; most recent was " + file);
+ }
+ }
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ final ApkLite baseApk = apks.remove(null);
+ return composePackageLiteFromApks(input, packageDir, baseApk, apks);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about the package by given location,
+ * base APK, and split APKs.
+ *
+ * @param packageDir Path to the package
+ * @param baseApk Parsed base APK
+ * @param splitApks Parsed split APKs
+ * @return PackageLite
+ */
+ public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input,
+ File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) {
+ return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about the package by given location,
+ * base APK, and split APKs.
+ *
+ * @param packageDir Path to the package
+ * @param baseApk Parsed base APK
+ * @param splitApks Parsed split APKs
+ * @param apkRenamed Indicate whether the APKs are renamed after parsed.
+ * @return PackageLite
+ */
+ public static ParseResult<PackageLite> composePackageLiteFromApks(
+ ParseInput input, File packageDir, ApkLite baseApk,
+ ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
+ if (baseApk == null) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Missing base APK in " + packageDir);
+ }
+ // Always apply deterministic ordering based on splitName
+ final int size = ArrayUtils.size(splitApks);
+
+ String[] splitNames = null;
+ boolean[] isFeatureSplits = null;
+ String[] usesSplitNames = null;
+ String[] configForSplits = null;
+ String[] splitCodePaths = null;
+ int[] splitRevisionCodes = null;
+ if (size > 0) {
+ splitNames = new String[size];
+ isFeatureSplits = new boolean[size];
+ usesSplitNames = new String[size];
+ configForSplits = new String[size];
+ splitCodePaths = new String[size];
+ splitRevisionCodes = new int[size];
+
+ splitNames = splitApks.keySet().toArray(splitNames);
+ Arrays.sort(splitNames, sSplitNameComparator);
+
+ for (int i = 0; i < size; i++) {
+ final ApkLite apk = splitApks.get(splitNames[i]);
+ usesSplitNames[i] = apk.getUsesSplitName();
+ isFeatureSplits[i] = apk.isFeatureSplit();
+ configForSplits[i] = apk.getConfigForSplit();
+ splitCodePaths[i] = apkRenamed ? new File(packageDir,
+ splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
+ splitRevisionCodes[i] = apk.getRevisionCode();
+ }
+ }
+
+ final String codePath = packageDir.getAbsolutePath();
+ final String baseCodePath = apkRenamed ? new File(packageDir,
+ splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
+ return input.success(
+ new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
+ usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
+ baseApk.getTargetSdkVersion()));
+ }
+
+ /**
+ * Utility method that retrieves canonical file name by given split name from parsed APK.
+ *
+ * @param apk Parsed APK
+ * @return The canonical file name
+ */
+ public static String splitNameToFileName(@NonNull ApkLite apk) {
+ Objects.requireNonNull(apk);
+ final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
+ return fileName + APK_FILE_EXTENSION;
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param apkFile path to a single APK
+ * @param flags optional parse flags, such as
+ * {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) {
+ return parseApkLiteInner(input, apkFile, null, null, flags);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param fd already open file descriptor of an apk file
+ * @param debugPathName arbitrary text name for this file, for debug output
+ * @param flags optional parse flags, such as
+ * {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ParseResult<ApkLite> parseApkLite(ParseInput input,
+ FileDescriptor fd, String debugPathName, int flags) {
+ return parseApkLiteInner(input, null, fd, debugPathName, flags);
+ }
+
+ private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
+ File apkFile, FileDescriptor fd, String debugPathName, int flags) {
+ final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
+
+ XmlResourceParser parser = null;
+ ApkAssets apkAssets = null;
+ try {
+ try {
+ apkAssets = fd != null
+ ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
+ : ApkAssets.loadFromPath(apkPath);
+ } catch (IOException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+ "Failed to parse " + apkPath, e);
+ }
+
+ parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
+
+ final PackageParser.SigningDetails signingDetails;
+ if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) {
+ final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ ParseResult<PackageParser.SigningDetails> result =
+ ParsingPackageUtils.getSigningDetails(input,
+ apkFile.getAbsolutePath(), skipVerify, false,
+ PackageParser.SigningDetails.UNKNOWN,
+ DEFAULT_TARGET_SDK_VERSION);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ signingDetails = result.getResult();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ } else {
+ signingDetails = PackageParser.SigningDetails.UNKNOWN;
+ }
+
+ return parseApkLite(input, apkPath, parser, signingDetails);
+ } catch (XmlPullParserException | IOException | RuntimeException e) {
+ Slog.w(TAG, "Failed to parse " + apkPath, e);
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to parse " + apkPath, e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
+ }
+ }
+ // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
+ }
+ }
+
+ private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
+ XmlResourceParser parser, PackageParser.SigningDetails signingDetails)
+ throws IOException, XmlPullParserException {
+ ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ Pair<String, String> packageSplit = result.getResult();
+
+ int installLocation = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "installLocation", PARSE_DEFAULT_INSTALL_LOCATION);
+ int versionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "versionCode", 0);
+ int versionCodeMajor = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "versionCodeMajor",
+ 0);
+ int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0);
+ boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+ boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "isolatedSplits", false);
+ boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "isFeatureSplit", false);
+ boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "isSplitRequired", false);
+ String configForSplit = parser.getAttributeValue(null, "configForSplit");
+
+ int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
+ int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
+ boolean debuggable = false;
+ boolean profilableByShell = false;
+ boolean multiArch = false;
+ boolean use32bitAbi = false;
+ boolean extractNativeLibs = true;
+ boolean useEmbeddedDex = false;
+ String usesSplitName = null;
+ String targetPackage = null;
+ boolean overlayIsStatic = false;
+ int overlayPriority = 0;
+ int rollbackDataPolicy = 0;
+
+ String requiredSystemPropertyName = null;
+ String requiredSystemPropertyValue = null;
+
+ // Only search the tree when the tag is the direct child of <manifest> tag
+ int type;
+ final int searchDepth = parser.getDepth() + 1;
+
+ final List<VerifierInfo> verifiers = new ArrayList<>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() != searchDepth) {
+ continue;
+ }
+
+ if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+ final VerifierInfo verifier = parseVerifier(parser);
+ if (verifier != null) {
+ verifiers.add(verifier);
+ }
+ } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) {
+ debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable",
+ false);
+ multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch",
+ false);
+ use32bitAbi = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "use32bitAbi",
+ false);
+ extractNativeLibs = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "extractNativeLibs", true);
+ useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "useEmbeddedDex", false);
+ rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "rollbackDataPolicy", 0);
+
+ final int innerDepth = parser.getDepth();
+ int innerType;
+ while ((innerType = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (innerType != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (innerType == XmlPullParser.END_TAG || innerType == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() != innerDepth + 1) {
+ // Search only under <application>.
+ continue;
+ }
+
+ if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) {
+ profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "shell", profilableByShell);
+ }
+ }
+ } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) {
+ requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "requiredSystemPropertyName");
+ requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "requiredSystemPropertyValue");
+ targetPackage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "targetPackage");
+ overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic",
+ false);
+ overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0);
+ } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) {
+ if (usesSplitName != null) {
+ Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
+ continue;
+ }
+
+ usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
+ if (usesSplitName == null) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<uses-split> tag requires 'android:name' attribute");
+ }
+ } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) {
+ // Mirrors ParsingPackageUtils#parseUsesSdk until lite and full parsing is combined
+ String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "minSdkVersion");
+ String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "targetSdkVersion");
+
+ int minVer = DEFAULT_MIN_SDK_VERSION;
+ String minCode = null;
+ int targetVer = DEFAULT_TARGET_SDK_VERSION;
+ String targetCode = null;
+
+ if (!TextUtils.isEmpty(minSdkVersionString)) {
+ try {
+ minVer = Integer.parseInt(minSdkVersionString);
+ } catch (NumberFormatException ignored) {
+ minCode = minSdkVersionString;
+ }
+ }
+
+ if (!TextUtils.isEmpty(targetSdkVersionString)) {
+ try {
+ targetVer = Integer.parseInt(targetSdkVersionString);
+ } catch (NumberFormatException ignored) {
+ targetCode = targetSdkVersionString;
+ if (minCode == null) {
+ minCode = targetCode;
+ }
+ }
+ } else {
+ targetVer = minVer;
+ targetCode = minCode;
+ }
+
+ ParseResult<Integer> targetResult = ParsingPackageUtils.computeTargetSdkVersion(
+ targetVer, targetCode, ParsingPackageUtils.SDK_CODENAMES, input);
+ if (targetResult.isError()) {
+ return input.error(targetResult);
+ }
+ targetSdkVersion = targetResult.getResult();
+
+ ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion(
+ minVer, minCode, ParsingPackageUtils.SDK_VERSION,
+ ParsingPackageUtils.SDK_CODENAMES, input);
+ if (minResult.isError()) {
+ return input.error(minResult);
+ }
+ minSdkVersion = minResult.getResult();
+ }
+ }
+
+ // Check to see if overlay should be excluded based on system property condition
+ if (!PackageParser.checkRequiredSystemProperties(requiredSystemPropertyName,
+ requiredSystemPropertyValue)) {
+ Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ + codePath + ": overlay ignored due to required system property: "
+ + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
+ targetPackage = null;
+ overlayIsStatic = false;
+ overlayPriority = 0;
+ }
+
+ return input.success(
+ new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+ configForSplit, usesSplitName, isSplitRequired, versionCode,
+ versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
+ coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
+ useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
+ overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion,
+ rollbackDataPolicy));
+ }
+
+ public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input,
+ XmlResourceParser parser) throws IOException, XmlPullParserException {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No start tag found");
+ }
+ if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) {
+ return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No <manifest> tag");
+ }
+
+ final String packageName = parser.getAttributeValue(null, "package");
+ if (!"android".equals(packageName)) {
+ final ParseResult<?> nameResult = validateName(input, packageName, true, true);
+ if (nameResult.isError()) {
+ return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest package: " + nameResult.getErrorMessage());
+ }
+ }
+
+ String splitName = parser.getAttributeValue(null, "split");
+ if (splitName != null) {
+ if (splitName.length() == 0) {
+ splitName = null;
+ } else {
+ final ParseResult<?> nameResult = validateName(input, splitName, false, false);
+ if (nameResult.isError()) {
+ return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest split: " + nameResult.getErrorMessage());
+ }
+ }
+ }
+
+ return input.success(Pair.create(packageName.intern(),
+ (splitName != null) ? splitName.intern() : splitName));
+ }
+
+ public static VerifierInfo parseVerifier(AttributeSet attrs) {
+ String packageName = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
+ String encodedPublicKey = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "publicKey");
+
+ if (packageName == null || packageName.length() == 0) {
+ Slog.i(TAG, "verifier package name was null; skipping");
+ return null;
+ }
+
+ final PublicKey publicKey = PackageParser.parsePublicKey(encodedPublicKey);
+ if (publicKey == null) {
+ Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
+ return null;
+ }
+
+ return new VerifierInfo(packageName, publicKey);
+ }
+
+ /**
+ * Used to sort a set of APKs based on their split names, always placing the
+ * base APK (with {@code null} split name) first.
+ */
+ private static class SplitNameComparator implements Comparator<String> {
+ @Override
+ public int compare(String lhs, String rhs) {
+ if (lhs == null) {
+ return -1;
+ } else if (rhs == null) {
+ return 1;
+ } else {
+ return lhs.compareTo(rhs);
+ }
+ }
+ }
+
+ /**
+ * Check if the given file is an APK file.
+ *
+ * @param file the file to check.
+ * @return {@code true} if the given file is an APK file.
+ */
+ public static boolean isApkFile(File file) {
+ return isApkPath(file.getName());
+ }
+
+ /**
+ * Check if the given path ends with APK file extension.
+ *
+ * @param path the path to check.
+ * @return {@code true} if the given path ends with APK file extension.
+ */
+ public static boolean isApkPath(String path) {
+ return path.endsWith(APK_FILE_EXTENSION);
+ }
+}
diff --git a/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
new file mode 100644
index 0000000..c9054fd
--- /dev/null
+++ b/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (C) 2020 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.parsing;
+
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.apex.ApexInfo;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FallbackCategoryProvider;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageUserState;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.SELinuxUtil;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.component.ComponentParseUtils;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedAttribution;
+import android.content.pm.parsing.component.ParsedComponent;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.os.Environment;
+import android.os.UserHandle;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/** @hide **/
+public class PackageInfoWithoutStateUtils {
+
+ public static final String SYSTEM_DATA_PATH =
+ Environment.getDataDirectoryPath() + File.separator + "system";
+
+ @Nullable
+ public static PackageInfo generate(ParsingPackageRead pkg, int[] gids,
+ @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state, int userId) {
+ return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions,
+ state, userId, null);
+ }
+
+ @Nullable
+ public static PackageInfo generate(ParsingPackageRead pkg, ApexInfo apexInfo, int flags) {
+ return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
+ new PackageUserState(), UserHandle.getCallingUserId(), apexInfo);
+ }
+
+ @Nullable
+ private static PackageInfo generateWithComponents(ParsingPackageRead pkg, int[] gids,
+ @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state, int userId,
+ @Nullable ApexInfo apexInfo) {
+ ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId);
+ if (applicationInfo == null) {
+ return null;
+ }
+ PackageInfo info = generateWithoutComponents(pkg, gids, flags, firstInstallTime,
+ lastUpdateTime, grantedPermissions, state, userId, apexInfo, applicationInfo);
+
+ if (info == null) {
+ return null;
+ }
+
+ if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+ final int N = pkg.getActivities().size();
+ if (N > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[N];
+ for (int i = 0; i < N; i++) {
+ final ParsedActivity a = pkg.getActivities().get(i);
+ if (ComponentParseUtils.isMatch(state, false, pkg.isEnabled(), a,
+ flags)) {
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
+ a.getName())) {
+ continue;
+ }
+ res[num++] = generateActivityInfo(pkg, a, flags, state,
+ applicationInfo, userId);
+ }
+ }
+ info.activities = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+ final int size = pkg.getReceivers().size();
+ if (size > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedActivity a = pkg.getReceivers().get(i);
+ if (ComponentParseUtils.isMatch(state, false, pkg.isEnabled(), a,
+ flags)) {
+ res[num++] = generateActivityInfo(pkg, a, flags, state,
+ applicationInfo, userId);
+ }
+ }
+ info.receivers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_SERVICES) != 0) {
+ final int size = pkg.getServices().size();
+ if (size > 0) {
+ int num = 0;
+ final ServiceInfo[] res = new ServiceInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedService s = pkg.getServices().get(i);
+ if (ComponentParseUtils.isMatch(state, false, pkg.isEnabled(), s,
+ flags)) {
+ res[num++] = generateServiceInfo(pkg, s, flags, state,
+ applicationInfo, userId);
+ }
+ }
+ info.services = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+ final int size = pkg.getProviders().size();
+ if (size > 0) {
+ int num = 0;
+ final ProviderInfo[] res = new ProviderInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedProvider pr = pkg.getProviders()
+ .get(i);
+ if (ComponentParseUtils.isMatch(state, false, pkg.isEnabled(), pr,
+ flags)) {
+ res[num++] = generateProviderInfo(pkg, pr, flags, state,
+ applicationInfo, userId);
+ }
+ }
+ info.providers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_INSTRUMENTATION) != 0) {
+ int N = pkg.getInstrumentations().size();
+ if (N > 0) {
+ info.instrumentation = new InstrumentationInfo[N];
+ for (int i = 0; i < N; i++) {
+ info.instrumentation[i] = generateInstrumentationInfo(
+ pkg.getInstrumentations().get(i), pkg, flags, userId,
+ true /* assignUserFields */);
+ }
+ }
+ }
+
+ return info;
+ }
+
+ @Nullable
+ public static PackageInfo generateWithoutComponents(ParsingPackageRead pkg, int[] gids,
+ @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state, int userId,
+ @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) {
+ if (!checkUseInstalled(pkg, state, flags)) {
+ return null;
+ }
+
+ return generateWithoutComponentsUnchecked(pkg, gids, flags, firstInstallTime,
+ lastUpdateTime, grantedPermissions, state, userId, apexInfo, applicationInfo);
+ }
+
+ /**
+ * This bypasses critical checks that are necessary for usage with data passed outside of
+ * system server.
+ *
+ * Prefer {@link #generateWithoutComponents(ParsingPackageRead, int[], int, long, long, Set,
+ * PackageUserState, int, ApexInfo, ApplicationInfo)}.
+ */
+ @NonNull
+ public static PackageInfo generateWithoutComponentsUnchecked(ParsingPackageRead pkg, int[] gids,
+ @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime,
+ Set<String> grantedPermissions, PackageUserState state, int userId,
+ @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) {
+ PackageInfo pi = new PackageInfo();
+ pi.packageName = pkg.getPackageName();
+ pi.splitNames = pkg.getSplitNames();
+ pi.versionCode = pkg.getVersionCode();
+ pi.versionCodeMajor = pkg.getVersionCodeMajor();
+ pi.baseRevisionCode = pkg.getBaseRevisionCode();
+ pi.splitRevisionCodes = pkg.getSplitRevisionCodes();
+ pi.versionName = pkg.getVersionName();
+ pi.sharedUserId = pkg.getSharedUserId();
+ pi.sharedUserLabel = pkg.getSharedUserLabel();
+ pi.applicationInfo = applicationInfo;
+ pi.installLocation = pkg.getInstallLocation();
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || (pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ pi.requiredForAllUsers = pkg.isRequiredForAllUsers();
+ }
+ pi.restrictedAccountType = pkg.getRestrictedAccountType();
+ pi.requiredAccountType = pkg.getRequiredAccountType();
+ pi.overlayTarget = pkg.getOverlayTarget();
+ pi.targetOverlayableName = pkg.getOverlayTargetName();
+ pi.overlayCategory = pkg.getOverlayCategory();
+ pi.overlayPriority = pkg.getOverlayPriority();
+ pi.mOverlayIsStatic = pkg.isOverlayIsStatic();
+ pi.compileSdkVersion = pkg.getCompileSdkVersion();
+ pi.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName();
+ pi.firstInstallTime = firstInstallTime;
+ pi.lastUpdateTime = lastUpdateTime;
+ if ((flags & PackageManager.GET_GIDS) != 0) {
+ pi.gids = gids;
+ }
+ if ((flags & PackageManager.GET_CONFIGURATIONS) != 0) {
+ int size = pkg.getConfigPreferences().size();
+ if (size > 0) {
+ pi.configPreferences = new ConfigurationInfo[size];
+ pkg.getConfigPreferences().toArray(pi.configPreferences);
+ }
+ size = pkg.getReqFeatures().size();
+ if (size > 0) {
+ pi.reqFeatures = new FeatureInfo[size];
+ pkg.getReqFeatures().toArray(pi.reqFeatures);
+ }
+ size = pkg.getFeatureGroups().size();
+ if (size > 0) {
+ pi.featureGroups = new FeatureGroupInfo[size];
+ pkg.getFeatureGroups().toArray(pi.featureGroups);
+ }
+ }
+ if ((flags & PackageManager.GET_PERMISSIONS) != 0) {
+ int size = ArrayUtils.size(pkg.getPermissions());
+ if (size > 0) {
+ pi.permissions = new PermissionInfo[size];
+ for (int i = 0; i < size; i++) {
+ pi.permissions[i] = generatePermissionInfo(pkg.getPermissions().get(i),
+ flags);
+ }
+ }
+ final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+ size = usesPermissions.size();
+ if (size > 0) {
+ pi.requestedPermissions = new String[size];
+ pi.requestedPermissionsFlags = new int[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ pi.requestedPermissions[i] = usesPermission.name;
+ // The notion of required permissions is deprecated but for compatibility.
+ pi.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+ if (grantedPermissions != null
+ && grantedPermissions.contains(usesPermission.name)) {
+ pi.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_GRANTED;
+ }
+ if ((usesPermission.usesPermissionFlags
+ & ParsedUsesPermission.FLAG_NEVER_FOR_LOCATION) != 0) {
+ pi.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+ }
+ }
+ }
+ }
+ if ((flags & PackageManager.GET_ATTRIBUTIONS) != 0) {
+ int size = ArrayUtils.size(pkg.getAttributions());
+ if (size > 0) {
+ pi.attributions = new Attribution[size];
+ for (int i = 0; i < size; i++) {
+ pi.attributions[i] = generateAttribution(pkg.getAttributions().get(i));
+ }
+ }
+ if (pkg.areAttributionsUserVisible()) {
+ pi.applicationInfo.privateFlagsExt
+ |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ } else {
+ pi.applicationInfo.privateFlagsExt
+ &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ }
+ } else {
+ pi.applicationInfo.privateFlagsExt
+ &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ }
+
+ if (apexInfo != null) {
+ File apexFile = new File(apexInfo.modulePath);
+
+ pi.applicationInfo.sourceDir = apexFile.getPath();
+ pi.applicationInfo.publicSourceDir = apexFile.getPath();
+ if (apexInfo.isFactory) {
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+ } else {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+ }
+ if (apexInfo.isActive) {
+ pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
+ } else {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+ }
+ pi.isApex = true;
+ }
+
+ PackageParser.SigningDetails signingDetails = pkg.getSigningDetails();
+ // deprecated method of getting signing certificates
+ if ((flags & PackageManager.GET_SIGNATURES) != 0) {
+ if (signingDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ pi.signatures = new Signature[1];
+ pi.signatures[0] = signingDetails.pastSigningCertificates[0];
+ } else if (signingDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = signingDetails.signatures.length;
+ pi.signatures = new Signature[numberOfSigs];
+ System.arraycopy(signingDetails.signatures, 0, pi.signatures, 0,
+ numberOfSigs);
+ }
+ }
+
+ // replacement for GET_SIGNATURES
+ if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ if (signingDetails != PackageParser.SigningDetails.UNKNOWN) {
+ // only return a valid SigningInfo if there is signing information to report
+ pi.signingInfo = new SigningInfo(signingDetails);
+ } else {
+ pi.signingInfo = null;
+ }
+ }
+
+ return pi;
+ }
+
+ @Nullable
+ public static ApplicationInfo generateApplicationInfo(ParsingPackageRead pkg,
+ @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId) {
+ if (pkg == null) {
+ return null;
+ }
+
+ if (!checkUseInstalled(pkg, state, flags)) {
+ return null;
+ }
+
+ return generateApplicationInfoUnchecked(pkg, flags, state, userId,
+ true /* assignUserFields */);
+ }
+
+ /**
+ * This bypasses critical checks that are necessary for usage with data passed outside of
+ * system server.
+ *
+ * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}.
+ *
+ * @param assignUserFields whether to fill the returned {@link ApplicationInfo} with user
+ * specific fields. This can be skipped when building from a system
+ * server package, as there are cached strings which can be used rather
+ * than querying and concatenating the comparatively expensive
+ * {@link Environment#getDataDirectory(String)}}.
+ */
+ @NonNull
+ public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg,
+ @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId,
+ boolean assignUserFields) {
+ // Make shallow copy so we can store the metadata/libraries safely
+ ApplicationInfo ai = pkg.toAppInfoWithoutState();
+
+ if (assignUserFields) {
+ assignUserFields(pkg, ai, userId);
+ }
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ ai.metaData = null;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) == 0) {
+ ai.sharedLibraryFiles = null;
+ ai.sharedLibraryInfos = null;
+ }
+
+ // CompatibilityMode is global state.
+ if (!android.content.pm.PackageParser.sCompatibilityModeEnabled) {
+ ai.disableCompatibilityMode();
+ }
+
+ ai.flags |= flag(state.stopped, ApplicationInfo.FLAG_STOPPED)
+ | flag(state.installed, ApplicationInfo.FLAG_INSTALLED)
+ | flag(state.suspended, ApplicationInfo.FLAG_SUSPENDED);
+ ai.privateFlags |= flag(state.instantApp, ApplicationInfo.PRIVATE_FLAG_INSTANT)
+ | flag(state.virtualPreload, ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
+ | flag(state.hidden, ApplicationInfo.PRIVATE_FLAG_HIDDEN);
+
+ if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ ai.enabled = true;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ai.enabled = (flags & PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ ai.enabled = false;
+ }
+ ai.enabledSetting = state.enabled;
+ if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ ai.category = state.categoryHint;
+ }
+ if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
+ }
+ ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
+ final OverlayPaths overlayPaths = state.getAllOverlayPaths();
+ if (overlayPaths != null) {
+ ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]);
+ ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]);
+ }
+
+ return ai;
+ }
+
+ @Nullable
+ public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a,
+ @PackageManager.ComponentInfoFlags int flags, PackageUserState state,
+ @Nullable ApplicationInfo applicationInfo, int userId) {
+ if (a == null) return null;
+ if (!checkUseInstalled(pkg, state, flags)) {
+ return null;
+ }
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(pkg, flags, state, userId);
+ }
+ if (applicationInfo == null) {
+ return null;
+ }
+
+ return generateActivityInfoUnchecked(a, flags, applicationInfo);
+ }
+
+ /**
+ * This bypasses critical checks that are necessary for usage with data passed outside of
+ * system server.
+ *
+ * Prefer {@link #generateActivityInfo(ParsingPackageRead, ParsedActivity, int,
+ * PackageUserState, ApplicationInfo, int)}.
+ */
+ @NonNull
+ public static ActivityInfo generateActivityInfoUnchecked(@NonNull ParsedActivity a,
+ @PackageManager.ComponentInfoFlags int flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ // Make shallow copies so we can store the metadata safely
+ ActivityInfo ai = new ActivityInfo();
+ assignSharedFieldsForComponentInfo(ai, a);
+ ai.targetActivity = a.getTargetActivity();
+ ai.processName = a.getProcessName();
+ ai.exported = a.isExported();
+ ai.theme = a.getTheme();
+ ai.uiOptions = a.getUiOptions();
+ ai.parentActivityName = a.getParentActivityName();
+ ai.permission = a.getPermission();
+ ai.taskAffinity = a.getTaskAffinity();
+ ai.flags = a.getFlags();
+ ai.privateFlags = a.getPrivateFlags();
+ ai.launchMode = a.getLaunchMode();
+ ai.documentLaunchMode = a.getDocumentLaunchMode();
+ ai.maxRecents = a.getMaxRecents();
+ ai.configChanges = a.getConfigChanges();
+ ai.softInputMode = a.getSoftInputMode();
+ ai.persistableMode = a.getPersistableMode();
+ ai.lockTaskLaunchMode = a.getLockTaskLaunchMode();
+ ai.screenOrientation = a.getScreenOrientation();
+ ai.resizeMode = a.getResizeMode();
+ Float maxAspectRatio = a.getMaxAspectRatio();
+ ai.setMaxAspectRatio(maxAspectRatio != null ? maxAspectRatio : 0f);
+ Float minAspectRatio = a.getMinAspectRatio();
+ ai.setMinAspectRatio(minAspectRatio != null ? minAspectRatio : 0f);
+ ai.supportsSizeChanges = a.getSupportsSizeChanges();
+ ai.requestedVrComponent = a.getRequestedVrComponent();
+ ai.rotationAnimation = a.getRotationAnimation();
+ ai.colorMode = a.getColorMode();
+ ai.windowLayout = a.getWindowLayout();
+ ai.attributionTags = a.getAttributionTags();
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ ai.metaData = a.getMetaData();
+ }
+ ai.applicationInfo = applicationInfo;
+ return ai;
+ }
+
+ @Nullable
+ public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a,
+ @PackageManager.ComponentInfoFlags int flags, PackageUserState state, int userId) {
+ return generateActivityInfo(pkg, a, flags, state, null, userId);
+ }
+
+ @Nullable
+ public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s,
+ @PackageManager.ComponentInfoFlags int flags, PackageUserState state,
+ @Nullable ApplicationInfo applicationInfo, int userId) {
+ if (s == null) return null;
+ if (!checkUseInstalled(pkg, state, flags)) {
+ return null;
+ }
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(pkg, flags, state, userId);
+ }
+ if (applicationInfo == null) {
+ return null;
+ }
+
+ return generateServiceInfoUnchecked(s, flags, applicationInfo);
+ }
+
+ /**
+ * This bypasses critical checks that are necessary for usage with data passed outside of
+ * system server.
+ *
+ * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, int, PackageUserState,
+ * ApplicationInfo, int)}.
+ */
+ @NonNull
+ public static ServiceInfo generateServiceInfoUnchecked(@NonNull ParsedService s,
+ @PackageManager.ComponentInfoFlags int flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ // Make shallow copies so we can store the metadata safely
+ ServiceInfo si = new ServiceInfo();
+ assignSharedFieldsForComponentInfo(si, s);
+ si.exported = s.isExported();
+ si.flags = s.getFlags();
+ si.permission = s.getPermission();
+ si.processName = s.getProcessName();
+ si.mForegroundServiceType = s.getForegroundServiceType();
+ si.applicationInfo = applicationInfo;
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ si.metaData = s.getMetaData();
+ }
+ return si;
+ }
+
+ @Nullable
+ public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s,
+ @PackageManager.ComponentInfoFlags int flags, PackageUserState state, int userId) {
+ return generateServiceInfo(pkg, s, flags, state, null, userId);
+ }
+
+ @Nullable
+ public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p,
+ @PackageManager.ComponentInfoFlags int flags, PackageUserState state,
+ @Nullable ApplicationInfo applicationInfo, int userId) {
+ if (p == null) return null;
+ if (!checkUseInstalled(pkg, state, flags)) {
+ return null;
+ }
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(pkg, flags, state, userId);
+ }
+ if (applicationInfo == null) {
+ return null;
+ }
+
+ return generateProviderInfoUnchecked(p, flags, applicationInfo);
+ }
+
+ /**
+ * This bypasses critical checks that are necessary for usage with data passed outside of
+ * system server.
+ *
+ * Prefer {@link #generateProviderInfo(ParsingPackageRead, ParsedProvider, int,
+ * PackageUserState, ApplicationInfo, int)}.
+ */
+ @NonNull
+ public static ProviderInfo generateProviderInfoUnchecked(@NonNull ParsedProvider p,
+ @PackageManager.ComponentInfoFlags int flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ // Make shallow copies so we can store the metadata safely
+ ProviderInfo pi = new ProviderInfo();
+ assignSharedFieldsForComponentInfo(pi, p);
+ pi.exported = p.isExported();
+ pi.flags = p.getFlags();
+ pi.processName = p.getProcessName();
+ pi.authority = p.getAuthority();
+ pi.isSyncable = p.isSyncable();
+ pi.readPermission = p.getReadPermission();
+ pi.writePermission = p.getWritePermission();
+ pi.grantUriPermissions = p.isGrantUriPermissions();
+ pi.forceUriPermissions = p.isForceUriPermissions();
+ pi.multiprocess = p.isMultiProcess();
+ pi.initOrder = p.getInitOrder();
+ pi.uriPermissionPatterns = p.getUriPermissionPatterns();
+ pi.pathPermissions = p.getPathPermissions();
+ if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+ pi.uriPermissionPatterns = null;
+ }
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ pi.metaData = p.getMetaData();
+ }
+ pi.applicationInfo = applicationInfo;
+ return pi;
+ }
+
+ @Nullable
+ public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p,
+ @PackageManager.ComponentInfoFlags int flags, PackageUserState state, int userId) {
+ return generateProviderInfo(pkg, p, flags, state, null, userId);
+ }
+
+ /**
+ * @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, int,
+ * PackageUserState, int, boolean)}
+ */
+ @Nullable
+ public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
+ ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId,
+ boolean assignUserFields) {
+ if (i == null) return null;
+
+ InstrumentationInfo ii = new InstrumentationInfo();
+ assignSharedFieldsForPackageItemInfo(ii, i);
+ ii.targetPackage = i.getTargetPackage();
+ ii.targetProcesses = i.getTargetProcesses();
+ ii.handleProfiling = i.isHandleProfiling();
+ ii.functionalTest = i.isFunctionalTest();
+
+ ii.sourceDir = pkg.getBaseApkPath();
+ ii.publicSourceDir = pkg.getBaseApkPath();
+ ii.splitNames = pkg.getSplitNames();
+ ii.splitSourceDirs = pkg.getSplitCodePaths();
+ ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
+ ii.splitDependencies = pkg.getSplitDependencies();
+
+ if (assignUserFields) {
+ assignUserFields(pkg, ii, userId);
+ }
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ return ii;
+ }
+ ii.metaData = i.getMetaData();
+ return ii;
+ }
+
+ @Nullable
+ public static PermissionInfo generatePermissionInfo(ParsedPermission p,
+ @PackageManager.ComponentInfoFlags int flags) {
+ if (p == null) return null;
+
+ PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission());
+
+ assignSharedFieldsForPackageItemInfo(pi, p);
+
+ pi.group = p.getGroup();
+ pi.requestRes = p.getRequestRes();
+ pi.protectionLevel = p.getProtectionLevel();
+ pi.descriptionRes = p.getDescriptionRes();
+ pi.flags = p.getFlags();
+ pi.knownCerts = p.getKnownCerts();
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ return pi;
+ }
+ pi.metaData = p.getMetaData();
+ return pi;
+ }
+
+ @Nullable
+ public static PermissionGroupInfo generatePermissionGroupInfo(ParsedPermissionGroup pg,
+ @PackageManager.ComponentInfoFlags int flags) {
+ if (pg == null) return null;
+
+ PermissionGroupInfo pgi = new PermissionGroupInfo(
+ pg.getRequestDetailResourceId(),
+ pg.getBackgroundRequestResourceId(),
+ pg.getBackgroundRequestDetailResourceId()
+ );
+
+ assignSharedFieldsForPackageItemInfo(pgi, pg);
+ pgi.descriptionRes = pg.getDescriptionRes();
+ pgi.priority = pg.getPriority();
+ pgi.requestRes = pg.getRequestRes();
+ pgi.flags = pg.getFlags();
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ return pgi;
+ }
+ pgi.metaData = pg.getMetaData();
+ return pgi;
+ }
+
+ @Nullable
+ public static Attribution generateAttribution(ParsedAttribution pa) {
+ if (pa == null) return null;
+ return new Attribution(pa.tag, pa.label);
+ }
+
+ private static void assignSharedFieldsForComponentInfo(@NonNull ComponentInfo componentInfo,
+ @NonNull ParsedMainComponent mainComponent) {
+ assignSharedFieldsForPackageItemInfo(componentInfo, mainComponent);
+ componentInfo.descriptionRes = mainComponent.getDescriptionRes();
+ componentInfo.directBootAware = mainComponent.isDirectBootAware();
+ componentInfo.enabled = mainComponent.isEnabled();
+ componentInfo.splitName = mainComponent.getSplitName();
+ componentInfo.attributionTags = mainComponent.getAttributionTags();
+ }
+
+ private static void assignSharedFieldsForPackageItemInfo(
+ @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component) {
+ packageItemInfo.nonLocalizedLabel = ComponentParseUtils.getNonLocalizedLabel(component);
+ packageItemInfo.icon = ComponentParseUtils.getIcon(component);
+
+ packageItemInfo.banner = component.getBanner();
+ packageItemInfo.labelRes = component.getLabelRes();
+ packageItemInfo.logo = component.getLogo();
+ packageItemInfo.name = component.getName();
+ packageItemInfo.packageName = component.getPackageName();
+ }
+
+ @CheckResult
+ private static int flag(boolean hasFlag, int flag) {
+ if (hasFlag) {
+ return flag;
+ } else {
+ return 0;
+ }
+ }
+
+ /** @see ApplicationInfo#flags */
+ public static int appInfoFlags(ParsingPackageRead pkg) {
+ // @formatter:off
+ return flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
+ | flag(pkg.isBaseHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
+ | flag(pkg.isAllowBackup(), ApplicationInfo.FLAG_ALLOW_BACKUP)
+ | flag(pkg.isKillAfterRestore(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
+ | flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ | flag(pkg.isFullBackupOnly(), ApplicationInfo.FLAG_FULL_BACKUP_ONLY)
+ | flag(pkg.isPersistent(), ApplicationInfo.FLAG_PERSISTENT)
+ | flag(pkg.isDebuggable(), ApplicationInfo.FLAG_DEBUGGABLE)
+ | flag(pkg.isVmSafeMode(), ApplicationInfo.FLAG_VM_SAFE_MODE)
+ | flag(pkg.isHasCode(), ApplicationInfo.FLAG_HAS_CODE)
+ | flag(pkg.isAllowTaskReparenting(), ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING)
+ | flag(pkg.isAllowClearUserData(), ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)
+ | flag(pkg.isLargeHeap(), ApplicationInfo.FLAG_LARGE_HEAP)
+ | flag(pkg.isUsesCleartextTraffic(), ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC)
+ | flag(pkg.isSupportsRtl(), ApplicationInfo.FLAG_SUPPORTS_RTL)
+ | flag(pkg.isTestOnly(), ApplicationInfo.FLAG_TEST_ONLY)
+ | flag(pkg.isMultiArch(), ApplicationInfo.FLAG_MULTIARCH)
+ | flag(pkg.isExtractNativeLibs(), ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS)
+ | flag(pkg.isGame(), ApplicationInfo.FLAG_IS_GAME)
+ | flag(pkg.isSupportsSmallScreens(), ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS)
+ | flag(pkg.isSupportsNormalScreens(), ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS)
+ | flag(pkg.isSupportsLargeScreens(), ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS)
+ | flag(pkg.isSupportsExtraLargeScreens(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS)
+ | flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS)
+ | flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES);
+ // @formatter:on
+ }
+
+ /** @see ApplicationInfo#privateFlags */
+ public static int appInfoPrivateFlags(ParsingPackageRead pkg) {
+ // @formatter:off
+ int privateFlags = flag(pkg.isStaticSharedLibrary(), ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY)
+ | flag(pkg.isOverlay(), ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY)
+ | flag(pkg.isIsolatedSplitLoading(), ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING)
+ | flag(pkg.isHasDomainUrls(), ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS)
+ | flag(pkg.isProfileableByShell(), ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL)
+ | flag(pkg.isBackupInForeground(), ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND)
+ | flag(pkg.isUseEmbeddedDex(), ApplicationInfo.PRIVATE_FLAG_USE_EMBEDDED_DEX)
+ | flag(pkg.isDefaultToDeviceProtectedStorage(), ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE)
+ | flag(pkg.isDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE)
+ | flag(pkg.isPartiallyDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE)
+ | flag(pkg.isAllowClearUserDataOnFailedRestore(), ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE)
+ | flag(pkg.isAllowAudioPlaybackCapture(), ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE)
+ | flag(pkg.isRequestLegacyExternalStorage(), ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE)
+ | flag(pkg.isUsesNonSdkApi(), ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API)
+ | flag(pkg.isHasFragileUserData(), ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
+ | flag(pkg.isCantSaveState(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
+ | flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION)
+ | flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING);
+ // @formatter:on
+
+ Boolean resizeableActivity = pkg.getResizeableActivity();
+ if (resizeableActivity != null) {
+ if (resizeableActivity) {
+ privateFlags |= ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
+ } else {
+ privateFlags |= ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+ }
+ }
+
+ return privateFlags;
+ }
+
+ /** @see ApplicationInfo#privateFlagsExt */
+ public static int appInfoPrivateFlagsExt(ParsingPackageRead pkg) {
+ // @formatter:off
+ int privateFlagsExt =
+ flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
+ | flag(pkg.hasRequestForegroundServiceExemption(),
+ ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
+ | flag(pkg.areAttributionsUserVisible(),
+ ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE);
+ // @formatter:on
+ return privateFlagsExt;
+ }
+
+ private static boolean checkUseInstalled(ParsingPackageRead pkg, PackageUserState state,
+ @PackageManager.PackageInfoFlags int flags) {
+ // If available for the target user
+ return state.isAvailable(flags);
+ }
+
+ @NonNull
+ public static File getDataDir(ParsingPackageRead pkg, int userId) {
+ if ("android".equals(pkg.getPackageName())) {
+ return Environment.getDataSystemDirectory();
+ }
+
+ if (pkg.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ return getDeviceProtectedDataDir(pkg, userId);
+ } else {
+ return getCredentialProtectedDataDir(pkg, userId);
+ }
+ }
+
+ @NonNull
+ public static File getDeviceProtectedDataDir(ParsingPackageRead pkg, int userId) {
+ return Environment.getDataUserDePackageDirectory(pkg.getVolumeUuid(), userId,
+ pkg.getPackageName());
+ }
+
+ @NonNull
+ public static File getCredentialProtectedDataDir(ParsingPackageRead pkg, int userId) {
+ return Environment.getDataUserCePackageDirectory(pkg.getVolumeUuid(), userId,
+ pkg.getPackageName());
+ }
+
+ private static void assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId) {
+ // This behavior is undefined for no-state ApplicationInfos when called by a public API,
+ // since the uid is never assigned by the system. It will always effectively be appId 0.
+ info.uid = UserHandle.getUid(userId, UserHandle.getAppId(info.uid));
+
+ String pkgName = pkg.getPackageName();
+ if ("android".equals(pkgName)) {
+ info.dataDir = SYSTEM_DATA_PATH;
+ return;
+ }
+
+ // For performance reasons, all these paths are built as strings
+ String baseDataDirPrefix =
+ Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
+ String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
+ info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
+ + userIdPkgSuffix;
+ info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;
+
+ if (pkg.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ info.dataDir = info.deviceProtectedDataDir;
+ } else {
+ info.dataDir = info.credentialProtectedDataDir;
+ }
+ }
+
+ private static void assignUserFields(ParsingPackageRead pkg, InstrumentationInfo info,
+ int userId) {
+ String pkgName = pkg.getPackageName();
+ if ("android".equals(pkgName)) {
+ info.dataDir = SYSTEM_DATA_PATH;
+ return;
+ }
+
+ // For performance reasons, all these paths are built as strings
+ String baseDataDirPrefix =
+ Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
+ String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
+ info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
+ + userIdPkgSuffix;
+ info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;
+
+ if (pkg.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ info.dataDir = info.deviceProtectedDataDir;
+ } else {
+ info.dataDir = info.credentialProtectedDataDir;
+ }
+ }
+}
diff --git a/android/content/pm/parsing/PackageLite.java b/android/content/pm/parsing/PackageLite.java
new file mode 100644
index 0000000..9172555
--- /dev/null
+++ b/android/content/pm/parsing/PackageLite.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2021 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.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.content.pm.VerifierInfo;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lightweight parsed details about a single package.
+ *
+ * @hide
+ */
+@DataClass(genConstructor = false, genConstDefs = false)
+public class PackageLite {
+ /** Name of the package as used to identify it in the system */
+ private final @NonNull String mPackageName;
+ /**
+ * Path where this package was found on disk. For monolithic packages
+ * this is path to single base APK file; for cluster packages this is
+ * path to the cluster directory.
+ */
+ private final @NonNull String mPath;
+ /** Path of base APK */
+ private final @NonNull String mBaseApkPath;
+ /** Paths of any split APKs, ordered by parsed splitName */
+ private final @Nullable String[] mSplitApkPaths;
+ /** Names of any split APKs, ordered by parsed splitName */
+ private final @Nullable String[] mSplitNames;
+ /** Dependencies of any split APKs, ordered by parsed splitName */
+ private final @Nullable String[] mUsesSplitNames;
+ private final @Nullable String[] mConfigForSplit;
+ /** Major and minor version number of this package */
+ private final int mVersionCodeMajor;
+ private final int mVersionCode;
+ private final int mTargetSdk;
+ /** Revision code of base APK */
+ private final int mBaseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ private final @Nullable int[] mSplitRevisionCodes;
+ /**
+ * Indicate the install location of this package
+ *
+ * @see {@link PackageInfo#INSTALL_LOCATION_AUTO}
+ * @see {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}
+ * @see {@link PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL}
+ */
+ private final int mInstallLocation;
+ /** Information about a package verifiers as used during package verification */
+ private final @NonNull VerifierInfo[] mVerifiers;
+
+ /** Indicate whether any split APKs that are features. Ordered by splitName */
+ private final @Nullable boolean[] mIsFeatureSplits;
+ /** Indicate whether each split should be load into their own Context objects */
+ private final boolean mIsolatedSplits;
+ /**
+ * Indicate whether this package requires at least one split (either feature or resource)
+ * to be present in order to function
+ */
+ private final boolean mSplitRequired;
+ /** Indicate whether this app is coreApp */
+ private final boolean mCoreApp;
+ /** Indicate whether this app can be debugged */
+ private final boolean mDebuggable;
+ /** Indicate whether this app needs to be loaded into other applications' processes */
+ private final boolean mMultiArch;
+ /** Indicate whether the 32 bit version of the ABI should be used */
+ private final boolean mUse32bitAbi;
+ /** Indicate whether installer extracts native libraries */
+ private final boolean mExtractNativeLibs;
+ /** Indicate whether this app is profileable by Shell */
+ private final boolean mProfileableByShell;
+ /**
+ * Indicate whether this package wants to run the dex within its APK but not extracted
+ * or locally compiled variants.
+ */
+ private final boolean mUseEmbeddedDex;
+
+ public PackageLite(String path, String baseApkPath, ApkLite baseApk,
+ String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
+ String[] configForSplit, String[] splitApkPaths, int[] splitRevisionCodes,
+ int targetSdk) {
+ // The following paths may be different from the path in ApkLite because we
+ // move or rename the APK files. Use parameters to indicate the correct paths.
+ mPath = path;
+ mBaseApkPath = baseApkPath;
+ mPackageName = baseApk.getPackageName();
+ mVersionCode = baseApk.getVersionCode();
+ mVersionCodeMajor = baseApk.getVersionCodeMajor();
+ mInstallLocation = baseApk.getInstallLocation();
+ mVerifiers = baseApk.getVerifiers();
+ mBaseRevisionCode = baseApk.getRevisionCode();
+ mCoreApp = baseApk.isCoreApp();
+ mDebuggable = baseApk.isDebuggable();
+ mMultiArch = baseApk.isMultiArch();
+ mUse32bitAbi = baseApk.isUse32bitAbi();
+ mExtractNativeLibs = baseApk.isExtractNativeLibs();
+ mIsolatedSplits = baseApk.isIsolatedSplits();
+ mUseEmbeddedDex = baseApk.isUseEmbeddedDex();
+ mSplitRequired = baseApk.isSplitRequired();
+ mProfileableByShell = baseApk.isProfileableByShell();
+ mSplitNames = splitNames;
+ mIsFeatureSplits = isFeatureSplits;
+ mUsesSplitNames = usesSplitNames;
+ mConfigForSplit = configForSplit;
+ mSplitApkPaths = splitApkPaths;
+ mSplitRevisionCodes = splitRevisionCodes;
+ mTargetSdk = targetSdk;
+ }
+
+ /**
+ * Return code path to the base APK file, and split APK files if any.
+ */
+ public List<String> getAllApkPaths() {
+ final ArrayList<String> paths = new ArrayList<>();
+ paths.add(mBaseApkPath);
+ if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
+ Collections.addAll(paths, mSplitApkPaths);
+ }
+ return paths;
+ }
+
+ /**
+ * Return {@link #mVersionCode} and {@link #mVersionCodeMajor} combined together as a
+ * single long value. The {@link #mVersionCodeMajor} is placed in the upper 32 bits.
+ */
+ public long getLongVersionCode() {
+ return PackageInfo.composeLongVersionCode(mVersionCodeMajor, mVersionCode);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/PackageLite.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Name of the package as used to identify it in the system
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Path where this package was found on disk. For monolithic packages
+ * this is path to single base APK file; for cluster packages this is
+ * path to the cluster directory.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPath() {
+ return mPath;
+ }
+
+ /**
+ * Path of base APK
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getBaseApkPath() {
+ return mBaseApkPath;
+ }
+
+ /**
+ * Paths of any split APKs, ordered by parsed splitName
+ */
+ @DataClass.Generated.Member
+ public @Nullable String[] getSplitApkPaths() {
+ return mSplitApkPaths;
+ }
+
+ /**
+ * Names of any split APKs, ordered by parsed splitName
+ */
+ @DataClass.Generated.Member
+ public @Nullable String[] getSplitNames() {
+ return mSplitNames;
+ }
+
+ /**
+ * Dependencies of any split APKs, ordered by parsed splitName
+ */
+ @DataClass.Generated.Member
+ public @Nullable String[] getUsesSplitNames() {
+ return mUsesSplitNames;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String[] getConfigForSplit() {
+ return mConfigForSplit;
+ }
+
+ /**
+ * Major and minor version number of this package
+ */
+ @DataClass.Generated.Member
+ public int getVersionCodeMajor() {
+ return mVersionCodeMajor;
+ }
+
+ @DataClass.Generated.Member
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ @DataClass.Generated.Member
+ public int getTargetSdk() {
+ return mTargetSdk;
+ }
+
+ /**
+ * Revision code of base APK
+ */
+ @DataClass.Generated.Member
+ public int getBaseRevisionCode() {
+ return mBaseRevisionCode;
+ }
+
+ /**
+ * Revision codes of any split APKs, ordered by parsed splitName
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getSplitRevisionCodes() {
+ return mSplitRevisionCodes;
+ }
+
+ /**
+ * Indicate the install location of this package
+ *
+ * @see {@link PackageInfo#INSTALL_LOCATION_AUTO}
+ * @see {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}
+ * @see {@link PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL}
+ */
+ @DataClass.Generated.Member
+ public int getInstallLocation() {
+ return mInstallLocation;
+ }
+
+ /**
+ * Information about a package verifiers as used during package verification
+ */
+ @DataClass.Generated.Member
+ public @NonNull VerifierInfo[] getVerifiers() {
+ return mVerifiers;
+ }
+
+ /**
+ * Indicate whether any split APKs that are features. Ordered by splitName
+ */
+ @DataClass.Generated.Member
+ public @Nullable boolean[] getIsFeatureSplits() {
+ return mIsFeatureSplits;
+ }
+
+ /**
+ * Indicate whether each split should be load into their own Context objects
+ */
+ @DataClass.Generated.Member
+ public boolean isIsolatedSplits() {
+ return mIsolatedSplits;
+ }
+
+ /**
+ * Indicate whether this package requires at least one split (either feature or resource)
+ * to be present in order to function
+ */
+ @DataClass.Generated.Member
+ public boolean isSplitRequired() {
+ return mSplitRequired;
+ }
+
+ /**
+ * Indicate whether this app is coreApp
+ */
+ @DataClass.Generated.Member
+ public boolean isCoreApp() {
+ return mCoreApp;
+ }
+
+ /**
+ * Indicate whether this app can be debugged
+ */
+ @DataClass.Generated.Member
+ public boolean isDebuggable() {
+ return mDebuggable;
+ }
+
+ /**
+ * Indicate whether this app needs to be loaded into other applications' processes
+ */
+ @DataClass.Generated.Member
+ public boolean isMultiArch() {
+ return mMultiArch;
+ }
+
+ /**
+ * Indicate whether the 32 bit version of the ABI should be used
+ */
+ @DataClass.Generated.Member
+ public boolean isUse32bitAbi() {
+ return mUse32bitAbi;
+ }
+
+ /**
+ * Indicate whether installer extracts native libraries
+ */
+ @DataClass.Generated.Member
+ public boolean isExtractNativeLibs() {
+ return mExtractNativeLibs;
+ }
+
+ /**
+ * Indicate whether this app is profileable by Shell
+ */
+ @DataClass.Generated.Member
+ public boolean isProfileableByShell() {
+ return mProfileableByShell;
+ }
+
+ /**
+ * Indicate whether this package wants to run the dex within its APK but not extracted
+ * or locally compiled variants.
+ */
+ @DataClass.Generated.Member
+ public boolean isUseEmbeddedDex() {
+ return mUseEmbeddedDex;
+ }
+
+ @DataClass.Generated(
+ time = 1615914120261L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nclass PackageLite extends java.lang.Object implements []\[email protected](genConstructor=false, genConstDefs=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/parsing/ParsingPackage.java b/android/content/pm/parsing/ParsingPackage.java
new file mode 100644
index 0000000..ed68dbf
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackage.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2020 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.parsing;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.PackageParser;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedAttribution;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import java.security.PublicKey;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Methods used for mutation during direct package parsing.
+ *
+ * @hide
+ */
+@SuppressWarnings("UnusedReturnValue")
+public interface ParsingPackage extends ParsingPackageRead {
+
+ ParsingPackage addActivity(ParsedActivity parsedActivity);
+
+ ParsingPackage addAdoptPermission(String adoptPermission);
+
+ ParsingPackage addConfigPreference(ConfigurationInfo configPreference);
+
+ ParsingPackage addFeatureGroup(FeatureGroupInfo featureGroup);
+
+ ParsingPackage addImplicitPermission(String permission);
+
+ ParsingPackage addInstrumentation(ParsedInstrumentation instrumentation);
+
+ ParsingPackage addKeySet(String keySetName, PublicKey publicKey);
+
+ ParsingPackage addLibraryName(String libraryName);
+
+ ParsingPackage addOriginalPackage(String originalPackage);
+
+ ParsingPackage addOverlayable(String overlayableName, String actorName);
+
+ ParsingPackage addPermission(ParsedPermission permission);
+
+ ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup);
+
+ ParsingPackage addPreferredActivityFilter(String className, ParsedIntentInfo intentInfo);
+
+ /** Add a property to the application scope */
+ ParsingPackage addProperty(Property property);
+
+ ParsingPackage addProtectedBroadcast(String protectedBroadcast);
+
+ ParsingPackage addProvider(ParsedProvider parsedProvider);
+
+ ParsingPackage addAttribution(ParsedAttribution attribution);
+
+ ParsingPackage addReceiver(ParsedActivity parsedReceiver);
+
+ ParsingPackage addReqFeature(FeatureInfo reqFeature);
+
+ ParsingPackage addUsesPermission(ParsedUsesPermission parsedUsesPermission);
+
+ ParsingPackage addService(ParsedService parsedService);
+
+ ParsingPackage addUsesLibrary(String libraryName);
+
+ ParsingPackage addUsesOptionalLibrary(String libraryName);
+
+ ParsingPackage addUsesNativeLibrary(String libraryName);
+
+ ParsingPackage addUsesOptionalNativeLibrary(String libraryName);
+
+ ParsingPackage addUsesStaticLibrary(String libraryName);
+
+ ParsingPackage addUsesStaticLibraryCertDigests(String[] certSha256Digests);
+
+ ParsingPackage addUsesStaticLibraryVersion(long version);
+
+ ParsingPackage addQueriesIntent(Intent intent);
+
+ ParsingPackage addQueriesPackage(String packageName);
+
+ ParsingPackage addQueriesProvider(String authority);
+
+ ParsingPackage setProcesses(@NonNull Map<String, ParsedProcess> processes);
+
+ ParsingPackage asSplit(
+ String[] splitNames,
+ String[] splitCodePaths,
+ int[] splitRevisionCodes,
+ @Nullable SparseArray<int[]> splitDependencies
+ );
+
+ ParsingPackage setMetaData(Bundle metaData);
+
+ ParsingPackage setForceQueryable(boolean forceQueryable);
+
+ ParsingPackage setMaxAspectRatio(float maxAspectRatio);
+
+ ParsingPackage setMinAspectRatio(float minAspectRatio);
+
+ ParsingPackage setPermission(String permission);
+
+ ParsingPackage setProcessName(String processName);
+
+ ParsingPackage setSharedUserId(String sharedUserId);
+
+ ParsingPackage setStaticSharedLibName(String staticSharedLibName);
+
+ ParsingPackage setTaskAffinity(String taskAffinity);
+
+ ParsingPackage setTargetSdkVersion(int targetSdkVersion);
+
+ ParsingPackage setUiOptions(int uiOptions);
+
+ ParsingPackage setBaseHardwareAccelerated(boolean baseHardwareAccelerated);
+
+ ParsingPackage setResizeableActivity(Boolean resizeable);
+
+ ParsingPackage setResizeableActivityViaSdkVersion(boolean resizeableViaSdkVersion);
+
+ ParsingPackage setAllowAudioPlaybackCapture(boolean allowAudioPlaybackCapture);
+
+ ParsingPackage setAllowBackup(boolean allowBackup);
+
+ ParsingPackage setAllowClearUserData(boolean allowClearUserData);
+
+ ParsingPackage setAllowClearUserDataOnFailedRestore(boolean allowClearUserDataOnFailedRestore);
+
+ ParsingPackage setAllowTaskReparenting(boolean allowTaskReparenting);
+
+ ParsingPackage setOverlay(boolean isOverlay);
+
+ ParsingPackage setBackupInForeground(boolean backupInForeground);
+
+ ParsingPackage setCantSaveState(boolean cantSaveState);
+
+ ParsingPackage setDebuggable(boolean debuggable);
+
+ ParsingPackage setDefaultToDeviceProtectedStorage(boolean defaultToDeviceProtectedStorage);
+
+ ParsingPackage setDirectBootAware(boolean directBootAware);
+
+ ParsingPackage setExternalStorage(boolean externalStorage);
+
+ ParsingPackage setExtractNativeLibs(boolean extractNativeLibs);
+
+ ParsingPackage setFullBackupOnly(boolean fullBackupOnly);
+
+ ParsingPackage setHasCode(boolean hasCode);
+
+ ParsingPackage setHasFragileUserData(boolean hasFragileUserData);
+
+ ParsingPackage setGame(boolean isGame);
+
+ ParsingPackage setIsolatedSplitLoading(boolean isolatedSplitLoading);
+
+ ParsingPackage setKillAfterRestore(boolean killAfterRestore);
+
+ ParsingPackage setLargeHeap(boolean largeHeap);
+
+ ParsingPackage setMultiArch(boolean multiArch);
+
+ ParsingPackage setPartiallyDirectBootAware(boolean partiallyDirectBootAware);
+
+ ParsingPackage setPersistent(boolean persistent);
+
+ ParsingPackage setProfileableByShell(boolean profileableByShell);
+
+ ParsingPackage setProfileable(boolean profileable);
+
+ ParsingPackage setRequestLegacyExternalStorage(boolean requestLegacyExternalStorage);
+
+ ParsingPackage setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging);
+
+ ParsingPackage setAutoRevokePermissions(int autoRevokePermissions);
+
+ ParsingPackage setPreserveLegacyExternalStorage(boolean preserveLegacyExternalStorage);
+
+ ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion);
+
+ ParsingPackage setSplitHasCode(int splitIndex, boolean splitHasCode);
+
+ ParsingPackage setStaticSharedLibrary(boolean staticSharedLibrary);
+
+ ParsingPackage setSupportsRtl(boolean supportsRtl);
+
+ ParsingPackage setTestOnly(boolean testOnly);
+
+ ParsingPackage setUseEmbeddedDex(boolean useEmbeddedDex);
+
+ ParsingPackage setUsesCleartextTraffic(boolean usesCleartextTraffic);
+
+ ParsingPackage setUsesNonSdkApi(boolean usesNonSdkApi);
+
+ ParsingPackage setVisibleToInstantApps(boolean visibleToInstantApps);
+
+ ParsingPackage setVmSafeMode(boolean vmSafeMode);
+
+ ParsingPackage removeUsesOptionalLibrary(String libraryName);
+
+ ParsingPackage removeUsesOptionalNativeLibrary(String libraryName);
+
+ ParsingPackage setAnyDensity(int anyDensity);
+
+ ParsingPackage setAppComponentFactory(String appComponentFactory);
+
+ ParsingPackage setBackupAgentName(String backupAgentName);
+
+ ParsingPackage setBanner(int banner);
+
+ ParsingPackage setCategory(int category);
+
+ ParsingPackage setClassLoaderName(String classLoaderName);
+
+ ParsingPackage setClassName(String className);
+
+ ParsingPackage setCompatibleWidthLimitDp(int compatibleWidthLimitDp);
+
+ ParsingPackage setDescriptionRes(int descriptionRes);
+
+ ParsingPackage setEnabled(boolean enabled);
+
+ ParsingPackage setGwpAsanMode(@ApplicationInfo.GwpAsanMode int gwpAsanMode);
+
+ ParsingPackage setMemtagMode(@ApplicationInfo.MemtagMode int memtagMode);
+
+ ParsingPackage setNativeHeapZeroInitialized(
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized);
+
+ ParsingPackage setRequestRawExternalStorageAccess(
+ @Nullable Boolean requestRawExternalStorageAccess);
+
+ ParsingPackage setCrossProfile(boolean crossProfile);
+
+ ParsingPackage setFullBackupContent(int fullBackupContent);
+
+ ParsingPackage setDataExtractionRules(int dataExtractionRules);
+
+ ParsingPackage setHasDomainUrls(boolean hasDomainUrls);
+
+ ParsingPackage setIconRes(int iconRes);
+
+ ParsingPackage setInstallLocation(int installLocation);
+
+ ParsingPackage setLabelRes(int labelRes);
+
+ ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
+
+ ParsingPackage setLogo(int logo);
+
+ ParsingPackage setManageSpaceActivityName(String manageSpaceActivityName);
+
+ ParsingPackage setMinExtensionVersions(@Nullable SparseIntArray minExtensionVersions);
+
+ ParsingPackage setMinSdkVersion(int minSdkVersion);
+
+ ParsingPackage setNetworkSecurityConfigRes(int networkSecurityConfigRes);
+
+ ParsingPackage setNonLocalizedLabel(CharSequence nonLocalizedLabel);
+
+ ParsingPackage setOverlayCategory(String overlayCategory);
+
+ ParsingPackage setOverlayIsStatic(boolean overlayIsStatic);
+
+ ParsingPackage setOverlayPriority(int overlayPriority);
+
+ ParsingPackage setOverlayTarget(String overlayTarget);
+
+ ParsingPackage setOverlayTargetName(String overlayTargetName);
+
+ ParsingPackage setRealPackage(String realPackage);
+
+ ParsingPackage setRequiredAccountType(String requiredAccountType);
+
+ ParsingPackage setRequiredForAllUsers(boolean requiredForAllUsers);
+
+ ParsingPackage setRequiresSmallestWidthDp(int requiresSmallestWidthDp);
+
+ ParsingPackage setResizeable(int resizeable);
+
+ ParsingPackage setRestrictUpdateHash(byte[] restrictUpdateHash);
+
+ ParsingPackage setRestrictedAccountType(String restrictedAccountType);
+
+ ParsingPackage setRoundIconRes(int roundIconRes);
+
+ ParsingPackage setSharedUserLabel(int sharedUserLabel);
+
+ ParsingPackage setSigningDetails(PackageParser.SigningDetails signingDetails);
+
+ ParsingPackage setSplitClassLoaderName(int splitIndex, String classLoaderName);
+
+ ParsingPackage setStaticSharedLibVersion(long staticSharedLibVersion);
+
+ ParsingPackage setSupportsLargeScreens(int supportsLargeScreens);
+
+ ParsingPackage setSupportsNormalScreens(int supportsNormalScreens);
+
+ ParsingPackage setSupportsSmallScreens(int supportsSmallScreens);
+
+ ParsingPackage setSupportsExtraLargeScreens(int supportsExtraLargeScreens);
+
+ ParsingPackage setTargetSandboxVersion(int targetSandboxVersion);
+
+ ParsingPackage setTheme(int theme);
+
+ ParsingPackage setRequestForegroundServiceExemption(boolean requestForegroundServiceExemption);
+
+ ParsingPackage setUpgradeKeySets(@NonNull Set<String> upgradeKeySets);
+
+ ParsingPackage setUse32BitAbi(boolean use32BitAbi);
+
+ ParsingPackage setVolumeUuid(@Nullable String volumeUuid);
+
+ ParsingPackage setZygotePreloadName(String zygotePreloadName);
+
+ ParsingPackage sortActivities();
+
+ ParsingPackage sortReceivers();
+
+ ParsingPackage sortServices();
+
+ ParsingPackage setBaseRevisionCode(int baseRevisionCode);
+
+ ParsingPackage setVersionName(String versionName);
+
+ ParsingPackage setCompileSdkVersion(int compileSdkVersion);
+
+ ParsingPackage setCompileSdkVersionCodename(String compileSdkVersionCodename);
+
+ ParsingPackage setAttributionsAreUserVisible(boolean attributionsAreUserVisible);
+
+ // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
+ // for moving to the next step
+ @CallSuper
+ Object hideAsParsed();
+}
diff --git a/android/content/pm/parsing/ParsingPackageImpl.java b/android/content/pm/parsing/ParsingPackageImpl.java
new file mode 100644
index 0000000..5a7f210
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackageImpl.java
@@ -0,0 +1,2775 @@
+/*
+ * Copyright (C) 2020 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.parsing;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+
+import android.annotation.CallSuper;
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.PackageParser;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedAttribution;
+import android.content.pm.parsing.component.ParsedComponent;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForBoolean;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArray;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringList;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringValueMap;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * The backing data for a package that was parsed from disk.
+ *
+ * The field nullability annotations here are for internal reference. For effective nullability,
+ * see the parent interfaces.
+ *
+ * TODO(b/135203078): Convert Lists used as sets into Sets, to better express intended use case
+ *
+ * @hide
+ */
+public class ParsingPackageImpl implements ParsingPackage, Parcelable {
+
+ private static final String TAG = "PackageImpl";
+
+ public static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+ public static ForInternedString sForInternedString = Parcelling.Cache.getOrCreate(
+ ForInternedString.class);
+ public static ForInternedStringArray sForInternedStringArray = Parcelling.Cache.getOrCreate(
+ ForInternedStringArray.class);
+ public static ForInternedStringList sForInternedStringList = Parcelling.Cache.getOrCreate(
+ ForInternedStringList.class);
+ public static ForInternedStringValueMap sForInternedStringValueMap =
+ Parcelling.Cache.getOrCreate(ForInternedStringValueMap.class);
+ public static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+ public static ForInternedStringSet sForInternedStringSet =
+ Parcelling.Cache.getOrCreate(ForInternedStringSet.class);
+ protected static ParsedIntentInfo.StringPairListParceler sForIntentInfoPairs =
+ Parcelling.Cache.getOrCreate(ParsedIntentInfo.StringPairListParceler.class);
+
+ private static final Comparator<ParsedMainComponent> ORDER_COMPARATOR =
+ (first, second) -> Integer.compare(second.getOrder(), first.getOrder());
+
+ // These are objects because null represents not explicitly set
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean supportsSmallScreens;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean supportsNormalScreens;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean supportsLargeScreens;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean supportsExtraLargeScreens;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean resizeable;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean anyDensity;
+
+ protected int versionCode;
+ protected int versionCodeMajor;
+ private int baseRevisionCode;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String versionName;
+
+ private int compileSdkVersion;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String compileSdkVersionCodeName;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ protected String packageName;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String realPackage;
+
+ @NonNull
+ protected String mBaseApkPath;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String restrictedAccountType;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String requiredAccountType;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String overlayTarget;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String overlayTargetName;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String overlayCategory;
+ private int overlayPriority;
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringValueMap.class)
+ private Map<String, String> overlayables = emptyMap();
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String staticSharedLibName;
+ private long staticSharedLibVersion;
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ private List<String> libraryNames = emptyList();
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> usesLibraries = emptyList();
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> usesOptionalLibraries = emptyList();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> usesNativeLibraries = emptyList();
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> usesOptionalNativeLibraries = emptyList();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ private List<String> usesStaticLibraries = emptyList();
+ @Nullable
+ private long[] usesStaticLibrariesVersions;
+
+ @Nullable
+ private String[][] usesStaticLibrariesCertDigests;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String sharedUserId;
+
+ private int sharedUserLabel;
+ @NonNull
+ private List<ConfigurationInfo> configPreferences = emptyList();
+ @NonNull
+ private List<FeatureInfo> reqFeatures = emptyList();
+ @NonNull
+ private List<FeatureGroupInfo> featureGroups = emptyList();
+
+ @Nullable
+ private byte[] restrictUpdateHash;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> originalPackages = emptyList();
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> adoptPermissions = emptyList();
+ /**
+ * @deprecated consider migrating to {@link #getUsesPermissions} which has
+ * more parsed details, such as flags
+ */
+ @NonNull
+ @Deprecated
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> requestedPermissions = emptyList();
+
+ @NonNull
+ private List<ParsedUsesPermission> usesPermissions = emptyList();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ private List<String> implicitPermissions = emptyList();
+
+ @NonNull
+ private Set<String> upgradeKeySets = emptySet();
+ @NonNull
+ private Map<String, ArraySet<PublicKey>> keySetMapping = emptyMap();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ protected List<String> protectedBroadcasts = emptyList();
+
+ @NonNull
+ protected List<ParsedActivity> activities = emptyList();
+
+ @NonNull
+ protected List<ParsedActivity> receivers = emptyList();
+
+ @NonNull
+ protected List<ParsedService> services = emptyList();
+
+ @NonNull
+ protected List<ParsedProvider> providers = emptyList();
+
+ @NonNull
+ private List<ParsedAttribution> attributions = emptyList();
+
+ @NonNull
+ protected List<ParsedPermission> permissions = emptyList();
+
+ @NonNull
+ protected List<ParsedPermissionGroup> permissionGroups = emptyList();
+
+ @NonNull
+ protected List<ParsedInstrumentation> instrumentations = emptyList();
+
+ @NonNull
+ @DataClass.ParcelWith(ParsedIntentInfo.ListParceler.class)
+ private List<Pair<String, ParsedIntentInfo>> preferredActivityFilters = emptyList();
+
+ @NonNull
+ private Map<String, ParsedProcess> processes = emptyMap();
+
+ @Nullable
+ private Bundle metaData;
+
+ @NonNull
+ private Map<String, Property> mProperties = emptyMap();
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ protected String volumeUuid;
+ @Nullable
+ private PackageParser.SigningDetails signingDetails;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ protected String mPath;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ private List<Intent> queriesIntents = emptyList();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ private List<String> queriesPackages = emptyList();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringSet.class)
+ private Set<String> queriesProviders = emptySet();
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedStringArray.class)
+ private String[] splitClassLoaderNames;
+ @Nullable
+ protected String[] splitCodePaths;
+ @Nullable
+ private SparseArray<int[]> splitDependencies;
+ @Nullable
+ private int[] splitFlags;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedStringArray.class)
+ private String[] splitNames;
+ @Nullable
+ private int[] splitRevisionCodes;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String appComponentFactory;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String backupAgentName;
+ private int banner;
+ private int category;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String classLoaderName;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String className;
+ private int compatibleWidthLimitDp;
+ private int descriptionRes;
+
+ private int fullBackupContent;
+ private int dataExtractionRules;
+ private int iconRes;
+ private int installLocation = ParsingPackageUtils.PARSE_DEFAULT_INSTALL_LOCATION;
+ private int labelRes;
+ private int largestWidthLimitDp;
+ private int logo;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String manageSpaceActivityName;
+ private float maxAspectRatio;
+ private float minAspectRatio;
+ @Nullable
+ private SparseIntArray minExtensionVersions;
+ private int minSdkVersion = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
+ private int networkSecurityConfigRes;
+ @Nullable
+ private CharSequence nonLocalizedLabel;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String permission;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String processName;
+ private int requiresSmallestWidthDp;
+ private int roundIconRes;
+ private int targetSandboxVersion;
+ private int targetSdkVersion = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String taskAffinity;
+ private int theme;
+
+ private int uiOptions;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String zygotePreloadName;
+
+ /**
+ * @see ParsingPackageRead#getResizeableActivity()
+ */
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean resizeableActivity;
+
+ private int autoRevokePermissions;
+
+ @ApplicationInfo.GwpAsanMode
+ private int gwpAsanMode;
+
+ @ApplicationInfo.MemtagMode
+ private int memtagMode;
+
+ @ApplicationInfo.NativeHeapZeroInitialized
+ private int nativeHeapZeroInitialized;
+
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean requestRawExternalStorageAccess;
+
+ // TODO(chiuwinson): Non-null
+ @Nullable
+ private ArraySet<String> mimeGroups;
+
+ // Usually there's code to set enabled to true during parsing, but it's possible to install
+ // an APK targeting <R that doesn't contain an <application> tag. That code would be skipped
+ // and never assign this, so initialize this to true for those cases.
+ private long mBooleans = Booleans.ENABLED;
+
+ /**
+ * Flags used for a internal bitset. These flags should never be persisted or exposed outside
+ * of this class. It is expected that PackageCacher explicitly clears itself whenever the
+ * Parcelable implementation changes such that all these flags can be re-ordered or invalidated.
+ */
+ protected static class Booleans {
+ @LongDef({
+ EXTERNAL_STORAGE,
+ BASE_HARDWARE_ACCELERATED,
+ ALLOW_BACKUP,
+ KILL_AFTER_RESTORE,
+ RESTORE_ANY_VERSION,
+ FULL_BACKUP_ONLY,
+ PERSISTENT,
+ DEBUGGABLE,
+ VM_SAFE_MODE,
+ HAS_CODE,
+ ALLOW_TASK_REPARENTING,
+ ALLOW_CLEAR_USER_DATA,
+ LARGE_HEAP,
+ USES_CLEARTEXT_TRAFFIC,
+ SUPPORTS_RTL,
+ TEST_ONLY,
+ MULTI_ARCH,
+ EXTRACT_NATIVE_LIBS,
+ GAME,
+ STATIC_SHARED_LIBRARY,
+ OVERLAY,
+ ISOLATED_SPLIT_LOADING,
+ HAS_DOMAIN_URLS,
+ PROFILEABLE_BY_SHELL,
+ BACKUP_IN_FOREGROUND,
+ USE_EMBEDDED_DEX,
+ DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
+ DIRECT_BOOT_AWARE,
+ PARTIALLY_DIRECT_BOOT_AWARE,
+ RESIZEABLE_ACTIVITY_VIA_SDK_VERSION,
+ ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE,
+ ALLOW_AUDIO_PLAYBACK_CAPTURE,
+ REQUEST_LEGACY_EXTERNAL_STORAGE,
+ USES_NON_SDK_API,
+ HAS_FRAGILE_USER_DATA,
+ CANT_SAVE_STATE,
+ ALLOW_NATIVE_HEAP_POINTER_TAGGING,
+ PRESERVE_LEGACY_EXTERNAL_STORAGE,
+ REQUIRED_FOR_ALL_USERS,
+ OVERLAY_IS_STATIC,
+ USE_32_BIT_ABI,
+ VISIBLE_TO_INSTANT_APPS,
+ FORCE_QUERYABLE,
+ CROSS_PROFILE,
+ ENABLED,
+ DISALLOW_PROFILING,
+ REQUEST_FOREGROUND_SERVICE_EXEMPTION,
+ })
+ public @interface Values {}
+ private static final long EXTERNAL_STORAGE = 1L;
+ private static final long BASE_HARDWARE_ACCELERATED = 1L << 1;
+ private static final long ALLOW_BACKUP = 1L << 2;
+ private static final long KILL_AFTER_RESTORE = 1L << 3;
+ private static final long RESTORE_ANY_VERSION = 1L << 4;
+ private static final long FULL_BACKUP_ONLY = 1L << 5;
+ private static final long PERSISTENT = 1L << 6;
+ private static final long DEBUGGABLE = 1L << 7;
+ private static final long VM_SAFE_MODE = 1L << 8;
+ private static final long HAS_CODE = 1L << 9;
+ private static final long ALLOW_TASK_REPARENTING = 1L << 10;
+ private static final long ALLOW_CLEAR_USER_DATA = 1L << 11;
+ private static final long LARGE_HEAP = 1L << 12;
+ private static final long USES_CLEARTEXT_TRAFFIC = 1L << 13;
+ private static final long SUPPORTS_RTL = 1L << 14;
+ private static final long TEST_ONLY = 1L << 15;
+ private static final long MULTI_ARCH = 1L << 16;
+ private static final long EXTRACT_NATIVE_LIBS = 1L << 17;
+ private static final long GAME = 1L << 18;
+ private static final long STATIC_SHARED_LIBRARY = 1L << 19;
+ private static final long OVERLAY = 1L << 20;
+ private static final long ISOLATED_SPLIT_LOADING = 1L << 21;
+ private static final long HAS_DOMAIN_URLS = 1L << 22;
+ private static final long PROFILEABLE_BY_SHELL = 1L << 23;
+ private static final long BACKUP_IN_FOREGROUND = 1L << 24;
+ private static final long USE_EMBEDDED_DEX = 1L << 25;
+ private static final long DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1L << 26;
+ private static final long DIRECT_BOOT_AWARE = 1L << 27;
+ private static final long PARTIALLY_DIRECT_BOOT_AWARE = 1L << 28;
+ private static final long RESIZEABLE_ACTIVITY_VIA_SDK_VERSION = 1L << 29;
+ private static final long ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE = 1L << 30;
+ private static final long ALLOW_AUDIO_PLAYBACK_CAPTURE = 1L << 31;
+ private static final long REQUEST_LEGACY_EXTERNAL_STORAGE = 1L << 32;
+ private static final long USES_NON_SDK_API = 1L << 33;
+ private static final long HAS_FRAGILE_USER_DATA = 1L << 34;
+ private static final long CANT_SAVE_STATE = 1L << 35;
+ private static final long ALLOW_NATIVE_HEAP_POINTER_TAGGING = 1L << 36;
+ private static final long PRESERVE_LEGACY_EXTERNAL_STORAGE = 1L << 37;
+ private static final long REQUIRED_FOR_ALL_USERS = 1L << 38;
+ private static final long OVERLAY_IS_STATIC = 1L << 39;
+ private static final long USE_32_BIT_ABI = 1L << 40;
+ private static final long VISIBLE_TO_INSTANT_APPS = 1L << 41;
+ private static final long FORCE_QUERYABLE = 1L << 42;
+ private static final long CROSS_PROFILE = 1L << 43;
+ private static final long ENABLED = 1L << 44;
+ private static final long DISALLOW_PROFILING = 1L << 45;
+ private static final long REQUEST_FOREGROUND_SERVICE_EXEMPTION = 1L << 46;
+ private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47;
+ }
+
+ private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) {
+ if (value) {
+ mBooleans |= flag;
+ } else {
+ mBooleans &= ~flag;
+ }
+ return this;
+ }
+
+ private boolean getBoolean(@Booleans.Values long flag) {
+ return (mBooleans & flag) != 0;
+ }
+
+ // Derived fields
+ @NonNull
+ private UUID mStorageUuid;
+ private long mLongVersionCode;
+
+ @VisibleForTesting
+ public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseApkPath,
+ @NonNull String path, @Nullable TypedArray manifestArray) {
+ this.packageName = TextUtils.safeIntern(packageName);
+ this.mBaseApkPath = baseApkPath;
+ this.mPath = path;
+
+ if (manifestArray != null) {
+ versionCode = manifestArray.getInteger(R.styleable.AndroidManifest_versionCode, 0);
+ versionCodeMajor = manifestArray.getInteger(
+ R.styleable.AndroidManifest_versionCodeMajor, 0);
+ setBaseRevisionCode(
+ manifestArray.getInteger(R.styleable.AndroidManifest_revisionCode, 0));
+ setVersionName(manifestArray.getNonConfigurationString(
+ R.styleable.AndroidManifest_versionName, 0));
+
+ setCompileSdkVersion(manifestArray.getInteger(
+ R.styleable.AndroidManifest_compileSdkVersion, 0));
+ setCompileSdkVersionCodename(manifestArray.getNonConfigurationString(
+ R.styleable.AndroidManifest_compileSdkVersionCodename, 0));
+
+ setIsolatedSplitLoading(manifestArray.getBoolean(
+ R.styleable.AndroidManifest_isolatedSplits, false));
+
+ }
+ }
+
+ public boolean isSupportsSmallScreens() {
+ if (supportsSmallScreens == null) {
+ return targetSdkVersion >= Build.VERSION_CODES.DONUT;
+ }
+
+ return supportsSmallScreens;
+ }
+
+ public boolean isSupportsNormalScreens() {
+ return supportsNormalScreens == null || supportsNormalScreens;
+ }
+
+ public boolean isSupportsLargeScreens() {
+ if (supportsLargeScreens == null) {
+ return targetSdkVersion >= Build.VERSION_CODES.DONUT;
+ }
+
+ return supportsLargeScreens;
+ }
+
+ public boolean isSupportsExtraLargeScreens() {
+ if (supportsExtraLargeScreens == null) {
+ return targetSdkVersion >= Build.VERSION_CODES.GINGERBREAD;
+ }
+
+ return supportsExtraLargeScreens;
+ }
+
+ public boolean isResizeable() {
+ if (resizeable == null) {
+ return targetSdkVersion >= Build.VERSION_CODES.DONUT;
+ }
+
+ return resizeable;
+ }
+
+ public boolean isAnyDensity() {
+ if (anyDensity == null) {
+ return targetSdkVersion >= Build.VERSION_CODES.DONUT;
+ }
+
+ return anyDensity;
+ }
+
+ @Override
+ public ParsingPackageImpl sortActivities() {
+ Collections.sort(this.activities, ORDER_COMPARATOR);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl sortReceivers() {
+ Collections.sort(this.receivers, ORDER_COMPARATOR);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl sortServices() {
+ Collections.sort(this.services, ORDER_COMPARATOR);
+ return this;
+ }
+
+ @CallSuper
+ @Override
+ public Object hideAsParsed() {
+ assignDerivedFields();
+ return this;
+ }
+
+ private void assignDerivedFields() {
+ mStorageUuid = StorageManager.convert(volumeUuid);
+ mLongVersionCode = PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode);
+ }
+
+ @Override
+ public ParsingPackageImpl addConfigPreference(ConfigurationInfo configPreference) {
+ this.configPreferences = CollectionUtils.add(this.configPreferences, configPreference);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addReqFeature(FeatureInfo reqFeature) {
+ this.reqFeatures = CollectionUtils.add(this.reqFeatures, reqFeature);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addFeatureGroup(FeatureGroupInfo featureGroup) {
+ this.featureGroups = CollectionUtils.add(this.featureGroups, featureGroup);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addProperty(@Nullable Property property) {
+ if (property == null) {
+ return this;
+ }
+ this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addProtectedBroadcast(String protectedBroadcast) {
+ if (!this.protectedBroadcasts.contains(protectedBroadcast)) {
+ this.protectedBroadcasts = CollectionUtils.add(this.protectedBroadcasts,
+ TextUtils.safeIntern(protectedBroadcast));
+ }
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addInstrumentation(ParsedInstrumentation instrumentation) {
+ this.instrumentations = CollectionUtils.add(this.instrumentations, instrumentation);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addOriginalPackage(String originalPackage) {
+ this.originalPackages = CollectionUtils.add(this.originalPackages, originalPackage);
+ return this;
+ }
+
+ @Override
+ public ParsingPackage addOverlayable(String overlayableName, String actorName) {
+ this.overlayables = CollectionUtils.add(this.overlayables, overlayableName,
+ TextUtils.safeIntern(actorName));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addAdoptPermission(String adoptPermission) {
+ this.adoptPermissions = CollectionUtils.add(this.adoptPermissions,
+ TextUtils.safeIntern(adoptPermission));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addPermission(ParsedPermission permission) {
+ this.permissions = CollectionUtils.add(this.permissions, permission);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addPermissionGroup(ParsedPermissionGroup permissionGroup) {
+ this.permissionGroups = CollectionUtils.add(this.permissionGroups, permissionGroup);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addUsesPermission(ParsedUsesPermission permission) {
+ this.usesPermissions = CollectionUtils.add(this.usesPermissions, permission);
+
+ // Continue populating legacy data structures to avoid performance
+ // issues until all that code can be migrated
+ this.requestedPermissions = CollectionUtils.add(this.requestedPermissions, permission.name);
+
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addImplicitPermission(String permission) {
+ this.implicitPermissions = CollectionUtils.add(this.implicitPermissions,
+ TextUtils.safeIntern(permission));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addKeySet(String keySetName, PublicKey publicKey) {
+ ArraySet<PublicKey> publicKeys = keySetMapping.get(keySetName);
+ if (publicKeys == null) {
+ publicKeys = new ArraySet<>();
+ }
+ publicKeys.add(publicKey);
+ keySetMapping = CollectionUtils.add(this.keySetMapping, keySetName, publicKeys);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addActivity(ParsedActivity parsedActivity) {
+ this.activities = CollectionUtils.add(this.activities, parsedActivity);
+ addMimeGroupsFromComponent(parsedActivity);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addReceiver(ParsedActivity parsedReceiver) {
+ this.receivers = CollectionUtils.add(this.receivers, parsedReceiver);
+ addMimeGroupsFromComponent(parsedReceiver);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addService(ParsedService parsedService) {
+ this.services = CollectionUtils.add(this.services, parsedService);
+ addMimeGroupsFromComponent(parsedService);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addProvider(ParsedProvider parsedProvider) {
+ this.providers = CollectionUtils.add(this.providers, parsedProvider);
+ addMimeGroupsFromComponent(parsedProvider);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addAttribution(ParsedAttribution attribution) {
+ this.attributions = CollectionUtils.add(this.attributions, attribution);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addLibraryName(String libraryName) {
+ this.libraryNames = CollectionUtils.add(this.libraryNames,
+ TextUtils.safeIntern(libraryName));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addUsesOptionalLibrary(String libraryName) {
+ this.usesOptionalLibraries = CollectionUtils.add(this.usesOptionalLibraries,
+ TextUtils.safeIntern(libraryName));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addUsesLibrary(String libraryName) {
+ this.usesLibraries = CollectionUtils.add(this.usesLibraries,
+ TextUtils.safeIntern(libraryName));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl removeUsesOptionalLibrary(String libraryName) {
+ this.usesOptionalLibraries = CollectionUtils.remove(this.usesOptionalLibraries,
+ libraryName);
+ return this;
+ }
+
+ @Override
+ public final ParsingPackageImpl addUsesOptionalNativeLibrary(String libraryName) {
+ this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries,
+ TextUtils.safeIntern(libraryName));
+ return this;
+ }
+
+ @Override
+ public final ParsingPackageImpl addUsesNativeLibrary(String libraryName) {
+ this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries,
+ TextUtils.safeIntern(libraryName));
+ return this;
+ }
+
+
+ @Override public ParsingPackageImpl removeUsesOptionalNativeLibrary(String libraryName) {
+ this.usesOptionalNativeLibraries = CollectionUtils.remove(this.usesOptionalNativeLibraries,
+ libraryName);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addUsesStaticLibrary(String libraryName) {
+ this.usesStaticLibraries = CollectionUtils.add(this.usesStaticLibraries,
+ TextUtils.safeIntern(libraryName));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addUsesStaticLibraryVersion(long version) {
+ this.usesStaticLibrariesVersions = ArrayUtils.appendLong(this.usesStaticLibrariesVersions,
+ version, true);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addUsesStaticLibraryCertDigests(String[] certSha256Digests) {
+ this.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
+ this.usesStaticLibrariesCertDigests, certSha256Digests, true);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addPreferredActivityFilter(String className,
+ ParsedIntentInfo intentInfo) {
+ this.preferredActivityFilters = CollectionUtils.add(this.preferredActivityFilters,
+ Pair.create(className, intentInfo));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addQueriesIntent(Intent intent) {
+ this.queriesIntents = CollectionUtils.add(this.queriesIntents, intent);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addQueriesPackage(String packageName) {
+ this.queriesPackages = CollectionUtils.add(this.queriesPackages,
+ TextUtils.safeIntern(packageName));
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl addQueriesProvider(String authority) {
+ this.queriesProviders = CollectionUtils.add(this.queriesProviders, authority);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSupportsSmallScreens(int supportsSmallScreens) {
+ if (supportsSmallScreens == 1) {
+ return this;
+ }
+
+ this.supportsSmallScreens = supportsSmallScreens < 0;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSupportsNormalScreens(int supportsNormalScreens) {
+ if (supportsNormalScreens == 1) {
+ return this;
+ }
+
+ this.supportsNormalScreens = supportsNormalScreens < 0;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSupportsLargeScreens(int supportsLargeScreens) {
+ if (supportsLargeScreens == 1) {
+ return this;
+ }
+
+ this.supportsLargeScreens = supportsLargeScreens < 0;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSupportsExtraLargeScreens(int supportsExtraLargeScreens) {
+ if (supportsExtraLargeScreens == 1) {
+ return this;
+ }
+
+ this.supportsExtraLargeScreens = supportsExtraLargeScreens < 0;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setResizeable(int resizeable) {
+ if (resizeable == 1) {
+ return this;
+ }
+
+ this.resizeable = resizeable < 0;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAnyDensity(int anyDensity) {
+ if (anyDensity == 1) {
+ return this;
+ }
+
+ this.anyDensity = anyDensity < 0;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl asSplit(String[] splitNames, String[] splitCodePaths,
+ int[] splitRevisionCodes, SparseArray<int[]> splitDependencies) {
+ this.splitNames = splitNames;
+ this.splitCodePaths = splitCodePaths;
+ this.splitRevisionCodes = splitRevisionCodes;
+ this.splitDependencies = splitDependencies;
+
+ int count = splitNames.length;
+ this.splitFlags = new int[count];
+ this.splitClassLoaderNames = new String[count];
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSplitHasCode(int splitIndex, boolean splitHasCode) {
+ this.splitFlags[splitIndex] = splitHasCode
+ ? this.splitFlags[splitIndex] | ApplicationInfo.FLAG_HAS_CODE
+ : this.splitFlags[splitIndex] & ~ApplicationInfo.FLAG_HAS_CODE;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSplitClassLoaderName(int splitIndex, String classLoaderName) {
+ this.splitClassLoaderNames[splitIndex] = classLoaderName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRequiredAccountType(@Nullable String requiredAccountType) {
+ this.requiredAccountType = TextUtils.nullIfEmpty(requiredAccountType);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayTarget(@Nullable String overlayTarget) {
+ this.overlayTarget = TextUtils.safeIntern(overlayTarget);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setVolumeUuid(@Nullable String volumeUuid) {
+ this.volumeUuid = TextUtils.safeIntern(volumeUuid);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setStaticSharedLibName(String staticSharedLibName) {
+ this.staticSharedLibName = TextUtils.safeIntern(staticSharedLibName);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSharedUserId(String sharedUserId) {
+ this.sharedUserId = TextUtils.safeIntern(sharedUserId);
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setNonLocalizedLabel(@Nullable CharSequence value) {
+ nonLocalizedLabel = value == null ? null : value.toString().trim();
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public String getProcessName() {
+ return processName != null ? processName : packageName;
+ }
+
+ @Override
+ public String toString() {
+ return "Package{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ @Deprecated
+ @Override
+ public ApplicationInfo toAppInfoWithoutState() {
+ ApplicationInfo appInfo = toAppInfoWithoutStateWithoutFlags();
+ appInfo.flags = PackageInfoWithoutStateUtils.appInfoFlags(this);
+ appInfo.privateFlags = PackageInfoWithoutStateUtils.appInfoPrivateFlags(this);
+ appInfo.privateFlagsExt = PackageInfoWithoutStateUtils.appInfoPrivateFlagsExt(this);
+ return appInfo;
+ }
+
+ @Override
+ public ApplicationInfo toAppInfoWithoutStateWithoutFlags() {
+ ApplicationInfo appInfo = new ApplicationInfo();
+
+ // Lines that are commented below are state related and should not be assigned here.
+ // They are left in as placeholders, since there is no good backwards compatible way to
+ // separate these.
+ appInfo.appComponentFactory = appComponentFactory;
+ appInfo.backupAgentName = backupAgentName;
+ appInfo.banner = banner;
+ appInfo.category = category;
+ appInfo.classLoaderName = classLoaderName;
+ appInfo.className = className;
+ appInfo.compatibleWidthLimitDp = compatibleWidthLimitDp;
+ appInfo.compileSdkVersion = compileSdkVersion;
+ appInfo.compileSdkVersionCodename = compileSdkVersionCodeName;
+// appInfo.credentialProtectedDataDir
+ appInfo.crossProfile = isCrossProfile();
+// appInfo.dataDir
+ appInfo.descriptionRes = descriptionRes;
+// appInfo.deviceProtectedDataDir
+ appInfo.enabled = getBoolean(Booleans.ENABLED);
+// appInfo.enabledSetting
+ appInfo.fullBackupContent = fullBackupContent;
+ appInfo.dataExtractionRulesRes = dataExtractionRules;
+ // TODO(b/135203078): See ParsingPackageImpl#getHiddenApiEnforcementPolicy
+// appInfo.mHiddenApiPolicy
+// appInfo.hiddenUntilInstalled
+ appInfo.icon =
+ (ParsingPackageUtils.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes;
+ appInfo.iconRes = iconRes;
+ appInfo.roundIconRes = roundIconRes;
+ appInfo.installLocation = installLocation;
+ appInfo.labelRes = labelRes;
+ appInfo.largestWidthLimitDp = largestWidthLimitDp;
+ appInfo.logo = logo;
+ appInfo.manageSpaceActivityName = manageSpaceActivityName;
+ appInfo.maxAspectRatio = maxAspectRatio;
+ appInfo.metaData = metaData;
+ appInfo.minAspectRatio = minAspectRatio;
+ appInfo.minSdkVersion = minSdkVersion;
+ appInfo.name = className;
+// appInfo.nativeLibraryDir
+// appInfo.nativeLibraryRootDir
+// appInfo.nativeLibraryRootRequiresIsa
+ appInfo.networkSecurityConfigRes = networkSecurityConfigRes;
+ appInfo.nonLocalizedLabel = nonLocalizedLabel;
+ appInfo.packageName = packageName;
+ appInfo.permission = permission;
+// appInfo.primaryCpuAbi
+ appInfo.processName = getProcessName();
+ appInfo.requiresSmallestWidthDp = requiresSmallestWidthDp;
+// appInfo.resourceDirs
+// appInfo.secondaryCpuAbi
+// appInfo.secondaryNativeLibraryDir
+// appInfo.seInfo
+// appInfo.seInfoUser
+// appInfo.sharedLibraryFiles
+// appInfo.sharedLibraryInfos
+// appInfo.showUserIcon
+ appInfo.splitClassLoaderNames = splitClassLoaderNames;
+ appInfo.splitDependencies = splitDependencies;
+ appInfo.splitNames = splitNames;
+ appInfo.storageUuid = mStorageUuid;
+ appInfo.targetSandboxVersion = targetSandboxVersion;
+ appInfo.targetSdkVersion = targetSdkVersion;
+ appInfo.taskAffinity = taskAffinity;
+ appInfo.theme = theme;
+// appInfo.uid
+ appInfo.uiOptions = uiOptions;
+ appInfo.volumeUuid = volumeUuid;
+ appInfo.zygotePreloadName = zygotePreloadName;
+ appInfo.setGwpAsanMode(gwpAsanMode);
+ appInfo.setMemtagMode(memtagMode);
+ appInfo.setNativeHeapZeroInitialized(nativeHeapZeroInitialized);
+ appInfo.setRequestRawExternalStorageAccess(requestRawExternalStorageAccess);
+ appInfo.setBaseCodePath(mBaseApkPath);
+ appInfo.setBaseResourcePath(mBaseApkPath);
+ appInfo.setCodePath(mPath);
+ appInfo.setResourcePath(mPath);
+ appInfo.setSplitCodePaths(splitCodePaths);
+ appInfo.setSplitResourcePaths(splitCodePaths);
+ appInfo.setVersionCode(mLongVersionCode);
+
+ return appInfo;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ sForBoolean.parcel(this.supportsSmallScreens, dest, flags);
+ sForBoolean.parcel(this.supportsNormalScreens, dest, flags);
+ sForBoolean.parcel(this.supportsLargeScreens, dest, flags);
+ sForBoolean.parcel(this.supportsExtraLargeScreens, dest, flags);
+ sForBoolean.parcel(this.resizeable, dest, flags);
+ sForBoolean.parcel(this.anyDensity, dest, flags);
+ dest.writeInt(this.versionCode);
+ dest.writeInt(this.versionCodeMajor);
+ dest.writeInt(this.baseRevisionCode);
+ sForInternedString.parcel(this.versionName, dest, flags);
+ dest.writeInt(this.compileSdkVersion);
+ dest.writeString(this.compileSdkVersionCodeName);
+ sForInternedString.parcel(this.packageName, dest, flags);
+ dest.writeString(this.realPackage);
+ dest.writeString(this.mBaseApkPath);
+ dest.writeString(this.restrictedAccountType);
+ dest.writeString(this.requiredAccountType);
+ sForInternedString.parcel(this.overlayTarget, dest, flags);
+ dest.writeString(this.overlayTargetName);
+ dest.writeString(this.overlayCategory);
+ dest.writeInt(this.overlayPriority);
+ sForInternedStringValueMap.parcel(this.overlayables, dest, flags);
+ sForInternedString.parcel(this.staticSharedLibName, dest, flags);
+ dest.writeLong(this.staticSharedLibVersion);
+ sForInternedStringList.parcel(this.libraryNames, dest, flags);
+ sForInternedStringList.parcel(this.usesLibraries, dest, flags);
+ sForInternedStringList.parcel(this.usesOptionalLibraries, dest, flags);
+ sForInternedStringList.parcel(this.usesNativeLibraries, dest, flags);
+ sForInternedStringList.parcel(this.usesOptionalNativeLibraries, dest, flags);
+ sForInternedStringList.parcel(this.usesStaticLibraries, dest, flags);
+ dest.writeLongArray(this.usesStaticLibrariesVersions);
+
+ if (this.usesStaticLibrariesCertDigests == null) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(this.usesStaticLibrariesCertDigests.length);
+ for (int index = 0; index < this.usesStaticLibrariesCertDigests.length; index++) {
+ dest.writeStringArray(this.usesStaticLibrariesCertDigests[index]);
+ }
+ }
+
+ sForInternedString.parcel(this.sharedUserId, dest, flags);
+ dest.writeInt(this.sharedUserLabel);
+ dest.writeTypedList(this.configPreferences);
+ dest.writeTypedList(this.reqFeatures);
+ dest.writeTypedList(this.featureGroups);
+ dest.writeByteArray(this.restrictUpdateHash);
+ dest.writeStringList(this.originalPackages);
+ sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
+ sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
+ dest.writeTypedList(this.usesPermissions);
+ sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
+ sForStringSet.parcel(this.upgradeKeySets, dest, flags);
+ ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
+ sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
+ dest.writeTypedList(this.activities);
+ dest.writeTypedList(this.receivers);
+ dest.writeTypedList(this.services);
+ dest.writeTypedList(this.providers);
+ dest.writeTypedList(this.attributions);
+ dest.writeTypedList(this.permissions);
+ dest.writeTypedList(this.permissionGroups);
+ dest.writeTypedList(this.instrumentations);
+ sForIntentInfoPairs.parcel(this.preferredActivityFilters, dest, flags);
+ dest.writeMap(this.processes);
+ dest.writeBundle(this.metaData);
+ sForInternedString.parcel(this.volumeUuid, dest, flags);
+ dest.writeParcelable(this.signingDetails, flags);
+ dest.writeString(this.mPath);
+ dest.writeTypedList(this.queriesIntents, flags);
+ sForInternedStringList.parcel(this.queriesPackages, dest, flags);
+ sForInternedStringSet.parcel(this.queriesProviders, dest, flags);
+ dest.writeString(this.appComponentFactory);
+ dest.writeString(this.backupAgentName);
+ dest.writeInt(this.banner);
+ dest.writeInt(this.category);
+ dest.writeString(this.classLoaderName);
+ dest.writeString(this.className);
+ dest.writeInt(this.compatibleWidthLimitDp);
+ dest.writeInt(this.descriptionRes);
+ dest.writeInt(this.fullBackupContent);
+ dest.writeInt(this.dataExtractionRules);
+ dest.writeInt(this.iconRes);
+ dest.writeInt(this.installLocation);
+ dest.writeInt(this.labelRes);
+ dest.writeInt(this.largestWidthLimitDp);
+ dest.writeInt(this.logo);
+ dest.writeString(this.manageSpaceActivityName);
+ dest.writeFloat(this.maxAspectRatio);
+ dest.writeFloat(this.minAspectRatio);
+ dest.writeInt(this.minSdkVersion);
+ dest.writeInt(this.networkSecurityConfigRes);
+ dest.writeCharSequence(this.nonLocalizedLabel);
+ dest.writeString(this.permission);
+ dest.writeString(this.processName);
+ dest.writeInt(this.requiresSmallestWidthDp);
+ dest.writeInt(this.roundIconRes);
+ dest.writeInt(this.targetSandboxVersion);
+ dest.writeInt(this.targetSdkVersion);
+ dest.writeString(this.taskAffinity);
+ dest.writeInt(this.theme);
+ dest.writeInt(this.uiOptions);
+ dest.writeString(this.zygotePreloadName);
+ dest.writeStringArray(this.splitClassLoaderNames);
+ dest.writeStringArray(this.splitCodePaths);
+ dest.writeSparseArray(this.splitDependencies);
+ dest.writeIntArray(this.splitFlags);
+ dest.writeStringArray(this.splitNames);
+ dest.writeIntArray(this.splitRevisionCodes);
+ sForBoolean.parcel(this.resizeableActivity, dest, flags);
+ dest.writeInt(this.autoRevokePermissions);
+ dest.writeArraySet(this.mimeGroups);
+ dest.writeInt(this.gwpAsanMode);
+ dest.writeSparseIntArray(this.minExtensionVersions);
+ dest.writeLong(this.mBooleans);
+ dest.writeMap(this.mProperties);
+ dest.writeInt(this.memtagMode);
+ dest.writeInt(this.nativeHeapZeroInitialized);
+ sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags);
+ }
+
+ public ParsingPackageImpl(Parcel in) {
+ // We use the boot classloader for all classes that we load.
+ final ClassLoader boot = Object.class.getClassLoader();
+ this.supportsSmallScreens = sForBoolean.unparcel(in);
+ this.supportsNormalScreens = sForBoolean.unparcel(in);
+ this.supportsLargeScreens = sForBoolean.unparcel(in);
+ this.supportsExtraLargeScreens = sForBoolean.unparcel(in);
+ this.resizeable = sForBoolean.unparcel(in);
+ this.anyDensity = sForBoolean.unparcel(in);
+ this.versionCode = in.readInt();
+ this.versionCodeMajor = in.readInt();
+ this.baseRevisionCode = in.readInt();
+ this.versionName = sForInternedString.unparcel(in);
+ this.compileSdkVersion = in.readInt();
+ this.compileSdkVersionCodeName = in.readString();
+ this.packageName = sForInternedString.unparcel(in);
+ this.realPackage = in.readString();
+ this.mBaseApkPath = in.readString();
+ this.restrictedAccountType = in.readString();
+ this.requiredAccountType = in.readString();
+ this.overlayTarget = sForInternedString.unparcel(in);
+ this.overlayTargetName = in.readString();
+ this.overlayCategory = in.readString();
+ this.overlayPriority = in.readInt();
+ this.overlayables = sForInternedStringValueMap.unparcel(in);
+ this.staticSharedLibName = sForInternedString.unparcel(in);
+ this.staticSharedLibVersion = in.readLong();
+ this.libraryNames = sForInternedStringList.unparcel(in);
+ this.usesLibraries = sForInternedStringList.unparcel(in);
+ this.usesOptionalLibraries = sForInternedStringList.unparcel(in);
+ this.usesNativeLibraries = sForInternedStringList.unparcel(in);
+ this.usesOptionalNativeLibraries = sForInternedStringList.unparcel(in);
+ this.usesStaticLibraries = sForInternedStringList.unparcel(in);
+ this.usesStaticLibrariesVersions = in.createLongArray();
+
+ int digestsSize = in.readInt();
+ if (digestsSize >= 0) {
+ this.usesStaticLibrariesCertDigests = new String[digestsSize][];
+ for (int index = 0; index < digestsSize; index++) {
+ this.usesStaticLibrariesCertDigests[index] = sForInternedStringArray.unparcel(in);
+ }
+ }
+
+ this.sharedUserId = sForInternedString.unparcel(in);
+ this.sharedUserLabel = in.readInt();
+ this.configPreferences = in.createTypedArrayList(ConfigurationInfo.CREATOR);
+ this.reqFeatures = in.createTypedArrayList(FeatureInfo.CREATOR);
+ this.featureGroups = in.createTypedArrayList(FeatureGroupInfo.CREATOR);
+ this.restrictUpdateHash = in.createByteArray();
+ this.originalPackages = in.createStringArrayList();
+ this.adoptPermissions = sForInternedStringList.unparcel(in);
+ this.requestedPermissions = sForInternedStringList.unparcel(in);
+ this.usesPermissions = in.createTypedArrayList(ParsedUsesPermission.CREATOR);
+ this.implicitPermissions = sForInternedStringList.unparcel(in);
+ this.upgradeKeySets = sForStringSet.unparcel(in);
+ this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in);
+ this.protectedBroadcasts = sForInternedStringList.unparcel(in);
+
+ this.activities = in.createTypedArrayList(ParsedActivity.CREATOR);
+ this.receivers = in.createTypedArrayList(ParsedActivity.CREATOR);
+ this.services = in.createTypedArrayList(ParsedService.CREATOR);
+ this.providers = in.createTypedArrayList(ParsedProvider.CREATOR);
+ this.attributions = in.createTypedArrayList(ParsedAttribution.CREATOR);
+ this.permissions = in.createTypedArrayList(ParsedPermission.CREATOR);
+ this.permissionGroups = in.createTypedArrayList(ParsedPermissionGroup.CREATOR);
+ this.instrumentations = in.createTypedArrayList(ParsedInstrumentation.CREATOR);
+ this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in);
+ this.processes = in.readHashMap(boot);
+ this.metaData = in.readBundle(boot);
+ this.volumeUuid = sForInternedString.unparcel(in);
+ this.signingDetails = in.readParcelable(boot);
+ this.mPath = in.readString();
+ this.queriesIntents = in.createTypedArrayList(Intent.CREATOR);
+ this.queriesPackages = sForInternedStringList.unparcel(in);
+ this.queriesProviders = sForInternedStringSet.unparcel(in);
+ this.appComponentFactory = in.readString();
+ this.backupAgentName = in.readString();
+ this.banner = in.readInt();
+ this.category = in.readInt();
+ this.classLoaderName = in.readString();
+ this.className = in.readString();
+ this.compatibleWidthLimitDp = in.readInt();
+ this.descriptionRes = in.readInt();
+ this.fullBackupContent = in.readInt();
+ this.dataExtractionRules = in.readInt();
+ this.iconRes = in.readInt();
+ this.installLocation = in.readInt();
+ this.labelRes = in.readInt();
+ this.largestWidthLimitDp = in.readInt();
+ this.logo = in.readInt();
+ this.manageSpaceActivityName = in.readString();
+ this.maxAspectRatio = in.readFloat();
+ this.minAspectRatio = in.readFloat();
+ this.minSdkVersion = in.readInt();
+ this.networkSecurityConfigRes = in.readInt();
+ this.nonLocalizedLabel = in.readCharSequence();
+ this.permission = in.readString();
+ this.processName = in.readString();
+ this.requiresSmallestWidthDp = in.readInt();
+ this.roundIconRes = in.readInt();
+ this.targetSandboxVersion = in.readInt();
+ this.targetSdkVersion = in.readInt();
+ this.taskAffinity = in.readString();
+ this.theme = in.readInt();
+ this.uiOptions = in.readInt();
+ this.zygotePreloadName = in.readString();
+ this.splitClassLoaderNames = in.createStringArray();
+ this.splitCodePaths = in.createStringArray();
+ this.splitDependencies = in.readSparseArray(boot);
+ this.splitFlags = in.createIntArray();
+ this.splitNames = in.createStringArray();
+ this.splitRevisionCodes = in.createIntArray();
+ this.resizeableActivity = sForBoolean.unparcel(in);
+
+ this.autoRevokePermissions = in.readInt();
+ this.mimeGroups = (ArraySet<String>) in.readArraySet(boot);
+ this.gwpAsanMode = in.readInt();
+ this.minExtensionVersions = in.readSparseIntArray();
+ this.mBooleans = in.readLong();
+ this.mProperties = in.readHashMap(boot);
+ this.memtagMode = in.readInt();
+ this.nativeHeapZeroInitialized = in.readInt();
+ this.requestRawExternalStorageAccess = sForBoolean.unparcel(in);
+ assignDerivedFields();
+ }
+
+ public static final Parcelable.Creator<ParsingPackageImpl> CREATOR =
+ new Parcelable.Creator<ParsingPackageImpl>() {
+ @Override
+ public ParsingPackageImpl createFromParcel(Parcel source) {
+ return new ParsingPackageImpl(source);
+ }
+
+ @Override
+ public ParsingPackageImpl[] newArray(int size) {
+ return new ParsingPackageImpl[size];
+ }
+ };
+
+ @Override
+ public int getVersionCode() {
+ return versionCode;
+ }
+
+ @Override
+ public int getVersionCodeMajor() {
+ return versionCodeMajor;
+ }
+
+ @Override
+ public int getBaseRevisionCode() {
+ return baseRevisionCode;
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName() {
+ return versionName;
+ }
+
+ @Override
+ public int getCompileSdkVersion() {
+ return compileSdkVersion;
+ }
+
+ @Nullable
+ @Override
+ public String getCompileSdkVersionCodeName() {
+ return compileSdkVersionCodeName;
+ }
+
+ @NonNull
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
+
+ @Nullable
+ @Override
+ public String getRealPackage() {
+ return realPackage;
+ }
+
+ @NonNull
+ @Override
+ public String getBaseApkPath() {
+ return mBaseApkPath;
+ }
+
+ @Override
+ public boolean isRequiredForAllUsers() {
+ return getBoolean(Booleans.REQUIRED_FOR_ALL_USERS);
+ }
+
+ @Nullable
+ @Override
+ public String getRestrictedAccountType() {
+ return restrictedAccountType;
+ }
+
+ @Nullable
+ @Override
+ public String getRequiredAccountType() {
+ return requiredAccountType;
+ }
+
+ @Nullable
+ @Override
+ public String getOverlayTarget() {
+ return overlayTarget;
+ }
+
+ @Nullable
+ @Override
+ public String getOverlayTargetName() {
+ return overlayTargetName;
+ }
+
+ @Nullable
+ @Override
+ public String getOverlayCategory() {
+ return overlayCategory;
+ }
+
+ @Override
+ public int getOverlayPriority() {
+ return overlayPriority;
+ }
+
+ @Override
+ public boolean isOverlayIsStatic() {
+ return getBoolean(Booleans.OVERLAY_IS_STATIC);
+ }
+
+ @NonNull
+ @Override
+ public Map<String,String> getOverlayables() {
+ return overlayables;
+ }
+
+ @Nullable
+ @Override
+ public String getStaticSharedLibName() {
+ return staticSharedLibName;
+ }
+
+ @Override
+ public long getStaticSharedLibVersion() {
+ return staticSharedLibVersion;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getLibraryNames() {
+ return libraryNames;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getUsesLibraries() {
+ return usesLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getUsesOptionalLibraries() {
+ return usesOptionalLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getUsesNativeLibraries() {
+ return usesNativeLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getUsesOptionalNativeLibraries() {
+ return usesOptionalNativeLibraries;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getUsesStaticLibraries() {
+ return usesStaticLibraries;
+ }
+
+ @Nullable
+ @Override
+ public long[] getUsesStaticLibrariesVersions() {
+ return usesStaticLibrariesVersions;
+ }
+
+ @Nullable
+ @Override
+ public String[][] getUsesStaticLibrariesCertDigests() {
+ return usesStaticLibrariesCertDigests;
+ }
+
+ @Nullable
+ @Override
+ public String getSharedUserId() {
+ return sharedUserId;
+ }
+
+ @Override
+ public int getSharedUserLabel() {
+ return sharedUserLabel;
+ }
+
+ @NonNull
+ @Override
+ public List<ConfigurationInfo> getConfigPreferences() {
+ return configPreferences;
+ }
+
+ @NonNull
+ @Override
+ public List<FeatureInfo> getReqFeatures() {
+ return reqFeatures;
+ }
+
+ @NonNull
+ @Override
+ public List<FeatureGroupInfo> getFeatureGroups() {
+ return featureGroups;
+ }
+
+ @Nullable
+ @Override
+ public byte[] getRestrictUpdateHash() {
+ return restrictUpdateHash;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getOriginalPackages() {
+ return originalPackages;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getAdoptPermissions() {
+ return adoptPermissions;
+ }
+
+ /**
+ * @deprecated consider migrating to {@link #getUsesPermissions} which has
+ * more parsed details, such as flags
+ */
+ @NonNull
+ @Override
+ @Deprecated
+ public List<String> getRequestedPermissions() {
+ return requestedPermissions;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedUsesPermission> getUsesPermissions() {
+ return usesPermissions;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getImplicitPermissions() {
+ return implicitPermissions;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Property> getProperties() {
+ return mProperties;
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getUpgradeKeySets() {
+ return upgradeKeySets;
+ }
+
+ @NonNull
+ @Override
+ public Map<String,ArraySet<PublicKey>> getKeySetMapping() {
+ return keySetMapping;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getProtectedBroadcasts() {
+ return protectedBroadcasts;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedActivity> getActivities() {
+ return activities;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedActivity> getReceivers() {
+ return receivers;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedService> getServices() {
+ return services;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedProvider> getProviders() {
+ return providers;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedAttribution> getAttributions() {
+ return attributions;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedPermission> getPermissions() {
+ return permissions;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedPermissionGroup> getPermissionGroups() {
+ return permissionGroups;
+ }
+
+ @NonNull
+ @Override
+ public List<ParsedInstrumentation> getInstrumentations() {
+ return instrumentations;
+ }
+
+ @NonNull
+ @Override
+ public List<Pair<String,ParsedIntentInfo>> getPreferredActivityFilters() {
+ return preferredActivityFilters;
+ }
+
+ @NonNull
+ @Override
+ public Map<String,ParsedProcess> getProcesses() {
+ return processes;
+ }
+
+ @Nullable
+ @Override
+ public Bundle getMetaData() {
+ return metaData;
+ }
+
+ private void addMimeGroupsFromComponent(ParsedComponent component) {
+ for (int i = component.getIntents().size() - 1; i >= 0; i--) {
+ IntentFilter filter = component.getIntents().get(i);
+ for (int groupIndex = filter.countMimeGroups() - 1; groupIndex >= 0; groupIndex--) {
+ mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public Set<String> getMimeGroups() {
+ return mimeGroups;
+ }
+
+ @Nullable
+ @Override
+ public String getVolumeUuid() {
+ return volumeUuid;
+ }
+
+ @Nullable
+ @Override
+ public PackageParser.SigningDetails getSigningDetails() {
+ return signingDetails;
+ }
+
+ @NonNull
+ @Override
+ public String getPath() {
+ return mPath;
+ }
+
+ @Override
+ public boolean isUse32BitAbi() {
+ return getBoolean(Booleans.USE_32_BIT_ABI);
+ }
+
+ @Override
+ public boolean isVisibleToInstantApps() {
+ return getBoolean(Booleans.VISIBLE_TO_INSTANT_APPS);
+ }
+
+ @Override
+ public boolean isForceQueryable() {
+ return getBoolean(Booleans.FORCE_QUERYABLE);
+ }
+
+ @NonNull
+ @Override
+ public List<Intent> getQueriesIntents() {
+ return queriesIntents;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getQueriesPackages() {
+ return queriesPackages;
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getQueriesProviders() {
+ return queriesProviders;
+ }
+
+ @Nullable
+ @Override
+ public String[] getSplitClassLoaderNames() {
+ return splitClassLoaderNames;
+ }
+
+ @Nullable
+ @Override
+ public String[] getSplitCodePaths() {
+ return splitCodePaths;
+ }
+
+ @Nullable
+ @Override
+ public SparseArray<int[]> getSplitDependencies() {
+ return splitDependencies;
+ }
+
+ @Nullable
+ @Override
+ public int[] getSplitFlags() {
+ return splitFlags;
+ }
+
+ @Nullable
+ @Override
+ public String[] getSplitNames() {
+ return splitNames;
+ }
+
+ @Nullable
+ @Override
+ public int[] getSplitRevisionCodes() {
+ return splitRevisionCodes;
+ }
+
+ @Nullable
+ @Override
+ public String getAppComponentFactory() {
+ return appComponentFactory;
+ }
+
+ @Nullable
+ @Override
+ public String getBackupAgentName() {
+ return backupAgentName;
+ }
+
+ @Override
+ public int getBanner() {
+ return banner;
+ }
+
+ @Override
+ public int getCategory() {
+ return category;
+ }
+
+ @Nullable
+ @Override
+ public String getClassLoaderName() {
+ return classLoaderName;
+ }
+
+ @Nullable
+ @Override
+ public String getClassName() {
+ return className;
+ }
+
+ @Override
+ public int getCompatibleWidthLimitDp() {
+ return compatibleWidthLimitDp;
+ }
+
+ @Override
+ public int getDescriptionRes() {
+ return descriptionRes;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return getBoolean(Booleans.ENABLED);
+ }
+
+ @Override
+ public boolean isCrossProfile() {
+ return getBoolean(Booleans.CROSS_PROFILE);
+ }
+
+ @Override
+ public int getFullBackupContent() {
+ return fullBackupContent;
+ }
+
+ @Override
+ public int getDataExtractionRules() {
+ return dataExtractionRules;
+ }
+
+ @Override
+ public int getIconRes() {
+ return iconRes;
+ }
+
+ @Override
+ public int getInstallLocation() {
+ return installLocation;
+ }
+
+ @Override
+ public int getLabelRes() {
+ return labelRes;
+ }
+
+ @Override
+ public int getLargestWidthLimitDp() {
+ return largestWidthLimitDp;
+ }
+
+ @Override
+ public int getLogo() {
+ return logo;
+ }
+
+ @Nullable
+ @Override
+ public String getManageSpaceActivityName() {
+ return manageSpaceActivityName;
+ }
+
+ @Override
+ public float getMaxAspectRatio() {
+ return maxAspectRatio;
+ }
+
+ @Override
+ public float getMinAspectRatio() {
+ return minAspectRatio;
+ }
+
+ @Nullable
+ @Override
+ public SparseIntArray getMinExtensionVersions() {
+ return minExtensionVersions;
+ }
+
+ @Override
+ public int getMinSdkVersion() {
+ return minSdkVersion;
+ }
+
+ @Override
+ public int getNetworkSecurityConfigRes() {
+ return networkSecurityConfigRes;
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getNonLocalizedLabel() {
+ return nonLocalizedLabel;
+ }
+
+ @Nullable
+ @Override
+ public String getPermission() {
+ return permission;
+ }
+
+ @Override
+ public int getRequiresSmallestWidthDp() {
+ return requiresSmallestWidthDp;
+ }
+
+ @Override
+ public int getRoundIconRes() {
+ return roundIconRes;
+ }
+
+ @Override
+ public int getTargetSandboxVersion() {
+ return targetSandboxVersion;
+ }
+
+ @Override
+ public int getTargetSdkVersion() {
+ return targetSdkVersion;
+ }
+
+ @Nullable
+ @Override
+ public String getTaskAffinity() {
+ return taskAffinity;
+ }
+
+ @Override
+ public int getTheme() {
+ return theme;
+ }
+
+ @Override
+ public int getUiOptions() {
+ return uiOptions;
+ }
+
+ @Nullable
+ @Override
+ public String getZygotePreloadName() {
+ return zygotePreloadName;
+ }
+
+ @Override
+ public boolean isExternalStorage() {
+ return getBoolean(Booleans.EXTERNAL_STORAGE);
+ }
+
+ @Override
+ public boolean isBaseHardwareAccelerated() {
+ return getBoolean(Booleans.BASE_HARDWARE_ACCELERATED);
+ }
+
+ @Override
+ public boolean isAllowBackup() {
+ return getBoolean(Booleans.ALLOW_BACKUP);
+ }
+
+ @Override
+ public boolean isKillAfterRestore() {
+ return getBoolean(Booleans.KILL_AFTER_RESTORE);
+ }
+
+ @Override
+ public boolean isRestoreAnyVersion() {
+ return getBoolean(Booleans.RESTORE_ANY_VERSION);
+ }
+
+ @Override
+ public boolean isFullBackupOnly() {
+ return getBoolean(Booleans.FULL_BACKUP_ONLY);
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return getBoolean(Booleans.PERSISTENT);
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return getBoolean(Booleans.DEBUGGABLE);
+ }
+
+ @Override
+ public boolean isVmSafeMode() {
+ return getBoolean(Booleans.VM_SAFE_MODE);
+ }
+
+ @Override
+ public boolean isHasCode() {
+ return getBoolean(Booleans.HAS_CODE);
+ }
+
+ @Override
+ public boolean isAllowTaskReparenting() {
+ return getBoolean(Booleans.ALLOW_TASK_REPARENTING);
+ }
+
+ @Override
+ public boolean isAllowClearUserData() {
+ return getBoolean(Booleans.ALLOW_CLEAR_USER_DATA);
+ }
+
+ @Override
+ public boolean isLargeHeap() {
+ return getBoolean(Booleans.LARGE_HEAP);
+ }
+
+ @Override
+ public boolean isUsesCleartextTraffic() {
+ return getBoolean(Booleans.USES_CLEARTEXT_TRAFFIC);
+ }
+
+ @Override
+ public boolean isSupportsRtl() {
+ return getBoolean(Booleans.SUPPORTS_RTL);
+ }
+
+ @Override
+ public boolean isTestOnly() {
+ return getBoolean(Booleans.TEST_ONLY);
+ }
+
+ @Override
+ public boolean isMultiArch() {
+ return getBoolean(Booleans.MULTI_ARCH);
+ }
+
+ @Override
+ public boolean isExtractNativeLibs() {
+ return getBoolean(Booleans.EXTRACT_NATIVE_LIBS);
+ }
+
+ @Override
+ public boolean isGame() {
+ return getBoolean(Booleans.GAME);
+ }
+
+ /**
+ * @see ParsingPackageRead#getResizeableActivity()
+ */
+ @Nullable
+ @Override
+ public Boolean getResizeableActivity() {
+ return resizeableActivity;
+ }
+
+ @Override
+ public boolean isStaticSharedLibrary() {
+ return getBoolean(Booleans.STATIC_SHARED_LIBRARY);
+ }
+
+ @Override
+ public boolean isOverlay() {
+ return getBoolean(Booleans.OVERLAY);
+ }
+
+ @Override
+ public boolean isIsolatedSplitLoading() {
+ return getBoolean(Booleans.ISOLATED_SPLIT_LOADING);
+ }
+
+ @Override
+ public boolean isHasDomainUrls() {
+ return getBoolean(Booleans.HAS_DOMAIN_URLS);
+ }
+
+ @Override
+ public boolean isProfileableByShell() {
+ return isProfileable() && getBoolean(Booleans.PROFILEABLE_BY_SHELL);
+ }
+
+ @Override
+ public boolean isProfileable() {
+ return !getBoolean(Booleans.DISALLOW_PROFILING);
+ }
+
+ @Override
+ public boolean isBackupInForeground() {
+ return getBoolean(Booleans.BACKUP_IN_FOREGROUND);
+ }
+
+ @Override
+ public boolean isUseEmbeddedDex() {
+ return getBoolean(Booleans.USE_EMBEDDED_DEX);
+ }
+
+ @Override
+ public boolean isDefaultToDeviceProtectedStorage() {
+ return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
+ }
+
+ @Override
+ public boolean isDirectBootAware() {
+ return getBoolean(Booleans.DIRECT_BOOT_AWARE);
+ }
+
+ @ApplicationInfo.GwpAsanMode
+ @Override
+ public int getGwpAsanMode() {
+ return gwpAsanMode;
+ }
+
+ @ApplicationInfo.MemtagMode
+ @Override
+ public int getMemtagMode() {
+ return memtagMode;
+ }
+
+ @ApplicationInfo.NativeHeapZeroInitialized
+ @Override
+ public int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
+ }
+
+ @Nullable
+ @Override
+ public Boolean hasRequestRawExternalStorageAccess() {
+ return requestRawExternalStorageAccess;
+ }
+
+ @Override
+ public boolean isPartiallyDirectBootAware() {
+ return getBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE);
+ }
+
+ @Override
+ public boolean isResizeableActivityViaSdkVersion() {
+ return getBoolean(Booleans.RESIZEABLE_ACTIVITY_VIA_SDK_VERSION);
+ }
+
+ @Override
+ public boolean isAllowClearUserDataOnFailedRestore() {
+ return getBoolean(Booleans.ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE);
+ }
+
+ @Override
+ public boolean isAllowAudioPlaybackCapture() {
+ return getBoolean(Booleans.ALLOW_AUDIO_PLAYBACK_CAPTURE);
+ }
+
+ @Override
+ public boolean isRequestLegacyExternalStorage() {
+ return getBoolean(Booleans.REQUEST_LEGACY_EXTERNAL_STORAGE);
+ }
+
+ @Override
+ public boolean isUsesNonSdkApi() {
+ return getBoolean(Booleans.USES_NON_SDK_API);
+ }
+
+ @Override
+ public boolean isHasFragileUserData() {
+ return getBoolean(Booleans.HAS_FRAGILE_USER_DATA);
+ }
+
+ @Override
+ public boolean isCantSaveState() {
+ return getBoolean(Booleans.CANT_SAVE_STATE);
+ }
+
+ @Override
+ public boolean isAllowNativeHeapPointerTagging() {
+ return getBoolean(Booleans.ALLOW_NATIVE_HEAP_POINTER_TAGGING);
+ }
+
+ @Override
+ public int getAutoRevokePermissions() {
+ return autoRevokePermissions;
+ }
+
+ @Override
+ public boolean hasPreserveLegacyExternalStorage() {
+ return getBoolean(Booleans.PRESERVE_LEGACY_EXTERNAL_STORAGE);
+ }
+
+ @Override
+ public boolean hasRequestForegroundServiceExemption() {
+ return getBoolean(Booleans.REQUEST_FOREGROUND_SERVICE_EXEMPTION);
+ }
+
+ @Override
+ public boolean areAttributionsUserVisible() {
+ return getBoolean(Booleans.ATTRIBUTIONS_ARE_USER_VISIBLE);
+ }
+
+ @Override
+ public ParsingPackageImpl setBaseRevisionCode(int value) {
+ baseRevisionCode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setCompileSdkVersion(int value) {
+ compileSdkVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRequiredForAllUsers(boolean value) {
+ return setBoolean(Booleans.REQUIRED_FOR_ALL_USERS, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayPriority(int value) {
+ overlayPriority = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayIsStatic(boolean value) {
+ return setBoolean(Booleans.OVERLAY_IS_STATIC, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setStaticSharedLibVersion(long value) {
+ staticSharedLibVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSharedUserLabel(int value) {
+ sharedUserLabel = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRestrictUpdateHash(@Nullable byte... value) {
+ restrictUpdateHash = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setUpgradeKeySets(@NonNull Set<String> value) {
+ upgradeKeySets = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setProcesses(@NonNull Map<String,ParsedProcess> value) {
+ processes = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMetaData(@Nullable Bundle value) {
+ metaData = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSigningDetails(@Nullable PackageParser.SigningDetails value) {
+ signingDetails = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setUse32BitAbi(boolean value) {
+ return setBoolean(Booleans.USE_32_BIT_ABI, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setVisibleToInstantApps(boolean value) {
+ return setBoolean(Booleans.VISIBLE_TO_INSTANT_APPS, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setForceQueryable(boolean value) {
+ return setBoolean(Booleans.FORCE_QUERYABLE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setBanner(int value) {
+ banner = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setCategory(int value) {
+ category = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setCompatibleWidthLimitDp(int value) {
+ compatibleWidthLimitDp = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setDescriptionRes(int value) {
+ descriptionRes = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setEnabled(boolean value) {
+ return setBoolean(Booleans.ENABLED, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setCrossProfile(boolean value) {
+ return setBoolean(Booleans.CROSS_PROFILE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setFullBackupContent(int value) {
+ fullBackupContent = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setDataExtractionRules(int value) {
+ dataExtractionRules = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setIconRes(int value) {
+ iconRes = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setInstallLocation(int value) {
+ installLocation = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setLabelRes(int value) {
+ labelRes = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setLargestWidthLimitDp(int value) {
+ largestWidthLimitDp = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setLogo(int value) {
+ logo = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMaxAspectRatio(float value) {
+ maxAspectRatio = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMinAspectRatio(float value) {
+ minAspectRatio = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMinExtensionVersions(@Nullable SparseIntArray value) {
+ minExtensionVersions = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMinSdkVersion(int value) {
+ minSdkVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setNetworkSecurityConfigRes(int value) {
+ networkSecurityConfigRes = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRequiresSmallestWidthDp(int value) {
+ requiresSmallestWidthDp = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRoundIconRes(int value) {
+ roundIconRes = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setTargetSandboxVersion(int value) {
+ targetSandboxVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setTargetSdkVersion(int value) {
+ targetSdkVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setTheme(int value) {
+ theme = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRequestForegroundServiceExemption(boolean value) {
+ return setBoolean(Booleans.REQUEST_FOREGROUND_SERVICE_EXEMPTION, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setUiOptions(int value) {
+ uiOptions = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setExternalStorage(boolean value) {
+ return setBoolean(Booleans.EXTERNAL_STORAGE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setBaseHardwareAccelerated(boolean value) {
+ return setBoolean(Booleans.BASE_HARDWARE_ACCELERATED, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowBackup(boolean value) {
+ return setBoolean(Booleans.ALLOW_BACKUP, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setKillAfterRestore(boolean value) {
+ return setBoolean(Booleans.KILL_AFTER_RESTORE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setRestoreAnyVersion(boolean value) {
+ return setBoolean(Booleans.RESTORE_ANY_VERSION, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setFullBackupOnly(boolean value) {
+ return setBoolean(Booleans.FULL_BACKUP_ONLY, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setPersistent(boolean value) {
+ return setBoolean(Booleans.PERSISTENT, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setDebuggable(boolean value) {
+ return setBoolean(Booleans.DEBUGGABLE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setVmSafeMode(boolean value) {
+ return setBoolean(Booleans.VM_SAFE_MODE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setHasCode(boolean value) {
+ return setBoolean(Booleans.HAS_CODE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowTaskReparenting(boolean value) {
+ return setBoolean(Booleans.ALLOW_TASK_REPARENTING, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowClearUserData(boolean value) {
+ return setBoolean(Booleans.ALLOW_CLEAR_USER_DATA, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setLargeHeap(boolean value) {
+ return setBoolean(Booleans.LARGE_HEAP, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setUsesCleartextTraffic(boolean value) {
+ return setBoolean(Booleans.USES_CLEARTEXT_TRAFFIC, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setSupportsRtl(boolean value) {
+ return setBoolean(Booleans.SUPPORTS_RTL, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setTestOnly(boolean value) {
+ return setBoolean(Booleans.TEST_ONLY, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setMultiArch(boolean value) {
+ return setBoolean(Booleans.MULTI_ARCH, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setExtractNativeLibs(boolean value) {
+ return setBoolean(Booleans.EXTRACT_NATIVE_LIBS, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setGame(boolean value) {
+ return setBoolean(Booleans.GAME, value);
+ }
+
+ /**
+ * @see ParsingPackageRead#getResizeableActivity()
+ */
+ @Override
+ public ParsingPackageImpl setResizeableActivity(@Nullable Boolean value) {
+ resizeableActivity = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setStaticSharedLibrary(boolean value) {
+ return setBoolean(Booleans.STATIC_SHARED_LIBRARY, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlay(boolean value) {
+ return setBoolean(Booleans.OVERLAY, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setIsolatedSplitLoading(boolean value) {
+ return setBoolean(Booleans.ISOLATED_SPLIT_LOADING, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setHasDomainUrls(boolean value) {
+ return setBoolean(Booleans.HAS_DOMAIN_URLS, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setProfileableByShell(boolean value) {
+ return setBoolean(Booleans.PROFILEABLE_BY_SHELL, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setProfileable(boolean value) {
+ return setBoolean(Booleans.DISALLOW_PROFILING, !value);
+ }
+
+ @Override
+ public ParsingPackageImpl setBackupInForeground(boolean value) {
+ return setBoolean(Booleans.BACKUP_IN_FOREGROUND, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setUseEmbeddedDex(boolean value) {
+ return setBoolean(Booleans.USE_EMBEDDED_DEX, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setDefaultToDeviceProtectedStorage(boolean value) {
+ return setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setDirectBootAware(boolean value) {
+ return setBoolean(Booleans.DIRECT_BOOT_AWARE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setGwpAsanMode(@ApplicationInfo.GwpAsanMode int value) {
+ gwpAsanMode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMemtagMode(@ApplicationInfo.MemtagMode int value) {
+ memtagMode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setNativeHeapZeroInitialized(
+ @ApplicationInfo.NativeHeapZeroInitialized int value) {
+ nativeHeapZeroInitialized = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRequestRawExternalStorageAccess(@Nullable Boolean value) {
+ requestRawExternalStorageAccess = value;
+ return this;
+ }
+ @Override
+ public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
+ return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setResizeableActivityViaSdkVersion(boolean value) {
+ return setBoolean(Booleans.RESIZEABLE_ACTIVITY_VIA_SDK_VERSION, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowClearUserDataOnFailedRestore(boolean value) {
+ return setBoolean(Booleans.ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowAudioPlaybackCapture(boolean value) {
+ return setBoolean(Booleans.ALLOW_AUDIO_PLAYBACK_CAPTURE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setRequestLegacyExternalStorage(boolean value) {
+ return setBoolean(Booleans.REQUEST_LEGACY_EXTERNAL_STORAGE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setUsesNonSdkApi(boolean value) {
+ return setBoolean(Booleans.USES_NON_SDK_API, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setHasFragileUserData(boolean value) {
+ return setBoolean(Booleans.HAS_FRAGILE_USER_DATA, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setCantSaveState(boolean value) {
+ return setBoolean(Booleans.CANT_SAVE_STATE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowNativeHeapPointerTagging(boolean value) {
+ return setBoolean(Booleans.ALLOW_NATIVE_HEAP_POINTER_TAGGING, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setAutoRevokePermissions(int value) {
+ autoRevokePermissions = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setPreserveLegacyExternalStorage(boolean value) {
+ return setBoolean(Booleans.PRESERVE_LEGACY_EXTERNAL_STORAGE, value);
+ }
+
+ @Override
+ public ParsingPackageImpl setVersionName(String versionName) {
+ this.versionName = versionName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackage setCompileSdkVersionCodename(String compileSdkVersionCodename) {
+ this.compileSdkVersionCodeName = compileSdkVersionCodename;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setProcessName(String processName) {
+ this.processName = processName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRealPackage(@Nullable String realPackage) {
+ this.realPackage = realPackage;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRestrictedAccountType(@Nullable String restrictedAccountType) {
+ this.restrictedAccountType = restrictedAccountType;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayTargetName(@Nullable String overlayTargetName) {
+ this.overlayTargetName = overlayTargetName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayCategory(@Nullable String overlayCategory) {
+ this.overlayCategory = overlayCategory;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAppComponentFactory(@Nullable String appComponentFactory) {
+ this.appComponentFactory = appComponentFactory;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setBackupAgentName(@Nullable String backupAgentName) {
+ this.backupAgentName = backupAgentName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setClassLoaderName(@Nullable String classLoaderName) {
+ this.classLoaderName = classLoaderName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setClassName(@Nullable String className) {
+ this.className = className == null ? null : className.trim();
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setManageSpaceActivityName(@Nullable String manageSpaceActivityName) {
+ this.manageSpaceActivityName = manageSpaceActivityName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setPermission(@Nullable String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setTaskAffinity(@Nullable String taskAffinity) {
+ this.taskAffinity = taskAffinity;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setZygotePreloadName(@Nullable String zygotePreloadName) {
+ this.zygotePreloadName = zygotePreloadName;
+ return this;
+ }
+
+ @Override
+ public ParsingPackage setAttributionsAreUserVisible(boolean attributionsAreUserVisible) {
+ setBoolean(Booleans.ATTRIBUTIONS_ARE_USER_VISIBLE, attributionsAreUserVisible);
+ return this;
+ }
+}
diff --git a/android/content/pm/parsing/ParsingPackageRead.java b/android/content/pm/parsing/ParsingPackageRead.java
new file mode 100644
index 0000000..a6e189d
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackageRead.java
@@ -0,0 +1,930 @@
+/*
+ * Copyright (C) 2020 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.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.PackageParser;
+import android.content.pm.ServiceInfo;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedAttribution;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import java.security.PublicKey;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Everything written by {@link ParsingPackage} and readable back.
+ *
+ * @hide
+ */
+@SuppressWarnings("UnusedReturnValue")
+public interface ParsingPackageRead extends Parcelable {
+
+ /**
+ * @see ActivityInfo
+ * @see PackageInfo#activities
+ */
+ @NonNull
+ List<ParsedActivity> getActivities();
+
+ /**
+ * The names of packages to adopt ownership of permissions from, parsed under
+ * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
+ * @see R.styleable#AndroidManifestOriginalPackage_name
+ */
+ @NonNull
+ List<String> getAdoptPermissions();
+
+ /**
+ * @see PackageInfo#configPreferences
+ * @see R.styleable#AndroidManifestUsesConfiguration
+ */
+ @NonNull
+ List<ConfigurationInfo> getConfigPreferences();
+
+ @NonNull
+ List<ParsedAttribution> getAttributions();
+
+ /**
+ * @see PackageInfo#featureGroups
+ * @see R.styleable#AndroidManifestUsesFeature
+ */
+ @NonNull
+ List<FeatureGroupInfo> getFeatureGroups();
+
+ /**
+ * Permissions requested but not in the manifest. These may have been split or migrated from
+ * previous versions/definitions.
+ */
+ @NonNull
+ List<String> getImplicitPermissions();
+
+ /**
+ * @see android.content.pm.InstrumentationInfo
+ * @see PackageInfo#instrumentation
+ */
+ @NonNull
+ List<ParsedInstrumentation> getInstrumentations();
+
+ /**
+ * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
+ * {@link ParsingPackageUtils#TAG_KEY_SETS}.
+ * @see R.styleable#AndroidManifestKeySet
+ * @see R.styleable#AndroidManifestPublicKey
+ */
+ @NonNull
+ Map<String, ArraySet<PublicKey>> getKeySetMapping();
+
+ /**
+ * Library names this package is declared as, for use by other packages with "uses-library".
+ * @see R.styleable#AndroidManifestLibrary
+ */
+ @NonNull
+ List<String> getLibraryNames();
+
+ /**
+ * For system use to migrate from an old package name to a new one, moving over data
+ * if available.
+ * @see R.styleable#AndroidManifestOriginalPackage}
+ */
+ @NonNull
+ List<String> getOriginalPackages();
+
+ /**
+ * Map of overlayable name to actor name.
+ */
+ @NonNull
+ Map<String, String> getOverlayables();
+
+ /**
+ * @see android.content.pm.PermissionInfo
+ * @see PackageInfo#permissions
+ */
+ @NonNull
+ List<ParsedPermission> getPermissions();
+
+ /**
+ * @see android.content.pm.PermissionGroupInfo
+ */
+ @NonNull
+ List<ParsedPermissionGroup> getPermissionGroups();
+
+ /**
+ * Used to determine the default preferred handler of an {@link Intent}.
+ *
+ * Map of component className to intent info inside that component.
+ * TODO(b/135203078): Is this actually used/working?
+ */
+ @NonNull
+ List<Pair<String, ParsedIntentInfo>> getPreferredActivityFilters();
+
+ /**
+ * System protected broadcasts.
+ * @see R.styleable#AndroidManifestProtectedBroadcast
+ */
+ @NonNull
+ List<String> getProtectedBroadcasts();
+
+ /**
+ * @see android.content.pm.ProviderInfo
+ * @see PackageInfo#providers
+ */
+ @NonNull
+ List<ParsedProvider> getProviders();
+
+ /**
+ * @see android.content.pm.ProcessInfo
+ */
+ @NonNull
+ Map<String, ParsedProcess> getProcesses();
+
+ /**
+ * Since they share several attributes, receivers are parsed as {@link ParsedActivity}, even
+ * though they represent different functionality.
+ * TODO(b/135203078): Reconsider this and maybe make ParsedReceiver so it's not so confusing
+ * @see ActivityInfo
+ * @see PackageInfo#receivers
+ */
+ @NonNull
+ List<ParsedActivity> getReceivers();
+
+ /**
+ * @see PackageInfo#reqFeatures
+ * @see R.styleable#AndroidManifestUsesFeature
+ */
+ @NonNull
+ List<FeatureInfo> getReqFeatures();
+
+ /**
+ * @deprecated consider migrating to {@link #getUsesPermissions} which has
+ * more parsed details, such as flags
+ */
+ @NonNull
+ @Deprecated
+ List<String> getRequestedPermissions();
+
+ /**
+ * All the permissions declared. This is an effective set, and may include permissions
+ * transformed from split/migrated permissions from previous versions, so may not be exactly
+ * what the package declares in its manifest.
+ * @see PackageInfo#requestedPermissions
+ * @see R.styleable#AndroidManifestUsesPermission
+ */
+ @NonNull
+ List<ParsedUsesPermission> getUsesPermissions();
+
+ /**
+ * Returns the properties set on the application
+ */
+ @NonNull
+ Map<String, Property> getProperties();
+
+ /**
+ * Whether or not the app requested explicitly resizeable Activities.
+ * A null value means nothing was explicitly requested.
+ */
+ @Nullable
+ Boolean getResizeableActivity();
+
+ /**
+ * @see ServiceInfo
+ * @see PackageInfo#services
+ */
+ @NonNull
+ List<ParsedService> getServices();
+
+ /** @see R.styleable#AndroidManifestUsesLibrary */
+ @NonNull
+ List<String> getUsesLibraries();
+
+ /**
+ * Like {@link #getUsesLibraries()}, but marked optional by setting
+ * {@link R.styleable#AndroidManifestUsesLibrary_required} to false . Application is expected
+ * to handle absence manually.
+ * @see R.styleable#AndroidManifestUsesLibrary
+ */
+ @NonNull
+ List<String> getUsesOptionalLibraries();
+
+ /** @see R.styleabele#AndroidManifestUsesNativeLibrary */
+ @NonNull
+ List<String> getUsesNativeLibraries();
+
+ /**
+ * Like {@link #getUsesNativeLibraries()}, but marked optional by setting
+ * {@link R.styleable#AndroidManifestUsesNativeLibrary_required} to false . Application is
+ * expected to handle absence manually.
+ * @see R.styleable#AndroidManifestUsesNativeLibrary
+ */
+ @NonNull
+ List<String> getUsesOptionalNativeLibraries();
+
+ /**
+ * TODO(b/135203078): Move static library stuff to an inner data class
+ * @see R.styleable#AndroidManifestUsesStaticLibrary
+ */
+ @NonNull
+ List<String> getUsesStaticLibraries();
+
+ /** @see R.styleable#AndroidManifestUsesStaticLibrary_certDigest */
+ @Nullable
+ String[][] getUsesStaticLibrariesCertDigests();
+
+ /** @see R.styleable#AndroidManifestUsesStaticLibrary_version */
+ @Nullable
+ long[] getUsesStaticLibrariesVersions();
+
+ /**
+ * Intents that this package may query or require and thus requires visibility into.
+ * @see R.styleable#AndroidManifestQueriesIntent
+ */
+ @NonNull
+ List<Intent> getQueriesIntents();
+
+ /**
+ * Other packages that this package may query or require and thus requires visibility into.
+ * @see R.styleable#AndroidManifestQueriesPackage
+ */
+ @NonNull
+ List<String> getQueriesPackages();
+
+ /**
+ * Authorities that this package may query or require and thus requires visibility into.
+ * @see R.styleable#AndroidManifestQueriesProvider
+ */
+ @NonNull
+ Set<String> getQueriesProviders();
+
+ /**
+ * We store the application meta-data independently to avoid multiple unwanted references
+ * TODO(b/135203078): What does this comment mean?
+ * TODO(b/135203078): Make all the Bundles immutable (and non-null by shared empty reference?)
+ */
+ @Nullable
+ Bundle getMetaData();
+
+ /** @see R.styleable#AndroidManifestApplication_forceQueryable */
+ boolean isForceQueryable();
+
+ /**
+ * @see ApplicationInfo#maxAspectRatio
+ * @see R.styleable#AndroidManifestApplication_maxAspectRatio
+ */
+ float getMaxAspectRatio();
+
+ /**
+ * @see ApplicationInfo#minAspectRatio
+ * @see R.styleable#AndroidManifestApplication_minAspectRatio
+ */
+ float getMinAspectRatio();
+
+ /**
+ * @see ApplicationInfo#permission
+ * @see R.styleable#AndroidManifestApplication_permission
+ */
+ @Nullable
+ String getPermission();
+
+ /**
+ * @see ApplicationInfo#processName
+ * @see R.styleable#AndroidManifestApplication_process
+ */
+ @NonNull
+ String getProcessName();
+
+ /**
+ * @see PackageInfo#sharedUserId
+ * @see R.styleable#AndroidManifest_sharedUserId
+ */
+ @Deprecated
+ @Nullable
+ String getSharedUserId();
+
+ /** @see R.styleable#AndroidManifestStaticLibrary_name */
+ @Nullable
+ String getStaticSharedLibName();
+
+ /**
+ * @see ApplicationInfo#taskAffinity
+ * @see R.styleable#AndroidManifestApplication_taskAffinity
+ */
+ @Nullable
+ String getTaskAffinity();
+
+ /**
+ * @see ApplicationInfo#targetSdkVersion
+ * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+ */
+ int getTargetSdkVersion();
+
+ /**
+ * @see ApplicationInfo#uiOptions
+ * @see R.styleable#AndroidManifestApplication_uiOptions
+ */
+ int getUiOptions();
+
+ boolean isCrossProfile();
+
+ boolean isResizeableActivityViaSdkVersion();
+
+ /** @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED */
+ boolean isBaseHardwareAccelerated();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >=
+ * {@link android.os.Build.VERSION_CODES#DONUT}.
+ * @see R.styleable#AndroidManifestSupportsScreens_resizeable
+ * @see ApplicationInfo#FLAG_RESIZEABLE_FOR_SCREENS
+ */
+ boolean isResizeable();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE */
+ boolean isAllowAudioPlaybackCapture();
+
+ /** @see ApplicationInfo#FLAG_ALLOW_BACKUP */
+ boolean isAllowBackup();
+
+ /** @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA */
+ boolean isAllowClearUserData();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE */
+ boolean isAllowClearUserDataOnFailedRestore();
+
+ /** @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING */
+ boolean isAllowTaskReparenting();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_IS_RESOURCE_OVERLAY
+ * @see ApplicationInfo#isResourceOverlay()
+ */
+ boolean isOverlay();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND */
+ boolean isBackupInForeground();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE */
+ boolean isCantSaveState();
+
+ /** @see ApplicationInfo#FLAG_DEBUGGABLE */
+ boolean isDebuggable();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE */
+ boolean isDefaultToDeviceProtectedStorage();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE */
+ boolean isDirectBootAware();
+
+ /** @see ApplicationInfo#FLAG_EXTERNAL_STORAGE */
+ boolean isExternalStorage();
+
+ /** @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS */
+ boolean isExtractNativeLibs();
+
+ /** @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY */
+ boolean isFullBackupOnly();
+
+ /** @see ApplicationInfo#FLAG_HAS_CODE */
+ boolean isHasCode();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA */
+ boolean isHasFragileUserData();
+
+ /** @see ApplicationInfo#FLAG_IS_GAME */
+ @Deprecated
+ boolean isGame();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING */
+ boolean isIsolatedSplitLoading();
+
+ /** @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE */
+ boolean isKillAfterRestore();
+
+ /** @see ApplicationInfo#FLAG_LARGE_HEAP */
+ boolean isLargeHeap();
+
+ /** @see ApplicationInfo#FLAG_MULTIARCH */
+ boolean isMultiArch();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE */
+ boolean isPartiallyDirectBootAware();
+
+ /** @see ApplicationInfo#FLAG_PERSISTENT */
+ boolean isPersistent();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL */
+ boolean isProfileableByShell();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE */
+ boolean isProfileable();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE */
+ boolean isRequestLegacyExternalStorage();
+
+ /** @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION */
+ boolean isRestoreAnyVersion();
+
+ // ParsingPackageRead setSplitHasCode(int splitIndex, boolean splitHasCode);
+
+ /** Flags of any split APKs; ordered by parsed splitName */
+ @Nullable
+ int[] getSplitFlags();
+
+ /** @see ApplicationInfo#splitSourceDirs */
+ @Nullable
+ String[] getSplitCodePaths();
+
+ /** @see ApplicationInfo#splitDependencies */
+ @Nullable
+ SparseArray<int[]> getSplitDependencies();
+
+ /**
+ * @see ApplicationInfo#splitNames
+ * @see PackageInfo#splitNames
+ */
+ @Nullable
+ String[] getSplitNames();
+
+ /** @see PackageInfo#splitRevisionCodes */
+ int[] getSplitRevisionCodes();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_STATIC_SHARED_LIBRARY */
+ boolean isStaticSharedLibrary();
+
+ /** @see ApplicationInfo#FLAG_SUPPORTS_RTL */
+ boolean isSupportsRtl();
+
+ /** @see ApplicationInfo#FLAG_TEST_ONLY */
+ boolean isTestOnly();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX */
+ boolean isUseEmbeddedDex();
+
+ /** @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC */
+ boolean isUsesCleartextTraffic();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_USES_NON_SDK_API */
+ boolean isUsesNonSdkApi();
+
+ /**
+ * Set if the any of components are visible to instant applications.
+ * @see R.styleable#AndroidManifestActivity_visibleToInstantApps
+ * @see R.styleable#AndroidManifestProvider_visibleToInstantApps
+ * @see R.styleable#AndroidManifestService_visibleToInstantApps
+ */
+ boolean isVisibleToInstantApps();
+
+ /** @see ApplicationInfo#FLAG_VM_SAFE_MODE */
+ boolean isVmSafeMode();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >=
+ * {@link android.os.Build.VERSION_CODES#DONUT}.
+ * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
+ * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
+ */
+ boolean isAnyDensity();
+
+ /**
+ * @see ApplicationInfo#appComponentFactory
+ * @see R.styleable#AndroidManifestApplication_appComponentFactory
+ */
+ @Nullable
+ String getAppComponentFactory();
+
+ /**
+ * @see ApplicationInfo#backupAgentName
+ * @see R.styleable#AndroidManifestApplication_backupAgent
+ */
+ @Nullable
+ String getBackupAgentName();
+
+ /**
+ * @see ApplicationInfo#banner
+ * @see R.styleable#AndroidManifestApplication_banner
+ */
+ int getBanner();
+
+ /**
+ * @see ApplicationInfo#category
+ * @see R.styleable#AndroidManifestApplication_appCategory
+ */
+ int getCategory();
+
+ /**
+ * @see ApplicationInfo#classLoaderName
+ * @see R.styleable#AndroidManifestApplication_classLoader
+ */
+ @Nullable
+ String getClassLoaderName();
+
+ /**
+ * @see ApplicationInfo#className
+ * @see R.styleable#AndroidManifestApplication_name
+ */
+ @Nullable
+ String getClassName();
+
+ String getPackageName();
+
+ /** Path of base APK */
+ String getBaseApkPath();
+
+ /**
+ * Path where this package was found on disk. For monolithic packages
+ * this is path to single base APK file; for cluster packages this is
+ * path to the cluster directory.
+ */
+ @NonNull
+ String getPath();
+
+ /**
+ * @see ApplicationInfo#compatibleWidthLimitDp
+ * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
+ */
+ int getCompatibleWidthLimitDp();
+
+ /**
+ * @see ApplicationInfo#descriptionRes
+ * @see R.styleable#AndroidManifestApplication_description
+ */
+ int getDescriptionRes();
+
+ /**
+ * @see ApplicationInfo#enabled
+ * @see R.styleable#AndroidManifestApplication_enabled
+ */
+ boolean isEnabled();
+
+ /**
+ * @see ApplicationInfo#fullBackupContent
+ * @see R.styleable#AndroidManifestApplication_fullBackupContent
+ */
+ int getFullBackupContent();
+
+ /**
+ * @see R.styleable#AndroidManifestApplication_dataExtractionRules
+ */
+ int getDataExtractionRules();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS */
+ boolean isHasDomainUrls();
+
+ /**
+ * @see ApplicationInfo#iconRes
+ * @see R.styleable#AndroidManifestApplication_icon
+ */
+ int getIconRes();
+
+ /**
+ * @see ApplicationInfo#installLocation
+ * @see R.styleable#AndroidManifest_installLocation
+ */
+ int getInstallLocation();
+
+ /**
+ * @see ApplicationInfo#labelRes
+ * @see R.styleable#AndroidManifestApplication_label
+ */
+ int getLabelRes();
+
+ /**
+ * @see ApplicationInfo#largestWidthLimitDp
+ * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
+ */
+ int getLargestWidthLimitDp();
+
+ /**
+ * @see ApplicationInfo#logo
+ * @see R.styleable#AndroidManifestApplication_logo
+ */
+ int getLogo();
+
+ /**
+ * @see ApplicationInfo#manageSpaceActivityName
+ * @see R.styleable#AndroidManifestApplication_manageSpaceActivity
+ */
+ @Nullable
+ String getManageSpaceActivityName();
+
+ /**
+ * @see ApplicationInfo#minExtensionVersions
+ * @see R.styleable#AndroidManifestExtensionSdk
+ */
+ @Nullable
+ SparseIntArray getMinExtensionVersions();
+
+ /**
+ * @see ApplicationInfo#minSdkVersion
+ * @see R.styleable#AndroidManifestUsesSdk_minSdkVersion
+ */
+ int getMinSdkVersion();
+
+ /**
+ * @see ApplicationInfo#networkSecurityConfigRes
+ * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
+ */
+ int getNetworkSecurityConfigRes();
+
+ /**
+ * If {@link R.styleable#AndroidManifestApplication_label} is a string literal, this is it.
+ * Otherwise, it's stored as {@link #getLabelRes()}.
+ * @see ApplicationInfo#nonLocalizedLabel
+ * @see R.styleable#AndroidManifestApplication_label
+ */
+ @Nullable
+ CharSequence getNonLocalizedLabel();
+
+ /**
+ * @see PackageInfo#overlayCategory
+ * @see R.styleable#AndroidManifestResourceOverlay_category
+ */
+ @Nullable
+ String getOverlayCategory();
+
+ /** @see PackageInfo#mOverlayIsStatic */
+ boolean isOverlayIsStatic();
+
+ /**
+ * @see PackageInfo#overlayPriority
+ * @see R.styleable#AndroidManifestResourceOverlay_priority
+ */
+ int getOverlayPriority();
+
+ /**
+ * @see PackageInfo#overlayTarget
+ * @see R.styleable#AndroidManifestResourceOverlay_targetPackage
+ */
+ @Nullable
+ String getOverlayTarget();
+
+ /**
+ * @see PackageInfo#targetOverlayableName
+ * @see R.styleable#AndroidManifestResourceOverlay_targetName
+ */
+ @Nullable
+ String getOverlayTargetName();
+
+ /**
+ * If a system app declares {@link #getOriginalPackages()}, and the app was previously installed
+ * under one of those original package names, the {@link #getPackageName()} system identifier
+ * will be changed to that previously installed name. This will then be non-null, set to the
+ * manifest package name, for tracking the package under its true name.
+ *
+ * TODO(b/135203078): Remove this in favor of checking originalPackages.isEmpty and
+ * getManifestPackageName
+ */
+ @Nullable
+ String getRealPackage();
+
+ /**
+ * The required account type without which this application will not function.
+ *
+ * @see PackageInfo#requiredAccountType
+ * @see R.styleable#AndroidManifestApplication_requiredAccountType
+ */
+ @Nullable
+ String getRequiredAccountType();
+
+ /**
+ * @see PackageInfo#requiredForAllUsers
+ * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
+ */
+ boolean isRequiredForAllUsers();
+
+ /**
+ * @see ApplicationInfo#requiresSmallestWidthDp
+ * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
+ */
+ int getRequiresSmallestWidthDp();
+
+ /**
+ * SHA-512 hash of the only APK that can be used to update a system package.
+ * @see R.styleable#AndroidManifestRestrictUpdate
+ */
+ @Nullable
+ byte[] getRestrictUpdateHash();
+
+ /**
+ * The restricted account authenticator type that is used by this application
+ *
+ * @see PackageInfo#restrictedAccountType
+ * @see R.styleable#AndroidManifestApplication_restrictedAccountType
+ */
+ @Nullable
+ String getRestrictedAccountType();
+
+ /**
+ * @see ApplicationInfo#roundIconRes
+ * @see R.styleable#AndroidManifestApplication_roundIcon
+ */
+ int getRoundIconRes();
+
+ /**
+ * @see PackageInfo#sharedUserLabel
+ * @see R.styleable#AndroidManifest_sharedUserLabel
+ */
+ @Deprecated
+ int getSharedUserLabel();
+
+ /**
+ * The signature data of all APKs in this package, which must be exactly the same across the
+ * base and splits.
+ */
+ PackageParser.SigningDetails getSigningDetails();
+
+ /**
+ * @see ApplicationInfo#splitClassLoaderNames
+ * @see R.styleable#AndroidManifestApplication_classLoader
+ */
+ @Nullable
+ String[] getSplitClassLoaderNames();
+
+ /** @see R.styleable#AndroidManifestStaticLibrary_version */
+ long getStaticSharedLibVersion();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >=
+ * {@link android.os.Build.VERSION_CODES#DONUT}.
+ * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
+ */
+ boolean isSupportsLargeScreens();
+
+ /**
+ * If omitted from manifest, returns true.
+ * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
+ */
+ boolean isSupportsNormalScreens();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >=
+ * {@link android.os.Build.VERSION_CODES#DONUT}.
+ * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
+ */
+ boolean isSupportsSmallScreens();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >=
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD}.
+ * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
+ */
+ boolean isSupportsExtraLargeScreens();
+
+ /** @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING */
+ boolean isAllowNativeHeapPointerTagging();
+
+ int getAutoRevokePermissions();
+
+ boolean hasPreserveLegacyExternalStorage();
+
+ /**
+ * @see ApplicationInfo#targetSandboxVersion
+ * @see R.styleable#AndroidManifest_targetSandboxVersion
+ */
+ @Deprecated
+ int getTargetSandboxVersion();
+
+ /**
+ * @see ApplicationInfo#theme
+ * @see R.styleable#AndroidManifestApplication_theme
+ */
+ int getTheme();
+
+ /**
+ * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
+ * {@link ParsingPackageUtils#TAG_KEY_SETS}.
+ * @see R.styleable#AndroidManifestUpgradeKeySet
+ */
+ @NonNull
+ Set<String> getUpgradeKeySets();
+
+ /**
+ * The install time abi override to choose 32bit abi's when multiple abi's
+ * are present. This is only meaningfull for multiarch applications.
+ * The use32bitAbi attribute is ignored if cpuAbiOverride is also set.
+ */
+ boolean isUse32BitAbi();
+
+ /** @see ApplicationInfo#volumeUuid */
+ @Nullable
+ String getVolumeUuid();
+
+ /** @see ApplicationInfo#zygotePreloadName */
+ @Nullable
+ String getZygotePreloadName();
+
+ /** Revision code of base APK */
+ int getBaseRevisionCode();
+
+ /** @see PackageInfo#versionName */
+ @Nullable
+ String getVersionName();
+
+ /** @see PackageInfo#versionCodeMajor */
+ @Nullable
+ int getVersionCode();
+
+ /** @see PackageInfo#versionCodeMajor */
+ @Nullable
+ int getVersionCodeMajor();
+
+ /**
+ * @see ApplicationInfo#compileSdkVersion
+ * @see R.styleable#AndroidManifest_compileSdkVersion
+ */
+ int getCompileSdkVersion();
+
+ /**
+ * @see ApplicationInfo#compileSdkVersionCodename
+ * @see R.styleable#AndroidManifest_compileSdkVersionCodename
+ */
+ @Nullable
+ String getCompileSdkVersionCodeName();
+
+ @Nullable
+ Set<String> getMimeGroups();
+
+ /**
+ * @see ApplicationInfo#gwpAsanMode
+ * @see R.styleable#AndroidManifest_gwpAsanMode
+ */
+ @ApplicationInfo.GwpAsanMode
+ int getGwpAsanMode();
+
+ /**
+ * @see ApplicationInfo#memtagMode
+ * @see R.styleable#AndroidManifest_memtagMode
+ */
+ @ApplicationInfo.MemtagMode
+ int getMemtagMode();
+
+ /**
+ * @see ApplicationInfo#nativeHeapZeroInitialized
+ * @see R.styleable#AndroidManifest_nativeHeapZeroInitialized
+ */
+ @ApplicationInfo.NativeHeapZeroInitialized
+ int getNativeHeapZeroInitialized();
+ @Nullable
+ Boolean hasRequestRawExternalStorageAccess();
+
+ /**
+ * @see ApplicationInfo#hasRequestForegroundServiceExemption()
+ * @see R.styleable#AndroidManifest_requestForegroundServiceExemption
+ */
+ boolean hasRequestForegroundServiceExemption();
+
+ // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
+ ApplicationInfo toAppInfoWithoutState();
+
+ /**
+ * same as toAppInfoWithoutState except without flag computation.
+ */
+ ApplicationInfo toAppInfoWithoutStateWithoutFlags();
+
+ /**
+ * Whether or not the app has said its attribution tags can be made user-visible.
+ * @see ApplicationInfo#areAttributionsUserVisible()
+ */
+ boolean areAttributionsUserVisible();
+}
diff --git a/android/content/pm/parsing/ParsingPackageUtils.java b/android/content/pm/parsing/ParsingPackageUtils.java
new file mode 100644
index 0000000..dce242c
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackageUtils.java
@@ -0,0 +1,3236 @@
+/*
+ * Copyright (C) 2020 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.parsing;
+
+import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Build.VERSION_CODES.DONUT;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import android.annotation.AnyRes;
+import android.annotation.CheckResult;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleableRes;
+import android.app.ActivityThread;
+import android.app.ResourcesManager;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.SigningDetails;
+import android.content.pm.Signature;
+import android.content.pm.parsing.component.ComponentParseUtils;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedActivityUtils;
+import android.content.pm.parsing.component.ParsedAttribution;
+import android.content.pm.parsing.component.ParsedAttributionUtils;
+import android.content.pm.parsing.component.ParsedComponent;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.parsing.component.ParsedInstrumentationUtils;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.content.pm.parsing.component.ParsedIntentInfoUtils;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedPermissionUtils;
+import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProcessUtils;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedProviderUtils;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedServiceUtils;
+import android.content.pm.parsing.component.ParsedUsesPermission;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseInput.DeferredError;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+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;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.ext.SdkExtensions;
+import android.permission.PermissionManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TypedValue;
+import android.util.apk.ApkSignatureVerifier;
+
+import com.android.internal.R;
+import com.android.internal.os.ClassLoaderFactory;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * TODO(b/135203078): Differentiate between parse_ methods and some add_ method for whether it
+ * mutates the passed-in component or not. Or consolidate so all parse_ methods mutate.
+ *
+ * @hide
+ */
+public class ParsingPackageUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ public static final boolean DEBUG_JAR = false;
+ public static final boolean DEBUG_BACKUP = false;
+ public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
+ /** File name in an APK for the Android manifest. */
+ public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+
+ /** Path prefix for apps on expanded storage */
+ public static final String MNT_EXPAND = "/mnt/expand/";
+
+ public static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";
+ public static final String TAG_APPLICATION = "application";
+ public static final String TAG_COMPATIBLE_SCREENS = "compatible-screens";
+ public static final String TAG_EAT_COMMENT = "eat-comment";
+ public static final String TAG_FEATURE_GROUP = "feature-group";
+ public static final String TAG_INSTRUMENTATION = "instrumentation";
+ public static final String TAG_KEY_SETS = "key-sets";
+ public static final String TAG_MANIFEST = "manifest";
+ public static final String TAG_ORIGINAL_PACKAGE = "original-package";
+ public static final String TAG_OVERLAY = "overlay";
+ public static final String TAG_PACKAGE = "package";
+ public static final String TAG_PACKAGE_VERIFIER = "package-verifier";
+ public static final String TAG_ATTRIBUTION = "attribution";
+ public static final String TAG_PERMISSION = "permission";
+ public static final String TAG_PERMISSION_GROUP = "permission-group";
+ public static final String TAG_PERMISSION_TREE = "permission-tree";
+ public static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";
+ public static final String TAG_QUERIES = "queries";
+ public static final String TAG_RESTRICT_UPDATE = "restrict-update";
+ public static final String TAG_SUPPORT_SCREENS = "supports-screens";
+ public static final String TAG_SUPPORTS_INPUT = "supports-input";
+ public static final String TAG_USES_CONFIGURATION = "uses-configuration";
+ public static final String TAG_USES_FEATURE = "uses-feature";
+ public static final String TAG_USES_GL_TEXTURE = "uses-gl-texture";
+ public static final String TAG_USES_PERMISSION = "uses-permission";
+ public static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23";
+ public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";
+ public static final String TAG_USES_SDK = "uses-sdk";
+ public static final String TAG_USES_SPLIT = "uses-split";
+ public static final String TAG_PROFILEABLE = "profileable";
+
+ public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
+ public static final String METADATA_SUPPORTS_SIZE_CHANGES = "android.supports_size_changes";
+ public static final String METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY =
+ "android.activity_window_layout_affinity";
+ public static final String METADATA_ACTIVITY_LAUNCH_MODE = "android.activity.launch_mode";
+
+ public static final int SDK_VERSION = Build.VERSION.SDK_INT;
+ public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+
+ public static boolean sCompatibilityModeEnabled = true;
+ public static boolean sUseRoundIcon = false;
+
+ public static final int PARSE_DEFAULT_INSTALL_LOCATION =
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1;
+
+ /** If set to true, we will only allow package files that exactly match
+ * the DTD. Otherwise, we try to get as much from the package as we
+ * can without failing. This should normally be set to false, to
+ * support extensions to the DTD in future versions. */
+ public static final boolean RIGID_PARSER = false;
+
+ public static final int PARSE_MUST_BE_APK = 1 << 0;
+ public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
+ public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
+ public static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
+ public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
+ public static final int PARSE_ENFORCE_CODE = 1 << 6;
+ public static final int PARSE_CHATTY = 1 << 31;
+
+ @IntDef(flag = true, prefix = { "PARSE_" }, value = {
+ PARSE_CHATTY,
+ PARSE_COLLECT_CERTIFICATES,
+ PARSE_ENFORCE_CODE,
+ PARSE_EXTERNAL_STORAGE,
+ PARSE_IGNORE_PROCESSES,
+ PARSE_IS_SYSTEM_DIR,
+ PARSE_MUST_BE_APK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ParseFlags {}
+
+ /**
+ * For those names would be used as a part of the file name. Limits size to 223 and reserves 32
+ * for the OS.
+ */
+ private static final int MAX_FILE_NAME_SIZE = 223;
+
+ /**
+ * @see #parseDefault(ParseInput, File, int, List, boolean)
+ */
+ @NonNull
+ public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
+ @ParseFlags int parseFlags,
+ @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+ boolean collectCertificates) {
+ ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
+ return parseDefault(input, file, parseFlags, splitPermissions, collectCertificates);
+ }
+
+ /**
+ * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
+ * request, without caching the input object and without querying the internal system state
+ * for feature support.
+ */
+ @NonNull
+ public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
+ @ParseFlags int parseFlags,
+ @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+ boolean collectCertificates) {
+ ParseResult<ParsingPackage> result;
+
+ ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, splitPermissions,
+ new Callback() {
+ @Override
+ public boolean hasFeature(String feature) {
+ // Assume the device doesn't support anything. This will affect permission
+ // parsing and will force <uses-permission/> declarations to include all
+ // requiredNotFeature permissions and exclude all requiredFeature
+ // permissions. This mirrors the old behavior.
+ return false;
+ }
+
+ @Override
+ public ParsingPackage startParsingPackage(
+ @NonNull String packageName,
+ @NonNull String baseApkPath,
+ @NonNull String path,
+ @NonNull TypedArray manifestArray, boolean isCoreApp) {
+ return new ParsingPackageImpl(packageName, baseApkPath, path,
+ manifestArray);
+ }
+ });
+ try {
+ result = parser.parsePackage(input, file, parseFlags);
+ if (result.isError()) {
+ return result;
+ }
+ } catch (PackageParser.PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Error parsing package", e);
+ }
+
+ try {
+ ParsingPackage pkg = result.getResult();
+ if (collectCertificates) {
+ pkg.setSigningDetails(
+ ParsingPackageUtils.getSigningDetails(pkg, false /* skipVerify */));
+ }
+
+ // Need to call this to finish the parsing stage
+ pkg.hideAsParsed();
+
+ return input.success(pkg);
+ } catch (PackageParser.PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Error collecting package certificates", e);
+ }
+ }
+
+ private boolean mOnlyCoreApps;
+ private String[] mSeparateProcesses;
+ private DisplayMetrics mDisplayMetrics;
+ @NonNull
+ private List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
+ private Callback mCallback;
+
+ public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
+ DisplayMetrics displayMetrics,
+ @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+ @NonNull Callback callback) {
+ mOnlyCoreApps = onlyCoreApps;
+ mSeparateProcesses = separateProcesses;
+ mDisplayMetrics = displayMetrics;
+ mSplitPermissionInfos = splitPermissions;
+ mCallback = callback;
+ }
+
+ /**
+ * Parse the package at the given location. Automatically detects if the
+ * package is a monolithic style (single APK file) or cluster style
+ * (directory of APKs).
+ * <p>
+ * This performs validity checking on cluster style packages, such as
+ * requiring identical package name and version codes, a single base APK,
+ * and unique split names.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
+ *
+ * If {@code useCaches} is true, the package parser might return a cached
+ * result from a previous parse of the same {@code packageFile} with the same
+ * {@code flags}. Note that this method does not check whether {@code packageFile}
+ * has changed since the last parse, it's up to callers to do so.
+ *
+ * @see PackageParser#parsePackageLite(File, int)
+ */
+ public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
+ int flags)
+ throws PackageParserException {
+ if (packageFile.isDirectory()) {
+ return parseClusterPackage(input, packageFile, flags);
+ } else {
+ return parseMonolithicPackage(input, packageFile, flags);
+ }
+ }
+
+ /**
+ * Parse all APKs contained in the given directory, treating them as a
+ * single package. This also performs validity checking, such as requiring
+ * identical package name and version codes, a single base APK, and unique
+ * split names.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
+ */
+ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
+ int flags) {
+ final ParseResult<PackageLite> liteResult =
+ ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
+ if (liteResult.isError()) {
+ return input.error(liteResult);
+ }
+
+ final PackageLite lite = liteResult.getResult();
+ if (mOnlyCoreApps && !lite.isCoreApp()) {
+ return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
+ "Not a coreApp: " + packageDir);
+ }
+
+ // Build the split dependency tree.
+ SparseArray<int[]> splitDependencies = null;
+ final SplitAssetLoader assetLoader;
+ if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
+ try {
+ splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
+ assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+ } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
+ return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
+ }
+ } else {
+ assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ }
+
+ try {
+ final File baseApk = new File(lite.getBaseApkPath());
+ final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
+ lite.getPath(), assetLoader, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ ParsingPackage pkg = result.getResult();
+ if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
+ pkg.asSplit(
+ lite.getSplitNames(),
+ lite.getSplitApkPaths(),
+ lite.getSplitRevisionCodes(),
+ splitDependencies
+ );
+ final int num = lite.getSplitNames().length;
+
+ for (int i = 0; i < num; i++) {
+ final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+ parseSplitApk(input, pkg, i, splitAssets, flags);
+ }
+ }
+
+ pkg.setUse32BitAbi(lite.isUse32bitAbi());
+ return input.success(pkg);
+ } catch (PackageParserException e) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to load assets: " + lite.getBaseApkPath(), e);
+ } finally {
+ IoUtils.closeQuietly(assetLoader);
+ }
+ }
+
+ /**
+ * Parse the given APK file, treating it as as a single monolithic package.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
+ */
+ private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
+ int flags) throws PackageParserException {
+ final ParseResult<PackageLite> liteResult =
+ ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
+ if (liteResult.isError()) {
+ return input.error(liteResult);
+ }
+
+ final PackageLite lite = liteResult.getResult();
+ if (mOnlyCoreApps && !lite.isCoreApp()) {
+ return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
+ "Not a coreApp: " + apkFile);
+ }
+
+ final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ try {
+ final ParseResult<ParsingPackage> result = parseBaseApk(input,
+ apkFile,
+ apkFile.getCanonicalPath(),
+ assetLoader, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult()
+ .setUse32BitAbi(lite.isUse32bitAbi()));
+ } catch (IOException e) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to get path: " + apkFile, e);
+ } finally {
+ IoUtils.closeQuietly(assetLoader);
+ }
+ }
+
+ private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
+ String codePath, SplitAssetLoader assetLoader, int flags)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ String volumeUuid = null;
+ if (apkPath.startsWith(MNT_EXPAND)) {
+ final int end = apkPath.indexOf('/', MNT_EXPAND.length());
+ volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
+ }
+
+ if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+
+ final AssetManager assets = assetLoader.getBaseAssetManager();
+ final int cookie = assets.findCookieForPath(apkPath);
+ if (cookie == 0) {
+ return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
+
+ try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
+ ANDROID_MANIFEST_FILENAME)) {
+ final Resources res = new Resources(assets, mDisplayMetrics, null);
+
+ ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
+ parser, flags);
+ if (result.isError()) {
+ return input.error(result.getErrorCode(),
+ apkPath + " (at " + parser.getPositionDescription() + "): "
+ + result.getErrorMessage());
+ }
+
+ final ParsingPackage pkg = result.getResult();
+ if (assets.containsAllocatedTable()) {
+ final ParseResult<?> deferResult = input.deferError(
+ "Targeting R+ (version " + Build.VERSION_CODES.R + " and above) requires"
+ + " the resources.arsc of installed APKs to be stored uncompressed"
+ + " and aligned on a 4-byte boundary",
+ DeferredError.RESOURCES_ARSC_COMPRESSED);
+ if (deferResult.isError()) {
+ return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED,
+ deferResult.getErrorMessage());
+ }
+ }
+
+ ApkAssets apkAssets = assetLoader.getBaseApkAssets();
+ boolean definesOverlayable = false;
+ try {
+ definesOverlayable = apkAssets.definesOverlayable();
+ } catch (IOException ignored) {
+ // Will fail if there's no packages in the ApkAssets, which can be treated as false
+ }
+
+ if (definesOverlayable) {
+ SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.valueAt(index);
+ Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
+ if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
+ for (String overlayable : overlayableToActor.keySet()) {
+ pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
+ }
+ }
+ }
+ }
+
+ pkg.setVolumeUuid(volumeUuid);
+
+ if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
+ pkg.setSigningDetails(getSigningDetails(pkg, false));
+ } else {
+ pkg.setSigningDetails(SigningDetails.UNKNOWN);
+ }
+
+ return input.success(pkg);
+ } catch (Exception e) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to read manifest from " + apkPath, e);
+ }
+ }
+
+ private ParseResult<ParsingPackage> parseSplitApk(ParseInput input,
+ ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) {
+ final String apkPath = pkg.getSplitCodePaths()[splitIndex];
+
+ if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
+
+ // This must always succeed, as the path has been added to the AssetManager before.
+ final int cookie = assets.findCookieForPath(apkPath);
+ if (cookie == 0) {
+ return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
+ try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
+ ANDROID_MANIFEST_FILENAME)) {
+ Resources res = new Resources(assets, mDisplayMetrics, null);
+ ParseResult<ParsingPackage> parseResult = parseSplitApk(input, pkg, res,
+ parser, flags, splitIndex);
+ if (parseResult.isError()) {
+ return input.error(parseResult.getErrorCode(),
+ apkPath + " (at " + parser.getPositionDescription() + "): "
+ + parseResult.getErrorMessage());
+ }
+
+ return parseResult;
+ } catch (Exception e) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to read manifest from " + apkPath, e);
+ }
+ }
+
+ /**
+ * Parse the manifest of a <em>base APK</em>. When adding new features you
+ * need to consider whether they should be supported by split APKs and child
+ * packages.
+ *
+ * @param apkPath The package apk file path
+ * @param res The resources from which to resolve values
+ * @param parser The manifest parser
+ * @param flags Flags how to parse
+ * @return Parsed package or null on error.
+ */
+ private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
+ String codePath, Resources res, XmlResourceParser parser, int flags)
+ throws XmlPullParserException, IOException {
+ final String splitName;
+ final String pkgName;
+
+ ParseResult<Pair<String, String>> packageSplitResult =
+ ApkLiteParseUtils.parsePackageSplitNames(input, parser);
+ if (packageSplitResult.isError()) {
+ return input.error(packageSplitResult);
+ }
+
+ Pair<String, String> packageSplit = packageSplitResult.getResult();
+ pkgName = packageSplit.first;
+ splitName = packageSplit.second;
+
+ if (!TextUtils.isEmpty(splitName)) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Expected base APK, but found split " + splitName
+ );
+ }
+
+ final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
+ try {
+ final boolean isCoreApp =
+ parser.getAttributeBooleanValue(null, "coreApp", false);
+ final ParsingPackage pkg = mCallback.startParsingPackage(
+ pkgName, apkPath, codePath, manifestArray, isCoreApp);
+ final ParseResult<ParsingPackage> result =
+ parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
+ if (result.isError()) {
+ return result;
+ }
+
+ return input.success(pkg);
+ } finally {
+ manifestArray.recycle();
+ }
+ }
+
+ /**
+ * Parse the manifest of a <em>split APK</em>.
+ * <p>
+ * Note that split APKs have many more restrictions on what they're capable
+ * of doing, so many valid features of a base APK have been carefully
+ * omitted here.
+ *
+ * @param pkg builder to fill
+ * @return false on failure
+ */
+ private ParseResult<ParsingPackage> parseSplitApk(ParseInput input, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser, int flags, int splitIndex)
+ throws XmlPullParserException, IOException, PackageParserException {
+ AttributeSet attrs = parser;
+
+ // We parsed manifest tag earlier; just skip past it
+ PackageParser.parsePackageSplitNames(parser, attrs);
+
+ int type;
+
+ boolean foundApp = false;
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult result;
+ String tagName = parser.getName();
+ if (TAG_APPLICATION.equals(tagName)) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ result = input.error("<manifest> has more than one <application>");
+ } else {
+ Slog.w(TAG, "<manifest> has more than one <application>");
+ result = input.success(null);
+ }
+ } else {
+ foundApp = true;
+ result = parseSplitApplication(input, pkg, res, parser, flags, splitIndex);
+ }
+ } else {
+ result = ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ if (!foundApp) {
+ ParseResult<?> deferResult = input.deferError(
+ "<manifest> does not contain an <application>", DeferredError.MISSING_APP_TAG);
+ if (deferResult.isError()) {
+ return input.error(deferResult);
+ }
+ }
+
+ return input.success(pkg);
+ }
+
+ /**
+ * Parse the {@code application} XML tree at the current parse location in a
+ * <em>split APK</em> manifest.
+ * <p>
+ * Note that split APKs have many more restrictions on what they're capable
+ * of doing, so many valid features of a base APK have been carefully
+ * omitted here.
+ */
+ private ParseResult<ParsingPackage> parseSplitApplication(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, int splitIndex)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
+ try {
+ pkg.setSplitHasCode(splitIndex, sa.getBoolean(
+ R.styleable.AndroidManifestApplication_hasCode, true));
+
+ final String classLoaderName = sa.getString(
+ R.styleable.AndroidManifestApplication_classLoader);
+ if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(
+ classLoaderName)) {
+ pkg.setSplitClassLoaderName(splitIndex, classLoaderName);
+ } else {
+ return input.error("Invalid class loader name: " + classLoaderName);
+ }
+ } finally {
+ sa.recycle();
+ }
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ ParsedMainComponent mainComponent = null;
+
+ final ParseResult result;
+ String tagName = parser.getName();
+ boolean isActivity = false;
+ switch (tagName) {
+ case "activity":
+ isActivity = true;
+ // fall-through
+ case "receiver":
+ ParseResult<ParsedActivity> activityResult =
+ ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
+ res,
+ parser, flags, sUseRoundIcon, input);
+ if (activityResult.isSuccess()) {
+ ParsedActivity activity = activityResult.getResult();
+ if (isActivity) {
+ pkg.addActivity(activity);
+ } else {
+ pkg.addReceiver(activity);
+ }
+ mainComponent = activity;
+ }
+ result = activityResult;
+ break;
+ case "service":
+ ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(
+ mSeparateProcesses, pkg, res, parser, flags,
+ sUseRoundIcon, input);
+ if (serviceResult.isSuccess()) {
+ ParsedService service = serviceResult.getResult();
+ pkg.addService(service);
+ mainComponent = service;
+ }
+ result = serviceResult;
+ break;
+ case "provider":
+ ParseResult<ParsedProvider> providerResult =
+ ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
+ flags, sUseRoundIcon, input);
+ if (providerResult.isSuccess()) {
+ ParsedProvider provider = providerResult.getResult();
+ pkg.addProvider(provider);
+ mainComponent = provider;
+ }
+ result = providerResult;
+ break;
+ case "activity-alias":
+ activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser,
+ sUseRoundIcon, input);
+ if (activityResult.isSuccess()) {
+ ParsedActivity activity = activityResult.getResult();
+ pkg.addActivity(activity);
+ mainComponent = activity;
+ }
+
+ result = activityResult;
+ break;
+ default:
+ result = parseSplitBaseAppChildTags(input, tagName, pkg, res, parser);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ if (mainComponent != null && mainComponent.getSplitName() == null) {
+ // If the loaded component did not specify a split, inherit the split name
+ // based on the split it is defined in.
+ // This is used to later load the correct split when starting this
+ // component.
+ mainComponent.setSplitName(pkg.getSplitNames()[splitIndex]);
+ }
+ }
+
+ return input.success(pkg);
+ }
+
+ /**
+ * For parsing non-MainComponents. Main ones have an order and some special handling which is
+ * done directly in {@link #parseSplitApplication(ParseInput, ParsingPackage, Resources,
+ * XmlResourceParser, int, int)}.
+ */
+ private ParseResult parseSplitBaseAppChildTags(ParseInput input, String tag, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {
+ switch (tag) {
+ case "meta-data":
+ // note: application meta-data is stored off to the side, so it can
+ // remain null in the primary copy (we like to avoid extra copies because
+ // it can be large)
+ ParseResult<Property> metaDataResult = parseMetaData(pkg, null, res,
+ parser, "<meta-data>", input);
+ if (metaDataResult.isSuccess() && metaDataResult.getResult() != null) {
+ pkg.setMetaData(metaDataResult.getResult().toBundle(pkg.getMetaData()));
+ }
+ return metaDataResult;
+ case "property":
+ ParseResult<Property> propertyResult = parseMetaData(pkg, null, res,
+ parser, "<property>", input);
+ if (propertyResult.isSuccess()) {
+ pkg.addProperty(propertyResult.getResult());
+ }
+ return propertyResult;
+ case "uses-static-library":
+ return parseUsesStaticLibrary(input, pkg, res, parser);
+ case "uses-library":
+ return parseUsesLibrary(input, pkg, res, parser);
+ case "uses-native-library":
+ return parseUsesNativeLibrary(input, pkg, res, parser);
+ case "uses-package":
+ // Dependencies for app installers; we don't currently try to
+ // enforce this.
+ return input.success(null);
+ default:
+ return ParsingUtils.unknownTag("<application>", pkg, parser, input);
+ }
+ }
+
+ private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
+ TypedArray sa, Resources res, XmlResourceParser parser, int flags)
+ throws XmlPullParserException, IOException {
+ ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
+ if (sharedUserResult.isError()) {
+ return sharedUserResult;
+ }
+
+ pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
+ R.styleable.AndroidManifest_installLocation, sa))
+ .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
+ R.styleable.AndroidManifest_targetSandboxVersion, sa))
+ /* Set the global "on SD card" flag */
+ .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+
+ boolean foundApp = false;
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ final ParseResult result;
+
+ // <application> has special logic, so it's handled outside the general method
+ if (TAG_APPLICATION.equals(tagName)) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ result = input.error("<manifest> has more than one <application>");
+ } else {
+ Slog.w(TAG, "<manifest> has more than one <application>");
+ result = input.success(null);
+ }
+ } else {
+ foundApp = true;
+ result = parseBaseApplication(input, pkg, res, parser, flags);
+ }
+ } else {
+ result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
+ ParseResult<?> deferResult = input.deferError(
+ "<manifest> does not contain an <application> or <instrumentation>",
+ DeferredError.MISSING_APP_TAG);
+ if (deferResult.isError()) {
+ return input.error(deferResult);
+ }
+ }
+
+ if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
+ return input.error(
+ INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Combination <attribution> tags are not valid"
+ );
+ }
+
+ convertNewPermissions(pkg);
+
+ convertSplitPermissions(pkg);
+
+ // At this point we can check if an application is not supporting densities and hence
+ // cannot be windowed / resized. Note that an SDK version of 0 is common for
+ // pre-Doughnut applications.
+ if (pkg.getTargetSdkVersion() < DONUT
+ || (!pkg.isSupportsSmallScreens()
+ && !pkg.isSupportsNormalScreens()
+ && !pkg.isSupportsLargeScreens()
+ && !pkg.isSupportsExtraLargeScreens()
+ && !pkg.isResizeable()
+ && !pkg.isAnyDensity())) {
+ adjustPackageToBeUnresizeableAndUnpipable(pkg);
+ }
+
+ return input.success(pkg);
+ }
+
+ private ParseResult parseBaseApkTag(String tag, ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
+ throws IOException, XmlPullParserException {
+ switch (tag) {
+ case TAG_OVERLAY:
+ return parseOverlay(input, pkg, res, parser);
+ case TAG_KEY_SETS:
+ return parseKeySets(input, pkg, res, parser);
+ case "feature": // TODO moltmann: Remove
+ case TAG_ATTRIBUTION:
+ return parseAttribution(input, pkg, res, parser);
+ case TAG_PERMISSION_GROUP:
+ return parsePermissionGroup(input, pkg, res, parser);
+ case TAG_PERMISSION:
+ return parsePermission(input, pkg, res, parser);
+ case TAG_PERMISSION_TREE:
+ return parsePermissionTree(input, pkg, res, parser);
+ case TAG_USES_PERMISSION:
+ case TAG_USES_PERMISSION_SDK_M:
+ case TAG_USES_PERMISSION_SDK_23:
+ return parseUsesPermission(input, pkg, res, parser);
+ case TAG_USES_CONFIGURATION:
+ return parseUsesConfiguration(input, pkg, res, parser);
+ case TAG_USES_FEATURE:
+ return parseUsesFeature(input, pkg, res, parser);
+ case TAG_FEATURE_GROUP:
+ return parseFeatureGroup(input, pkg, res, parser);
+ case TAG_USES_SDK:
+ return parseUsesSdk(input, pkg, res, parser);
+ case TAG_SUPPORT_SCREENS:
+ return parseSupportScreens(input, pkg, res, parser);
+ case TAG_PROTECTED_BROADCAST:
+ return parseProtectedBroadcast(input, pkg, res, parser);
+ case TAG_INSTRUMENTATION:
+ return parseInstrumentation(input, pkg, res, parser);
+ case TAG_ORIGINAL_PACKAGE:
+ return parseOriginalPackage(input, pkg, res, parser);
+ case TAG_ADOPT_PERMISSIONS:
+ return parseAdoptPermissions(input, pkg, res, parser);
+ case TAG_USES_GL_TEXTURE:
+ case TAG_COMPATIBLE_SCREENS:
+ case TAG_SUPPORTS_INPUT:
+ case TAG_EAT_COMMENT:
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ return input.success(pkg);
+ case TAG_RESTRICT_UPDATE:
+ return parseRestrictUpdateHash(flags, input, pkg, res, parser);
+ case TAG_QUERIES:
+ return parseQueries(input, pkg, res, parser);
+ default:
+ return ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseSharedUser(ParseInput input,
+ ParsingPackage pkg, TypedArray sa) {
+ String str = nonConfigString(0, R.styleable.AndroidManifest_sharedUserId, sa);
+ if (TextUtils.isEmpty(str)) {
+ return input.success(pkg);
+ }
+
+ if (!"android".equals(pkg.getPackageName())) {
+ ParseResult<?> nameResult = validateName(input, str, true, true);
+ if (nameResult.isError()) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
+ "<manifest> specifies bad sharedUserId name \"" + str + "\": "
+ + nameResult.getErrorMessage());
+ }
+ }
+
+ return input.success(pkg
+ .setSharedUserId(str.intern())
+ .setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
+ }
+
+ private static ParseResult<ParsingPackage> parseKeySets(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ // we've encountered the 'key-sets' tag
+ // all the keys and keysets that we want must be defined here
+ // so we're going to iterate over the parser and pull out the things we want
+ int outerDepth = parser.getDepth();
+ int currentKeySetDepth = -1;
+ int type;
+ String currentKeySet = null;
+ ArrayMap<String, PublicKey> publicKeys = new ArrayMap<>();
+ ArraySet<String> upgradeKeySets = new ArraySet<>();
+ ArrayMap<String, ArraySet<String>> definedKeySets = new ArrayMap<>();
+ ArraySet<String> improperKeySets = new ArraySet<>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG) {
+ if (parser.getDepth() == currentKeySetDepth) {
+ currentKeySet = null;
+ currentKeySetDepth = -1;
+ }
+ continue;
+ }
+ String tagName = parser.getName();
+ switch (tagName) {
+ case "key-set": {
+ if (currentKeySet != null) {
+ return input.error("Improperly nested 'key-set' tag at "
+ + parser.getPositionDescription());
+ }
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestKeySet);
+ try {
+ final String keysetName = sa.getNonResourceString(
+ R.styleable.AndroidManifestKeySet_name);
+ definedKeySets.put(keysetName, new ArraySet<>());
+ currentKeySet = keysetName;
+ currentKeySetDepth = parser.getDepth();
+ } finally {
+ sa.recycle();
+ }
+ } break;
+ case "public-key": {
+ if (currentKeySet == null) {
+ return input.error("Improperly nested 'key-set' tag at "
+ + parser.getPositionDescription());
+ }
+ TypedArray sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestPublicKey);
+ try {
+ final String publicKeyName = nonResString(
+ R.styleable.AndroidManifestPublicKey_name, sa);
+ final String encodedKey = nonResString(
+ R.styleable.AndroidManifestPublicKey_value, sa);
+ if (encodedKey == null && publicKeys.get(publicKeyName) == null) {
+ return input.error("'public-key' " + publicKeyName
+ + " must define a public-key value on first use at "
+ + parser.getPositionDescription());
+ } else if (encodedKey != null) {
+ PublicKey currentKey = PackageParser.parsePublicKey(encodedKey);
+ if (currentKey == null) {
+ Slog.w(TAG, "No recognized valid key in 'public-key' tag at "
+ + parser.getPositionDescription() + " key-set "
+ + currentKeySet
+ + " will not be added to the package's defined key-sets.");
+ improperKeySets.add(currentKeySet);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ if (publicKeys.get(publicKeyName) == null
+ || publicKeys.get(publicKeyName).equals(currentKey)) {
+
+ /* public-key first definition, or matches old definition */
+ publicKeys.put(publicKeyName, currentKey);
+ } else {
+ return input.error("Value of 'public-key' " + publicKeyName
+ + " conflicts with previously defined value at "
+ + parser.getPositionDescription());
+ }
+ }
+ definedKeySets.get(currentKeySet).add(publicKeyName);
+ XmlUtils.skipCurrentTag(parser);
+ } finally {
+ sa.recycle();
+ }
+ } break;
+ case "upgrade-key-set": {
+ TypedArray sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestUpgradeKeySet);
+ try {
+ String name = sa.getNonResourceString(
+ R.styleable.AndroidManifestUpgradeKeySet_name);
+ upgradeKeySets.add(name);
+ XmlUtils.skipCurrentTag(parser);
+ } finally {
+ sa.recycle();
+ }
+ } break;
+ default:
+ ParseResult result = ParsingUtils.unknownTag("<key-sets>", pkg, parser,
+ input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ break;
+ }
+ }
+ String packageName = pkg.getPackageName();
+ Set<String> publicKeyNames = publicKeys.keySet();
+ if (publicKeyNames.removeAll(definedKeySets.keySet())) {
+ return input.error("Package" + packageName
+ + " AndroidManifest.xml 'key-set' and 'public-key' names must be distinct.");
+ }
+
+ for (ArrayMap.Entry<String, ArraySet<String>> e : definedKeySets.entrySet()) {
+ final String keySetName = e.getKey();
+ if (e.getValue().size() == 0) {
+ Slog.w(TAG, "Package" + packageName + " AndroidManifest.xml "
+ + "'key-set' " + keySetName + " has no valid associated 'public-key'."
+ + " Not including in package's defined key-sets.");
+ continue;
+ } else if (improperKeySets.contains(keySetName)) {
+ Slog.w(TAG, "Package" + packageName + " AndroidManifest.xml "
+ + "'key-set' " + keySetName + " contained improper 'public-key'"
+ + " tags. Not including in package's defined key-sets.");
+ continue;
+ }
+
+ for (String s : e.getValue()) {
+ pkg.addKeySet(keySetName, publicKeys.get(s));
+ }
+ }
+ if (pkg.getKeySetMapping().keySet().containsAll(upgradeKeySets)) {
+ pkg.setUpgradeKeySets(upgradeKeySets);
+ } else {
+ return input.error("Package" + packageName
+ + " AndroidManifest.xml does not define all 'upgrade-key-set's .");
+ }
+
+ return input.success(pkg);
+ }
+
+ private static ParseResult<ParsingPackage> parseAttribution(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ ParseResult<ParsedAttribution> result = ParsedAttributionUtils.parseAttribution(res,
+ parser, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addAttribution(result.getResult()));
+ }
+
+ private static ParseResult<ParsingPackage> parsePermissionGroup(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult<ParsedPermissionGroup> result = ParsedPermissionUtils.parsePermissionGroup(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addPermissionGroup(result.getResult()));
+ }
+
+ private static ParseResult<ParsingPackage> parsePermission(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermission(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addPermission(result.getResult()));
+ }
+
+ private static ParseResult<ParsingPackage> parsePermissionTree(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermissionTree(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addPermission(result.getResult()));
+ }
+
+ private ParseResult<ParsingPackage> parseUsesPermission(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesPermission);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String name = sa.getNonResourceString(
+ R.styleable.AndroidManifestUsesPermission_name);
+
+ int maxSdkVersion = 0;
+ TypedValue val = sa.peekValue(
+ R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
+ if (val != null) {
+ if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
+ maxSdkVersion = val.data;
+ }
+ }
+
+ final ArraySet<String> requiredFeatures = new ArraySet<>();
+ String feature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature,
+ 0);
+ if (feature != null) {
+ requiredFeatures.add(feature);
+ }
+
+ final ArraySet<String> requiredNotFeatures = new ArraySet<>();
+ feature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable
+ .AndroidManifestUsesPermission_requiredNotFeature,
+ 0);
+ if (feature != null) {
+ requiredNotFeatures.add(feature);
+ }
+
+ final int usesPermissionFlags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_usesPermissionFlags,
+ 0);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final ParseResult<?> result;
+ switch (parser.getName()) {
+ case "required-feature":
+ result = parseRequiredFeature(input, res, parser);
+ if (result.isSuccess()) {
+ requiredFeatures.add((String) result.getResult());
+ }
+ break;
+
+ case "required-not-feature":
+ result = parseRequiredNotFeature(input, res, parser);
+ if (result.isSuccess()) {
+ requiredNotFeatures.add((String) result.getResult());
+ }
+ break;
+
+ default:
+ result = ParsingUtils.unknownTag("<uses-permission>", pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ // Can only succeed from here on out
+ ParseResult<ParsingPackage> success = input.success(pkg);
+
+ if (name == null) {
+ return success;
+ }
+
+ if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+ return success;
+ }
+
+ if (mCallback != null) {
+ // Only allow requesting this permission if the platform supports all of the
+ // "required-feature"s.
+ for (int i = requiredFeatures.size() - 1; i >= 0; i--) {
+ if (!mCallback.hasFeature(requiredFeatures.valueAt(i))) {
+ return success;
+ }
+ }
+
+ // Only allow requesting this permission if the platform does not supports any of
+ // the "required-not-feature"s.
+ for (int i = requiredNotFeatures.size() - 1; i >= 0; i--) {
+ if (mCallback.hasFeature(requiredNotFeatures.valueAt(i))) {
+ return success;
+ }
+ }
+ }
+
+ // Quietly ignore duplicate permission requests, but fail loudly if
+ // the two requests have conflicting flags
+ boolean found = false;
+ final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+ final int size = usesPermissions.size();
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ if (Objects.equals(usesPermission.name, name)) {
+ if (usesPermission.usesPermissionFlags != usesPermissionFlags) {
+ return input.error("Conflicting uses-permissions flags: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ } else {
+ Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ pkg.addUsesPermission(new ParsedUsesPermission(name, usesPermissionFlags));
+ }
+ return success;
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private ParseResult<String> parseRequiredFeature(ParseInput input, Resources res,
+ AttributeSet attrs) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestRequiredFeature);
+ try {
+ final String featureName = sa.getString(
+ R.styleable.AndroidManifestRequiredFeature_name);
+ return TextUtils.isEmpty(featureName)
+ ? input.error("Feature name is missing from <required-feature> tag.")
+ : input.success(featureName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private ParseResult<String> parseRequiredNotFeature(ParseInput input, Resources res,
+ AttributeSet attrs) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestRequiredNotFeature);
+ try {
+ final String featureName = sa.getString(
+ R.styleable.AndroidManifestRequiredNotFeature_name);
+ return TextUtils.isEmpty(featureName)
+ ? input.error("Feature name is missing from <required-not-feature> tag.")
+ : input.success(featureName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseUsesConfiguration(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesConfiguration);
+ try {
+ cPref.reqTouchScreen = sa.getInt(
+ R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen,
+ Configuration.TOUCHSCREEN_UNDEFINED);
+ cPref.reqKeyboardType = sa.getInt(
+ R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType,
+ Configuration.KEYBOARD_UNDEFINED);
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard,
+ false)) {
+ cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
+ }
+ cPref.reqNavigation = sa.getInt(
+ R.styleable.AndroidManifestUsesConfiguration_reqNavigation,
+ Configuration.NAVIGATION_UNDEFINED);
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav,
+ false)) {
+ cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
+ }
+ pkg.addConfigPreference(cPref);
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseUsesFeature(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ FeatureInfo fi = parseFeatureInfo(res, parser);
+ pkg.addReqFeature(fi);
+
+ if (fi.name == null) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ cPref.reqGlEsVersion = fi.reqGlEsVersion;
+ pkg.addConfigPreference(cPref);
+ }
+
+ return input.success(pkg);
+ }
+
+ private static FeatureInfo parseFeatureInfo(Resources res, AttributeSet attrs) {
+ FeatureInfo fi = new FeatureInfo();
+ TypedArray sa = res.obtainAttributes(attrs, R.styleable.AndroidManifestUsesFeature);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ fi.name = sa.getNonResourceString(R.styleable.AndroidManifestUsesFeature_name);
+ fi.version = sa.getInt(R.styleable.AndroidManifestUsesFeature_version, 0);
+ if (fi.name == null) {
+ fi.reqGlEsVersion = sa.getInt(R.styleable.AndroidManifestUsesFeature_glEsVersion,
+ FeatureInfo.GL_ES_VERSION_UNDEFINED);
+ }
+ if (sa.getBoolean(R.styleable.AndroidManifestUsesFeature_required, true)) {
+ fi.flags |= FeatureInfo.FLAG_REQUIRED;
+ }
+ return fi;
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseFeatureGroup(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ FeatureGroupInfo group = new FeatureGroupInfo();
+ ArrayList<FeatureInfo> features = null;
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String innerTagName = parser.getName();
+ if (innerTagName.equals("uses-feature")) {
+ FeatureInfo featureInfo = parseFeatureInfo(res, parser);
+ // FeatureGroups are stricter and mandate that
+ // any <uses-feature> declared are mandatory.
+ featureInfo.flags |= FeatureInfo.FLAG_REQUIRED;
+ features = ArrayUtils.add(features, featureInfo);
+ } else {
+ Slog.w(TAG,
+ "Unknown element under <feature-group>: " + innerTagName
+ + " at " + pkg.getBaseApkPath() + " "
+ + parser.getPositionDescription());
+ }
+ }
+
+ if (features != null) {
+ group.features = new FeatureInfo[features.size()];
+ group.features = features.toArray(group.features);
+ }
+
+ pkg.addFeatureGroup(group);
+ return input.success(pkg);
+ }
+
+ private static ParseResult<ParsingPackage> parseUsesSdk(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ if (SDK_VERSION > 0) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+ try {
+ int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
+ String minCode = null;
+ int targetVers = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
+ String targetCode = null;
+
+ TypedValue val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ if (val != null) {
+ if (val.type == TypedValue.TYPE_STRING && val.string != null) {
+ minCode = val.string.toString();
+ } else {
+ // If it's not a string, it's an integer.
+ minVers = val.data;
+ }
+ }
+
+ val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+ if (val != null) {
+ if (val.type == TypedValue.TYPE_STRING && val.string != null) {
+ targetCode = val.string.toString();
+ if (minCode == null) {
+ minCode = targetCode;
+ }
+ } else {
+ // If it's not a string, it's an integer.
+ targetVers = val.data;
+ }
+ } else {
+ targetVers = minVers;
+ targetCode = minCode;
+ }
+
+ ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
+ targetVers, targetCode, SDK_CODENAMES, input);
+ if (targetSdkVersionResult.isError()) {
+ return input.error(targetSdkVersionResult);
+ }
+
+ int targetSdkVersion = targetSdkVersionResult.getResult();
+
+ ParseResult<?> deferResult =
+ input.enableDeferredError(pkg.getPackageName(), targetSdkVersion);
+ if (deferResult.isError()) {
+ return input.error(deferResult);
+ }
+
+ ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
+ SDK_VERSION, SDK_CODENAMES, input);
+ if (minSdkVersionResult.isError()) {
+ return input.error(minSdkVersionResult);
+ }
+
+ int minSdkVersion = minSdkVersionResult.getResult();
+
+ pkg.setMinSdkVersion(minSdkVersion)
+ .setTargetSdkVersion(targetSdkVersion);
+
+ int type;
+ final int innerDepth = parser.getDepth();
+ SparseIntArray minExtensionVersions = null;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final ParseResult result;
+ if (parser.getName().equals("extension-sdk")) {
+ if (minExtensionVersions == null) {
+ minExtensionVersions = new SparseIntArray();
+ }
+ result = parseExtensionSdk(input, res, parser, minExtensionVersions);
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ result = ParsingUtils.unknownTag("<uses-sdk>", pkg, parser, input);
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+ pkg.setMinExtensionVersions(exactSizedCopyOfSparseArray(minExtensionVersions));
+ } finally {
+ sa.recycle();
+ }
+ }
+ return input.success(pkg);
+ }
+
+ @Nullable
+ private static SparseIntArray exactSizedCopyOfSparseArray(@Nullable SparseIntArray input) {
+ if (input == null) {
+ return null;
+ }
+ SparseIntArray output = new SparseIntArray(input.size());
+ for (int i = 0; i < input.size(); i++) {
+ output.put(input.keyAt(i), input.valueAt(i));
+ }
+ return output;
+ }
+
+ private static ParseResult<SparseIntArray> parseExtensionSdk(
+ ParseInput input, Resources res, XmlResourceParser parser,
+ SparseIntArray minExtensionVersions) {
+ int sdkVersion;
+ int minVersion;
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestExtensionSdk);
+ try {
+ sdkVersion = sa.getInt(R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1);
+ minVersion = sa.getInt(R.styleable.AndroidManifestExtensionSdk_minExtensionVersion, -1);
+ } finally {
+ sa.recycle();
+ }
+
+ if (sdkVersion < 0) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<extension-sdk> must specify an sdkVersion >= 0");
+ }
+ if (minVersion < 0) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<extension-sdk> must specify minExtensionVersion >= 0");
+ }
+
+ try {
+ int version = SdkExtensions.getExtensionVersion(sdkVersion);
+ if (version < minVersion) {
+ return input.error(
+ PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Package requires " + sdkVersion + " extension version " + minVersion
+ + " which exceeds device version " + version);
+ }
+ } catch (RuntimeException e) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Specified sdkVersion " + sdkVersion + " is not valid");
+ }
+ minExtensionVersions.put(sdkVersion, minVersion);
+ return input.success(minExtensionVersions);
+ }
+
+ /**
+ * {@link ParseResult} version of
+ * {@link PackageParser#computeMinSdkVersion(int, String, int, String[], String[])}
+ */
+ public static ParseResult<Integer> computeMinSdkVersion(@IntRange(from = 1) int minVers,
+ @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
+ @NonNull String[] platformSdkCodenames, @NonNull ParseInput input) {
+ // If it's a release SDK, make sure we meet the minimum SDK requirement.
+ if (minCode == null) {
+ if (minVers <= platformSdkVersion) {
+ return input.success(minVers);
+ }
+
+ // We don't meet the minimum SDK requirement.
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Requires newer sdk version #" + minVers
+ + " (current version is #" + platformSdkVersion + ")");
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, we
+ // definitely meet the minimum SDK requirement.
+ if (matchTargetCode(platformSdkCodenames, minCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+
+ // Otherwise, we're looking at an incompatible pre-release SDK.
+ if (platformSdkCodenames.length > 0) {
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Requires development platform " + minCode
+ + " (current platform is any of "
+ + Arrays.toString(platformSdkCodenames) + ")");
+ } else {
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Requires development platform " + minCode
+ + " but this is a release platform.");
+ }
+ }
+
+ /**
+ * {@link ParseResult} version of
+ * {@link PackageParser#computeTargetSdkVersion(int, String, String[], String[])}
+ */
+ public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
+ @Nullable String targetCode, @NonNull String[] platformSdkCodenames,
+ @NonNull ParseInput input) {
+ // If it's a release SDK, return the version number unmodified.
+ if (targetCode == null) {
+ return input.success(targetVers);
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, it
+ // definitely targets this SDK.
+ if (matchTargetCode(platformSdkCodenames, targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+
+ // Otherwise, we're looking at an incompatible pre-release SDK.
+ if (platformSdkCodenames.length > 0) {
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Requires development platform " + targetCode
+ + " (current platform is any of "
+ + Arrays.toString(platformSdkCodenames) + ")");
+ } else {
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Requires development platform " + targetCode
+ + " but this is a release platform.");
+ }
+ }
+
+ /**
+ * Matches a given {@code targetCode} against a set of release codeNames. Target codes can
+ * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form
+ * {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}).
+ */
+ private static boolean matchTargetCode(@NonNull String[] codeNames,
+ @NonNull String targetCode) {
+ final String targetCodeName;
+ final int targetCodeIdx = targetCode.indexOf('.');
+ if (targetCodeIdx == -1) {
+ targetCodeName = targetCode;
+ } else {
+ targetCodeName = targetCode.substring(0, targetCodeIdx);
+ }
+ return ArrayUtils.contains(codeNames, targetCodeName);
+ }
+
+ private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ if ((flags & PARSE_IS_SYSTEM_DIR) != 0) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestRestrictUpdate);
+ try {
+ final String hash = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestRestrictUpdate_hash,
+ 0);
+
+ if (hash != null) {
+ final int hashLength = hash.length();
+ final byte[] hashBytes = new byte[hashLength / 2];
+ for (int i = 0; i < hashLength; i += 2) {
+ hashBytes[i / 2] = (byte) ((Character.digit(hash.charAt(i), 16)
+ << 4)
+ + Character.digit(hash.charAt(i + 1), 16));
+ }
+ pkg.setRestrictUpdateHash(hashBytes);
+ } else {
+ pkg.setRestrictUpdateHash(null);
+ }
+ } finally {
+ sa.recycle();
+ }
+ }
+ return input.success(pkg);
+ }
+
+ private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (parser.getName().equals("intent")) {
+ ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(null,
+ pkg, res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ ParsedIntentInfo intentInfo = result.getResult();
+
+ Uri data = null;
+ String dataType = null;
+ String host = null;
+ final int numActions = intentInfo.countActions();
+ final int numSchemes = intentInfo.countDataSchemes();
+ final int numTypes = intentInfo.countDataTypes();
+ final int numHosts = intentInfo.getHosts().length;
+ if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {
+ return input.error("intent tags must contain either an action or data.");
+ }
+ if (numActions > 1) {
+ return input.error("intent tag may have at most one action.");
+ }
+ if (numTypes > 1) {
+ return input.error("intent tag may have at most one data type.");
+ }
+ if (numSchemes > 1) {
+ return input.error("intent tag may have at most one data scheme.");
+ }
+ if (numHosts > 1) {
+ return input.error("intent tag may have at most one data host.");
+ }
+ Intent intent = new Intent();
+ for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
+ intent.addCategory(intentInfo.getCategory(i));
+ }
+ if (numHosts == 1) {
+ host = intentInfo.getHosts()[0];
+ }
+ if (numSchemes == 1) {
+ data = new Uri.Builder()
+ .scheme(intentInfo.getDataScheme(0))
+ .authority(host)
+ .path(IntentFilter.WILDCARD_PATH)
+ .build();
+ }
+ if (numTypes == 1) {
+ dataType = intentInfo.getDataType(0);
+ // The dataType may have had the '/' removed for the dynamic mimeType feature.
+ // If we detect that case, we add the * back.
+ if (!dataType.contains("/")) {
+ dataType = dataType + "/*";
+ }
+ if (data == null) {
+ data = new Uri.Builder()
+ .scheme("content")
+ .authority(IntentFilter.WILDCARD)
+ .path(IntentFilter.WILDCARD_PATH)
+ .build();
+ }
+ }
+ intent.setDataAndType(data, dataType);
+ if (numActions == 1) {
+ intent.setAction(intentInfo.getAction(0));
+ }
+ pkg.addQueriesIntent(intent);
+ } else if (parser.getName().equals("package")) {
+ final TypedArray sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestQueriesPackage);
+ final String packageName = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestQueriesPackage_name, 0);
+ if (TextUtils.isEmpty(packageName)) {
+ return input.error("Package name is missing from package tag.");
+ }
+ pkg.addQueriesPackage(packageName.intern());
+ } else if (parser.getName().equals("provider")) {
+ final TypedArray sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestQueriesProvider);
+ try {
+ final String authorities = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestQueriesProvider_authorities, 0);
+ if (TextUtils.isEmpty(authorities)) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Authority missing from provider tag."
+ );
+ }
+ StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";");
+ while (authoritiesTokenizer.hasMoreElements()) {
+ pkg.addQueriesProvider(authoritiesTokenizer.nextToken());
+ }
+ } finally {
+ sa.recycle();
+ }
+ }
+ }
+ return input.success(pkg);
+ }
+
+ /**
+ * Parse the {@code application} XML tree at the current parse location in a
+ * <em>base APK</em> manifest.
+ * <p>
+ * When adding new features, carefully consider if they should also be
+ * supported by split APKs.
+ *
+ * This method should avoid using a getter for fields set by this method. Prefer assigning
+ * a local variable and using it. Otherwise there's an ordering problem which can be broken
+ * if any code moves around.
+ */
+ private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
+ throws XmlPullParserException, IOException {
+ final String pkgName = pkg.getPackageName();
+ int targetSdk = pkg.getTargetSdkVersion();
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
+ try {
+ // TODO(b/135203078): Remove this and force unit tests to mock an empty manifest
+ // This case can only happen in unit tests where we sometimes need to create fakes
+ // of various package parser data structures.
+ if (sa == null) {
+ return input.error("<application> does not contain any attributes");
+ }
+
+ String name = sa.getNonConfigurationString(R.styleable.AndroidManifestApplication_name,
+ 0);
+ if (name != null) {
+ String packageName = pkg.getPackageName();
+ String outInfoName = ParsingUtils.buildClassName(packageName, name);
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) {
+ return input.error("<application> invalid android:name");
+ } else if (outInfoName == null) {
+ return input.error("Empty class name in package " + packageName);
+ }
+
+ pkg.setClassName(outInfoName);
+ }
+
+ TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
+ if (labelValue != null) {
+ pkg.setLabelRes(labelValue.resourceId);
+ if (labelValue.resourceId == 0) {
+ pkg.setNonLocalizedLabel(labelValue.coerceToString());
+ }
+ }
+
+ parseBaseAppBasicFlags(pkg, sa);
+
+ String manageSpaceActivity = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
+ R.styleable.AndroidManifestApplication_manageSpaceActivity, sa);
+ if (manageSpaceActivity != null) {
+ String manageSpaceActivityName = ParsingUtils.buildClassName(pkgName,
+ manageSpaceActivity);
+
+ if (manageSpaceActivityName == null) {
+ return input.error("Empty class name in package " + pkgName);
+ }
+
+ pkg.setManageSpaceActivityName(manageSpaceActivityName);
+ }
+
+ if (pkg.isAllowBackup()) {
+ // backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
+ // and restoreAnyVersion are only relevant if backup is possible for the
+ // given application.
+ String backupAgent = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
+ R.styleable.AndroidManifestApplication_backupAgent, sa);
+ if (backupAgent != null) {
+ String backupAgentName = ParsingUtils.buildClassName(pkgName, backupAgent);
+ if (backupAgentName == null) {
+ return input.error("Empty class name in package " + pkgName);
+ }
+
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "android:backupAgent = " + backupAgentName
+ + " from " + pkgName + "+" + backupAgent);
+ }
+
+ pkg.setBackupAgentName(backupAgentName)
+ .setKillAfterRestore(bool(true,
+ R.styleable.AndroidManifestApplication_killAfterRestore, sa))
+ .setRestoreAnyVersion(bool(false,
+ R.styleable.AndroidManifestApplication_restoreAnyVersion, sa))
+ .setFullBackupOnly(bool(false,
+ R.styleable.AndroidManifestApplication_fullBackupOnly, sa))
+ .setBackupInForeground(bool(false,
+ R.styleable.AndroidManifestApplication_backupInForeground, sa));
+ }
+
+ TypedValue v = sa.peekValue(
+ R.styleable.AndroidManifestApplication_fullBackupContent);
+ int fullBackupContent = 0;
+
+ if (v != null) {
+ fullBackupContent = v.resourceId;
+
+ if (v.resourceId == 0) {
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "fullBackupContent specified as boolean=" +
+ (v.data == 0 ? "false" : "true"));
+ }
+ // "false" => -1, "true" => 0
+ fullBackupContent = v.data == 0 ? -1 : 0;
+ }
+
+ pkg.setFullBackupContent(fullBackupContent);
+ }
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
+ }
+ }
+
+ if (sa.getBoolean(R.styleable.AndroidManifestApplication_persistent, false)) {
+ // Check if persistence is based on a feature being present
+ final String requiredFeature = sa.getNonResourceString(R.styleable
+ .AndroidManifestApplication_persistentWhenFeatureAvailable);
+ pkg.setPersistent(requiredFeature == null || mCallback.hasFeature(requiredFeature));
+ }
+
+ if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
+ pkg.setResizeableActivity(sa.getBoolean(
+ R.styleable.AndroidManifestApplication_resizeableActivity, true));
+ } else {
+ pkg.setResizeableActivityViaSdkVersion(
+ targetSdk >= Build.VERSION_CODES.N);
+ }
+
+ String taskAffinity;
+ if (targetSdk >= Build.VERSION_CODES.FROYO) {
+ taskAffinity = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestApplication_taskAffinity,
+ Configuration.NATIVE_CONFIG_VERSION);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ taskAffinity = sa.getNonResourceString(
+ R.styleable.AndroidManifestApplication_taskAffinity);
+ }
+
+ ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
+ pkgName, pkgName, taskAffinity, input);
+ if (taskAffinityResult.isError()) {
+ return input.error(taskAffinityResult);
+ }
+
+ pkg.setTaskAffinity(taskAffinityResult.getResult());
+ String factory = sa.getNonResourceString(
+ R.styleable.AndroidManifestApplication_appComponentFactory);
+ if (factory != null) {
+ String appComponentFactory = ParsingUtils.buildClassName(pkgName, factory);
+ if (appComponentFactory == null) {
+ return input.error("Empty class name in package " + pkgName);
+ }
+
+ pkg.setAppComponentFactory(appComponentFactory);
+ }
+
+ CharSequence pname;
+ if (targetSdk >= Build.VERSION_CODES.FROYO) {
+ pname = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestApplication_process,
+ Configuration.NATIVE_CONFIG_VERSION);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ pname = sa.getNonResourceString(
+ R.styleable.AndroidManifestApplication_process);
+ }
+ ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
+ pkgName, null, pname, flags, mSeparateProcesses, input);
+ if (processNameResult.isError()) {
+ return input.error(processNameResult);
+ }
+
+ String processName = processNameResult.getResult();
+ pkg.setProcessName(processName);
+
+ if (pkg.isCantSaveState()) {
+ // A heavy-weight application can not be in a custom process.
+ // We can do direct compare because we intern all strings.
+ if (processName != null && !processName.equals(pkgName)) {
+ return input.error(
+ "cantSaveState applications can not use custom processes");
+ }
+ }
+
+ String classLoaderName = pkg.getClassLoaderName();
+ if (classLoaderName != null
+ && !ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
+ return input.error("Invalid class loader name: " + classLoaderName);
+ }
+
+ pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
+ pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+ if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) {
+ Boolean v = sa.getBoolean(
+ R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false);
+ pkg.setNativeHeapZeroInitialized(
+ v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
+ }
+ if (sa.hasValue(
+ R.styleable.AndroidManifestApplication_requestRawExternalStorageAccess)) {
+ pkg.setRequestRawExternalStorageAccess(sa.getBoolean(R.styleable
+ .AndroidManifestApplication_requestRawExternalStorageAccess,
+ false));
+ }
+ if (sa.hasValue(
+ R.styleable.AndroidManifestApplication_requestForegroundServiceExemption)) {
+ pkg.setRequestForegroundServiceExemption(sa.getBoolean(R.styleable
+ .AndroidManifestApplication_requestForegroundServiceExemption,
+ false));
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ boolean hasActivityOrder = false;
+ boolean hasReceiverOrder = false;
+ boolean hasServiceOrder = false;
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult result;
+ String tagName = parser.getName();
+ boolean isActivity = false;
+ switch (tagName) {
+ case "activity":
+ isActivity = true;
+ // fall-through
+ case "receiver":
+ ParseResult<ParsedActivity> activityResult =
+ ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
+ res, parser, flags, sUseRoundIcon, input);
+
+ if (activityResult.isSuccess()) {
+ ParsedActivity activity = activityResult.getResult();
+ if (isActivity) {
+ hasActivityOrder |= (activity.getOrder() != 0);
+ pkg.addActivity(activity);
+ } else {
+ hasReceiverOrder |= (activity.getOrder() != 0);
+ pkg.addReceiver(activity);
+ }
+ }
+
+ result = activityResult;
+ break;
+ case "service":
+ ParseResult<ParsedService> serviceResult =
+ ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
+ flags, sUseRoundIcon, input);
+ if (serviceResult.isSuccess()) {
+ ParsedService service = serviceResult.getResult();
+ hasServiceOrder |= (service.getOrder() != 0);
+ pkg.addService(service);
+ }
+
+ result = serviceResult;
+ break;
+ case "provider":
+ ParseResult<ParsedProvider> providerResult =
+ ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
+ flags, sUseRoundIcon, input);
+ if (providerResult.isSuccess()) {
+ pkg.addProvider(providerResult.getResult());
+ }
+
+ result = providerResult;
+ break;
+ case "activity-alias":
+ activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
+ parser, sUseRoundIcon, input);
+ if (activityResult.isSuccess()) {
+ ParsedActivity activity = activityResult.getResult();
+ hasActivityOrder |= (activity.getOrder() != 0);
+ pkg.addActivity(activity);
+ }
+
+ result = activityResult;
+ break;
+ default:
+ result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ if (TextUtils.isEmpty(pkg.getStaticSharedLibName())) {
+ // Add a hidden app detail activity to normal apps which forwards user to App Details
+ // page.
+ ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
+ if (a.isError()) {
+ // Error should be impossible here, as the only failure case as of SDK R is a
+ // string validation error on a constant ":app_details" string passed in by the
+ // parsing code itself. For this reason, this is just a hard failure instead of
+ // deferred.
+ return input.error(a);
+ }
+
+ pkg.addActivity(a.getResult());
+ }
+
+ if (hasActivityOrder) {
+ pkg.sortActivities();
+ }
+ if (hasReceiverOrder) {
+ pkg.sortReceivers();
+ }
+ if (hasServiceOrder) {
+ pkg.sortServices();
+ }
+
+ // Must be run 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(pkg);
+ setMinAspectRatio(pkg);
+ setSupportsSizeChanges(pkg);
+
+ pkg.setHasDomainUrls(hasDomainURLs(pkg));
+
+ return input.success(pkg);
+ }
+
+ /**
+ * Collection of single-line, no (or little) logic assignments. Separated for readability.
+ *
+ * Flags are separated by type and by default value. They are sorted alphabetically within each
+ * section.
+ */
+ private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
+ int targetSdk = pkg.getTargetSdkVersion();
+ //@formatter:off
+ // CHECKSTYLE:off
+ pkg
+ // Default true
+ .setAllowBackup(bool(true, R.styleable.AndroidManifestApplication_allowBackup, sa))
+ .setAllowClearUserData(bool(true, R.styleable.AndroidManifestApplication_allowClearUserData, sa))
+ .setAllowClearUserDataOnFailedRestore(bool(true, R.styleable.AndroidManifestApplication_allowClearUserDataOnFailedRestore, sa))
+ .setAllowNativeHeapPointerTagging(bool(true, R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, sa))
+ .setEnabled(bool(true, R.styleable.AndroidManifestApplication_enabled, sa))
+ .setExtractNativeLibs(bool(true, R.styleable.AndroidManifestApplication_extractNativeLibs, sa))
+ .setHasCode(bool(true, R.styleable.AndroidManifestApplication_hasCode, sa))
+ // Default false
+ .setAllowTaskReparenting(bool(false, R.styleable.AndroidManifestApplication_allowTaskReparenting, sa))
+ .setCantSaveState(bool(false, R.styleable.AndroidManifestApplication_cantSaveState, sa))
+ .setCrossProfile(bool(false, R.styleable.AndroidManifestApplication_crossProfile, sa))
+ .setDebuggable(bool(false, R.styleable.AndroidManifestApplication_debuggable, sa))
+ .setDefaultToDeviceProtectedStorage(bool(false, R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage, sa))
+ .setDirectBootAware(bool(false, R.styleable.AndroidManifestApplication_directBootAware, sa))
+ .setForceQueryable(bool(false, R.styleable.AndroidManifestApplication_forceQueryable, sa))
+ .setGame(bool(false, R.styleable.AndroidManifestApplication_isGame, sa))
+ .setHasFragileUserData(bool(false, R.styleable.AndroidManifestApplication_hasFragileUserData, sa))
+ .setLargeHeap(bool(false, R.styleable.AndroidManifestApplication_largeHeap, sa))
+ .setMultiArch(bool(false, R.styleable.AndroidManifestApplication_multiArch, sa))
+ .setPreserveLegacyExternalStorage(bool(false, R.styleable.AndroidManifestApplication_preserveLegacyExternalStorage, sa))
+ .setRequiredForAllUsers(bool(false, R.styleable.AndroidManifestApplication_requiredForAllUsers, sa))
+ .setSupportsRtl(bool(false, R.styleable.AndroidManifestApplication_supportsRtl, sa))
+ .setTestOnly(bool(false, R.styleable.AndroidManifestApplication_testOnly, sa))
+ .setUseEmbeddedDex(bool(false, R.styleable.AndroidManifestApplication_useEmbeddedDex, sa))
+ .setUsesNonSdkApi(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa))
+ .setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa))
+ .setAutoRevokePermissions(anInt(R.styleable.AndroidManifestApplication_autoRevokePermissions, sa))
+ .setAttributionsAreUserVisible(bool(false, R.styleable.AndroidManifestApplication_attributionsAreUserVisible, sa))
+ // targetSdkVersion gated
+ .setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
+ .setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
+ .setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
+ .setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
+ // Ints Default 0
+ .setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
+ // Ints
+ .setCategory(anInt(ApplicationInfo.CATEGORY_UNDEFINED, R.styleable.AndroidManifestApplication_appCategory, sa))
+ // Floats Default 0f
+ .setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
+ .setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
+ // Resource ID
+ .setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
+ .setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
+ .setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
+ .setLogo(resId(R.styleable.AndroidManifestApplication_logo, sa))
+ .setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
+ .setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
+ .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
+ .setDataExtractionRules(
+ resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
+ // Strings
+ .setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
+ .setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa))
+ .setRestrictedAccountType(string(R.styleable.AndroidManifestApplication_restrictedAccountType, sa))
+ .setZygotePreloadName(string(R.styleable.AndroidManifestApplication_zygotePreloadName, sa))
+ // Non-Config String
+ .setPermission(nonConfigString(0, R.styleable.AndroidManifestApplication_permission, sa));
+ // CHECKSTYLE:on
+ //@formatter:on
+ }
+
+ /**
+ * For parsing non-MainComponents. Main ones have an order and some special handling which is
+ * done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources,
+ * XmlResourceParser, int)}.
+ */
+ private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser, int flags)
+ throws IOException, XmlPullParserException {
+ switch (tag) {
+ case "meta-data":
+ // TODO(b/135203078): I have no idea what this comment means
+ // note: application meta-data is stored off to the side, so it can
+ // remain null in the primary copy (we like to avoid extra copies because
+ // it can be large)
+ final ParseResult<Property> metaDataResult = parseMetaData(pkg, null, res,
+ parser, "<meta-data>", input);
+ if (metaDataResult.isSuccess() && metaDataResult.getResult() != null) {
+ pkg.setMetaData(metaDataResult.getResult().toBundle(pkg.getMetaData()));
+ }
+ return metaDataResult;
+ case "property":
+ final ParseResult<Property> propertyResult = parseMetaData(pkg, null, res,
+ parser, "<property>", input);
+ if (propertyResult.isSuccess()) {
+ pkg.addProperty(propertyResult.getResult());
+ }
+ return propertyResult;
+ case "static-library":
+ return parseStaticLibrary(pkg, res, parser, input);
+ case "library":
+ return parseLibrary(pkg, res, parser, input);
+ case "uses-static-library":
+ return parseUsesStaticLibrary(input, pkg, res, parser);
+ case "uses-library":
+ return parseUsesLibrary(input, pkg, res, parser);
+ case "uses-native-library":
+ return parseUsesNativeLibrary(input, pkg, res, parser);
+ case "processes":
+ return parseProcesses(input, pkg, res, parser, mSeparateProcesses, flags);
+ case "uses-package":
+ // Dependencies for app installers; we don't currently try to
+ // enforce this.
+ return input.success(null);
+ case "profileable":
+ return parseProfileable(input, pkg, res, parser);
+ default:
+ return ParsingUtils.unknownTag("<application>", pkg, parser, input);
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseStaticLibrary(
+ ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, ParseInput input) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestStaticLibrary);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ R.styleable.AndroidManifestStaticLibrary_name);
+ final int version = sa.getInt(
+ R.styleable.AndroidManifestStaticLibrary_version, -1);
+ final int versionMajor = sa.getInt(
+ R.styleable.AndroidManifestStaticLibrary_versionMajor,
+ 0);
+
+ // Since the app canot run without a static lib - fail if malformed
+ if (lname == null || version < 0) {
+ return input.error("Bad static-library declaration name: " + lname
+ + " version: " + version);
+ } else if (pkg.getSharedUserId() != null) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
+ "sharedUserId not allowed in static shared library"
+ );
+ } else if (pkg.getStaticSharedLibName() != null) {
+ return input.error("Multiple static-shared libs for package "
+ + pkg.getPackageName());
+ }
+
+ return input.success(pkg.setStaticSharedLibName(lname.intern())
+ .setStaticSharedLibVersion(
+ PackageInfo.composeLongVersionCode(versionMajor, version))
+ .setStaticSharedLibrary(true));
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseLibrary(
+ ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, ParseInput input) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestLibrary);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(R.styleable.AndroidManifestLibrary_name);
+
+ if (lname != null) {
+ lname = lname.intern();
+ if (!ArrayUtils.contains(pkg.getLibraryNames(), lname)) {
+ pkg.addLibraryName(lname);
+ }
+ }
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseUsesStaticLibrary(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesStaticLibrary);
+ try {
+ // Note: don't allow this value to be a reference to a resource that may change.
+ String lname = sa.getNonResourceString(
+ R.styleable.AndroidManifestUsesLibrary_name);
+ final int version = sa.getInt(
+ R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
+ String certSha256Digest = sa.getNonResourceString(R.styleable
+ .AndroidManifestUsesStaticLibrary_certDigest);
+
+ // Since an APK providing a static shared lib can only provide the lib - fail if
+ // malformed
+ if (lname == null || version < 0 || certSha256Digest == null) {
+ return input.error("Bad uses-static-library declaration name: " + lname
+ + " version: " + version + " certDigest" + certSha256Digest);
+ }
+
+ // Can depend only on one version of the same library
+ List<String> usesStaticLibraries = pkg.getUsesStaticLibraries();
+ if (usesStaticLibraries.contains(lname)) {
+ return input.error(
+ "Depending on multiple versions of static library " + lname);
+ }
+
+ lname = lname.intern();
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+
+ // Fot apps targeting O-MR1 we require explicit enumeration of all certs.
+ String[] additionalCertSha256Digests = EmptyArray.STRING;
+ if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O_MR1) {
+ ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser);
+ if (certResult.isError()) {
+ return input.error(certResult);
+ }
+ additionalCertSha256Digests = certResult.getResult();
+ }
+
+ final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
+ certSha256Digests[0] = certSha256Digest;
+ System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+ 1, additionalCertSha256Digests.length);
+
+ return input.success(pkg.addUsesStaticLibrary(lname)
+ .addUsesStaticLibraryVersion(version)
+ .addUsesStaticLibraryCertDigests(certSha256Digests));
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseUsesLibrary(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesLibrary);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(R.styleable.AndroidManifestUsesLibrary_name);
+ boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesLibrary_required, true);
+
+ if (lname != null) {
+ lname = lname.intern();
+ if (req) {
+ // Upgrade to treat as stronger constraint
+ pkg.addUsesLibrary(lname)
+ .removeUsesOptionalLibrary(lname);
+ } else {
+ // Ignore if someone already defined as required
+ if (!ArrayUtils.contains(pkg.getUsesLibraries(), lname)) {
+ pkg.addUsesOptionalLibrary(lname);
+ }
+ }
+ }
+
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseUsesNativeLibrary(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesNativeLibrary);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ R.styleable.AndroidManifestUsesNativeLibrary_name);
+ boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesNativeLibrary_required,
+ true);
+
+ if (lname != null) {
+ if (req) {
+ // Upgrade to treat as stronger constraint
+ pkg.addUsesNativeLibrary(lname)
+ .removeUsesOptionalNativeLibrary(lname);
+ } else {
+ // Ignore if someone already defined as required
+ if (!ArrayUtils.contains(pkg.getUsesNativeLibraries(), lname)) {
+ pkg.addUsesOptionalNativeLibrary(lname);
+ }
+ }
+ }
+
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseProcesses(ParseInput input, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser, String[] separateProcesses, int flags)
+ throws IOException, XmlPullParserException {
+ ParseResult<ArrayMap<String, ParsedProcess>> result =
+ ParsedProcessUtils.parseProcesses(separateProcesses, pkg, res, parser, flags,
+ input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(pkg.setProcesses(result.getResult()));
+ }
+
+ @NonNull
+ private static ParseResult<ParsingPackage> parseProfileable(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProfileable);
+ try {
+ ParsingPackage newPkg = pkg.setProfileableByShell(pkg.isProfileableByShell()
+ || bool(false, R.styleable.AndroidManifestProfileable_shell, sa));
+ return input.success(newPkg.setProfileable(newPkg.isProfileable()
+ && bool(true, R.styleable.AndroidManifestProfileable_enabled, sa)));
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
+ Resources resources, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ String[] certSha256Digests = EmptyArray.STRING;
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String nodeName = parser.getName();
+ if (nodeName.equals("additional-certificate")) {
+ TypedArray sa = resources.obtainAttributes(parser,
+ R.styleable.AndroidManifestAdditionalCertificate);
+ try {
+ String certSha256Digest = sa.getNonResourceString(
+ R.styleable.AndroidManifestAdditionalCertificate_certDigest);
+
+ if (TextUtils.isEmpty(certSha256Digest)) {
+ return input.error("Bad additional-certificate declaration with empty"
+ + " certDigest:" + certSha256Digest);
+ }
+
+
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+ certSha256Digests = ArrayUtils.appendElement(String.class,
+ certSha256Digests, certSha256Digest);
+ } finally {
+ sa.recycle();
+ }
+ }
+ }
+
+ return input.success(certSha256Digests);
+ }
+
+ /**
+ * Generate activity object that forwards user to App Details page automatically.
+ * This activity should be invisible to user and user should not know or see it.
+ */
+ @NonNull
+ private static ParseResult<ParsedActivity> generateAppDetailsHiddenActivity(ParseInput input,
+ ParsingPackage pkg) {
+ String packageName = pkg.getPackageName();
+ ParseResult<String> result = ComponentParseUtils.buildTaskAffinityName(
+ packageName, packageName, ":app_details", input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ String taskAffinity = result.getResult();
+
+ // Build custom App Details activity info instead of parsing it from xml
+ return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
+ pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
+ pkg.isBaseHardwareAccelerated()));
+ }
+
+ /**
+ * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
+ *
+ * This is distinct from any of the functionality of app links domain verification, and cannot
+ * be converted to remain backwards compatible. It's possible the presence of this flag does
+ * not indicate a valid package for domain verification.
+ */
+ private static boolean hasDomainURLs(ParsingPackage pkg) {
+ final List<ParsedActivity> activities = pkg.getActivities();
+ final int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ List<ParsedIntentInfo> filters = activity.getIntents();
+ final int filtersSize = filters.size();
+ for (int filtersIndex = 0; filtersIndex < filtersSize; filtersIndex++) {
+ ParsedIntentInfo aii = filters.get(filtersIndex);
+ if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
+ if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
+ if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the max aspect ratio of every child activity that doesn't already have an aspect
+ * ratio set.
+ */
+ private static void setMaxAspectRatio(ParsingPackage pkg) {
+ // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
+ // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
+ float maxAspectRatio = pkg.getTargetSdkVersion() < O ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+
+ float packageMaxAspectRatio = pkg.getMaxAspectRatio();
+ if (packageMaxAspectRatio != 0) {
+ // Use the application max aspect ration as default if set.
+ maxAspectRatio = packageMaxAspectRatio;
+ } else {
+ Bundle appMetaData = pkg.getMetaData();
+ if (appMetaData != null && appMetaData.containsKey(METADATA_MAX_ASPECT_RATIO)) {
+ maxAspectRatio = appMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
+ }
+ }
+
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ // If the max aspect ratio for the activity has already been set, skip.
+ if (activity.getMaxAspectRatio() != null) {
+ continue;
+ }
+
+ // By default we prefer to use a values defined on the activity directly than values
+ // defined on the application. We do not check the styled attributes on the activity
+ // as it would have already been set when we processed the activity. We wait to
+ // process the meta data here since this method is called at the end of processing
+ // the application and all meta data is guaranteed.
+ final float activityAspectRatio = activity.getMetaData() != null
+ ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
+ : maxAspectRatio;
+
+ activity.setMaxAspectRatio(activity.getResizeMode(), activityAspectRatio);
+ }
+ }
+
+ /**
+ * Sets the min aspect ratio of every child activity that doesn't already have an aspect
+ * ratio set.
+ */
+ private void setMinAspectRatio(ParsingPackage pkg) {
+ // Use the application max aspect ration as default if set.
+ final float minAspectRatio = pkg.getMinAspectRatio();
+
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ if (activity.getMinAspectRatio() == null) {
+ activity.setMinAspectRatio(activity.getResizeMode(), minAspectRatio);
+ }
+ }
+ }
+
+ private void setSupportsSizeChanges(ParsingPackage pkg) {
+ final Bundle appMetaData = pkg.getMetaData();
+ final boolean supportsSizeChanges = appMetaData != null
+ && appMetaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false);
+
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ if (supportsSizeChanges || (activity.getMetaData() != null
+ && activity.getMetaData().getBoolean(
+ METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+ activity.setSupportsSizeChanges(true);
+ }
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseOverlay(ParseInput input, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestResourceOverlay);
+ try {
+ String target = sa.getString(R.styleable.AndroidManifestResourceOverlay_targetPackage);
+ int priority = anInt(0, R.styleable.AndroidManifestResourceOverlay_priority, sa);
+
+ if (target == null) {
+ return input.error("<overlay> does not specify a target package");
+ } else if (priority < 0 || priority > 9999) {
+ return input.error("<overlay> priority must be between 0 and 9999");
+ }
+
+ // check to see if overlay should be excluded based on system property condition
+ String propName = sa.getString(
+ R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName);
+ String propValue = sa.getString(
+ R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue);
+ if (!PackageParser.checkRequiredSystemProperties(propName, propValue)) {
+ String message = "Skipping target and overlay pair " + target + " and "
+ + pkg.getBaseApkPath()
+ + ": overlay ignored due to required system property: "
+ + propName + " with value: " + propValue;
+ Slog.i(TAG, message);
+ return input.skip(message);
+ }
+
+ return input.success(pkg.setOverlay(true)
+ .setOverlayTarget(target)
+ .setOverlayPriority(priority)
+ .setOverlayTargetName(
+ sa.getString(R.styleable.AndroidManifestResourceOverlay_targetName))
+ .setOverlayCategory(
+ sa.getString(R.styleable.AndroidManifestResourceOverlay_category))
+ .setOverlayIsStatic(
+ bool(false, R.styleable.AndroidManifestResourceOverlay_isStatic, sa)));
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseProtectedBroadcast(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProtectedBroadcast);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String name = nonResString(R.styleable.AndroidManifestProtectedBroadcast_name, sa);
+ if (name != null) {
+ pkg.addProtectedBroadcast(name);
+ }
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseSupportScreens(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestSupportsScreens);
+ try {
+ int requiresSmallestWidthDp = anInt(0,
+ R.styleable.AndroidManifestSupportsScreens_requiresSmallestWidthDp, sa);
+ int compatibleWidthLimitDp = anInt(0,
+ R.styleable.AndroidManifestSupportsScreens_compatibleWidthLimitDp, sa);
+ int largestWidthLimitDp = anInt(0,
+ R.styleable.AndroidManifestSupportsScreens_largestWidthLimitDp, sa);
+
+ // This is a trick to get a boolean and still able to detect
+ // if a value was actually set.
+ return input.success(pkg
+ .setSupportsSmallScreens(
+ anInt(1, R.styleable.AndroidManifestSupportsScreens_smallScreens, sa))
+ .setSupportsNormalScreens(
+ anInt(1, R.styleable.AndroidManifestSupportsScreens_normalScreens, sa))
+ .setSupportsLargeScreens(
+ anInt(1, R.styleable.AndroidManifestSupportsScreens_largeScreens, sa))
+ .setSupportsExtraLargeScreens(
+ anInt(1, R.styleable.AndroidManifestSupportsScreens_xlargeScreens, sa))
+ .setResizeable(
+ anInt(1, R.styleable.AndroidManifestSupportsScreens_resizeable, sa))
+ .setAnyDensity(
+ anInt(1, R.styleable.AndroidManifestSupportsScreens_anyDensity, sa))
+ .setRequiresSmallestWidthDp(requiresSmallestWidthDp)
+ .setCompatibleWidthLimitDp(compatibleWidthLimitDp)
+ .setLargestWidthLimitDp(largestWidthLimitDp));
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseInstrumentation(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult<ParsedInstrumentation> result = ParsedInstrumentationUtils.parseInstrumentation(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addInstrumentation(result.getResult()));
+ }
+
+ private static ParseResult<ParsingPackage> parseOriginalPackage(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestOriginalPackage);
+ try {
+ String orig = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestOriginalPackage_name,
+ 0);
+ if (!pkg.getPackageName().equals(orig)) {
+ if (pkg.getOriginalPackages().isEmpty()) {
+ pkg.setRealPackage(pkg.getPackageName());
+ }
+ pkg.addOriginalPackage(orig);
+ }
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult<ParsingPackage> parseAdoptPermissions(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestOriginalPackage);
+ try {
+ String name = nonConfigString(0, R.styleable.AndroidManifestOriginalPackage_name, sa);
+ if (name != null) {
+ pkg.addAdoptPermission(name);
+ }
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static void convertNewPermissions(ParsingPackage pkg) {
+ final int NP = PackageParser.NEW_PERMISSIONS.length;
+ StringBuilder newPermsMsg = null;
+ for (int ip = 0; ip < NP; ip++) {
+ final PackageParser.NewPermissionInfo npi
+ = PackageParser.NEW_PERMISSIONS[ip];
+ if (pkg.getTargetSdkVersion() >= npi.sdkVersion) {
+ break;
+ }
+ if (!pkg.getRequestedPermissions().contains(npi.name)) {
+ if (newPermsMsg == null) {
+ newPermsMsg = new StringBuilder(128);
+ newPermsMsg.append(pkg.getPackageName());
+ newPermsMsg.append(": compat added ");
+ } else {
+ newPermsMsg.append(' ');
+ }
+ newPermsMsg.append(npi.name);
+ pkg.addUsesPermission(new ParsedUsesPermission(npi.name, 0))
+ .addImplicitPermission(npi.name);
+ }
+ }
+ if (newPermsMsg != null) {
+ Slog.i(TAG, newPermsMsg.toString());
+ }
+ }
+
+ private void convertSplitPermissions(ParsingPackage pkg) {
+ final int listSize = mSplitPermissionInfos.size();
+ for (int is = 0; is < listSize; is++) {
+ final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
+ List<String> requestedPermissions = pkg.getRequestedPermissions();
+ if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
+ || !requestedPermissions.contains(spi.getSplitPermission())) {
+ continue;
+ }
+ final List<String> newPerms = spi.getNewPermissions();
+ for (int in = 0; in < newPerms.size(); in++) {
+ final String perm = newPerms.get(in);
+ if (!requestedPermissions.contains(perm)) {
+ pkg.addUsesPermission(new ParsedUsesPermission(perm, 0))
+ .addImplicitPermission(perm);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is a pre-density application which will get scaled - instead of being pixel perfect.
+ * This type of application is not resizable.
+ *
+ * @param pkg The package which needs to be marked as unresizable.
+ */
+ private static void adjustPackageToBeUnresizeableAndUnpipable(ParsingPackage pkg) {
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ activity.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setFlags(activity.getFlags() & ~FLAG_SUPPORTS_PICTURE_IN_PICTURE);
+ }
+ }
+
+ /**
+ * Check if the given name is valid.
+ *
+ * @param name The name to check.
+ * @param requireSeparator {@code true} if the name requires containing a separator at least.
+ * @param requireFilename {@code true} to apply file name validation to the given name. It also
+ * limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
+ * @return Success if it's valid.
+ */
+ public static String validateName(String name, boolean requireSeparator,
+ boolean requireFilename) {
+ final int N = name.length();
+ boolean hasSep = false;
+ boolean front = true;
+ for (int i = 0; i < N; i++) {
+ final char c = name.charAt(i);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ front = false;
+ continue;
+ }
+ if (!front) {
+ if ((c >= '0' && c <= '9') || c == '_') {
+ continue;
+ }
+ }
+ if (c == '.') {
+ hasSep = true;
+ front = true;
+ continue;
+ }
+ return "bad character '" + c + "'";
+ }
+ if (requireFilename) {
+ if (!FileUtils.isValidExtFilename(name)) {
+ return "Invalid filename";
+ } else if (N > MAX_FILE_NAME_SIZE) {
+ return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
+ }
+ }
+ return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
+ }
+
+ /**
+ * @see #validateName(String, boolean, boolean)
+ */
+ public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+ boolean requireFilename) {
+ final String errorMessage = validateName(name, requireSeparator, requireFilename);
+ if (errorMessage != null) {
+ return input.error(errorMessage);
+ }
+ return input.success(null);
+ }
+
+ /**
+ * Parse a meta data defined on the enclosing tag.
+ * <p>Meta data can be defined by either <meta-data> or <property> elements.
+ */
+ public static ParseResult<Property> parseMetaData(ParsingPackage pkg, ParsedComponent component,
+ Resources res, XmlResourceParser parser, String tagName, ParseInput input) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestMetaData);
+ try {
+ final Property property;
+ final String name = TextUtils.safeIntern(
+ nonConfigString(0, R.styleable.AndroidManifestMetaData_name, sa));
+ if (name == null) {
+ return input.error(tagName + " requires an android:name attribute");
+ }
+
+ final String packageName = pkg.getPackageName();
+ final String className = component != null ? component.getName() : null;
+ TypedValue v = sa.peekValue(R.styleable.AndroidManifestMetaData_resource);
+ if (v != null && v.resourceId != 0) {
+ property = new Property(name, v.resourceId, true, packageName, className);
+ } else {
+ v = sa.peekValue(R.styleable.AndroidManifestMetaData_value);
+ if (v != null) {
+ if (v.type == TypedValue.TYPE_STRING) {
+ final CharSequence cs = v.coerceToString();
+ final String stringValue = cs != null ? cs.toString() : null;
+ property = new Property(name, stringValue, packageName, className);
+ } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+ property = new Property(name, v.data != 0, packageName, className);
+ } else if (v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT) {
+ property = new Property(name, v.data, false, packageName, className);
+ } else if (v.type == TypedValue.TYPE_FLOAT) {
+ property = new Property(name, v.getFloat(), packageName, className);
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG,
+ tagName + " only supports string, integer, float, color, "
+ + "boolean, and resource reference types: "
+ + parser.getName() + " at "
+ + pkg.getBaseApkPath() + " "
+ + parser.getPositionDescription());
+ property = null;
+ } else {
+ return input.error(tagName + " only supports string, integer, float, "
+ + "color, boolean, and resource reference types");
+ }
+ }
+ } else {
+ return input.error(tagName + " requires an android:value "
+ + "or android:resource attribute");
+ }
+ }
+ return input.success(property);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ /**
+ * Collect certificates from all the APKs described in the given package. Also asserts that
+ * all APK contents are signed correctly and consistently.
+ *
+ * TODO(b/155513789): Remove this in favor of collecting certificates during the original parse
+ * call if requested. Leaving this as an optional method for the caller means we have to
+ * construct a dummy ParseInput.
+ */
+ @CheckResult
+ public static SigningDetails getSigningDetails(ParsingPackageRead pkg, boolean skipVerify)
+ throws PackageParserException {
+ SigningDetails signingDetails = SigningDetails.UNKNOWN;
+
+ ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ ParseResult<SigningDetails> result = getSigningDetails(
+ input,
+ pkg.getBaseApkPath(),
+ skipVerify,
+ pkg.isStaticSharedLibrary(),
+ signingDetails,
+ pkg.getTargetSdkVersion()
+ );
+ if (result.isError()) {
+ throw new PackageParser.PackageParserException(result.getErrorCode(),
+ result.getErrorMessage(), result.getException());
+ }
+
+ signingDetails = result.getResult();
+
+ String[] splitCodePaths = pkg.getSplitCodePaths();
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ for (int i = 0; i < splitCodePaths.length; i++) {
+ result = getSigningDetails(
+ input,
+ splitCodePaths[i],
+ skipVerify,
+ pkg.isStaticSharedLibrary(),
+ signingDetails,
+ pkg.getTargetSdkVersion()
+ );
+ if (result.isError()) {
+ throw new PackageParser.PackageParserException(result.getErrorCode(),
+ result.getErrorMessage(), result.getException());
+ }
+
+
+ signingDetails = result.getResult();
+ }
+ }
+ return signingDetails;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ @CheckResult
+ public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
+ String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary,
+ @NonNull SigningDetails existingSigningDetails, int targetSdk) {
+ int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+ targetSdk);
+ if (isStaticSharedLibrary) {
+ // must use v2 signing scheme
+ minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
+ }
+ SigningDetails verified;
+ try {
+ if (skipVerify) {
+ // systemDir APKs are already trusted, save time by not verifying; since the
+ // signature is not verified and some system apps can have their V2+ signatures
+ // stripped allow pulling the certs from the jar signature.
+ verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
+ baseCodePath, SigningDetails.SignatureSchemeVersion.JAR);
+ } else {
+ verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme);
+ }
+ } catch (PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed collecting certificates for " + baseCodePath, e);
+ }
+
+ // Verify that entries are signed consistently with the first pkg
+ // we encountered. Note that for splits, certificates may have
+ // already been populated during an earlier parse of a base APK.
+ if (existingSigningDetails == SigningDetails.UNKNOWN) {
+ return input.success(verified);
+ } else {
+ if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) {
+ return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ baseCodePath + " has mismatched certificates");
+ }
+
+ return input.success(existingSigningDetails);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static void readConfigUseRoundIcon(Resources r) {
+ if (r != null) {
+ sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ return;
+ }
+
+ final ApplicationInfo androidAppInfo;
+ try {
+ androidAppInfo = ActivityThread.getPackageManager().getApplicationInfo(
+ "android", 0 /* flags */,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ final Resources systemResources = Resources.getSystem();
+
+ // Create in-flight as this overlayable resource is only used when config changes
+ final Resources overlayableRes = ResourcesManager.getInstance().getResources(
+ null /* activityToken */,
+ null /* resDir */,
+ null /* splitResDirs */,
+ androidAppInfo.resourceDirs,
+ androidAppInfo.overlayPaths,
+ androidAppInfo.sharedLibraryFiles,
+ null /* overrideDisplayId */,
+ null /* overrideConfig */,
+ systemResources.getCompatibilityInfo(),
+ systemResources.getClassLoader(),
+ null /* loaders */);
+
+ sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ }
+
+ /*
+ The following set of methods makes code easier to read by re-ordering the TypedArray methods.
+
+ The first parameter is the default, which is the most important to understand for someone
+ reading through the parsing code.
+
+ That's followed by the attribute name, which is usually irrelevant during reading because
+ it'll look like setSomeValue(true, R.styleable.ReallyLongParentName_SomeValueAttr... and
+ the "setSomeValue" part is enough to communicate what the line does.
+
+ Last comes the TypedArray, which is by far the least important since each try-with-resources
+ should only have 1.
+ */
+
+ // Note there is no variant of bool without a defaultValue parameter, since explicit true/false
+ // is important to specify when adding an attribute.
+ private static boolean bool(boolean defaultValue, @StyleableRes int attribute, TypedArray sa) {
+ return sa.getBoolean(attribute, defaultValue);
+ }
+
+ private static float aFloat(float defaultValue, @StyleableRes int attribute, TypedArray sa) {
+ return sa.getFloat(attribute, defaultValue);
+ }
+
+ private static float aFloat(@StyleableRes int attribute, TypedArray sa) {
+ return sa.getFloat(attribute, 0f);
+ }
+
+ private static int anInt(int defaultValue, @StyleableRes int attribute, TypedArray sa) {
+ return sa.getInt(attribute, defaultValue);
+ }
+
+ private static int anInteger(int defaultValue, @StyleableRes int attribute, TypedArray sa) {
+ return sa.getInteger(attribute, defaultValue);
+ }
+
+ private static int anInt(@StyleableRes int attribute, TypedArray sa) {
+ return sa.getInt(attribute, 0);
+ }
+
+ @AnyRes
+ private static int resId(@StyleableRes int attribute, TypedArray sa) {
+ return sa.getResourceId(attribute, 0);
+ }
+
+ private static String string(@StyleableRes int attribute, TypedArray sa) {
+ return sa.getString(attribute);
+ }
+
+ private static String nonConfigString(int allowedChangingConfigs, @StyleableRes int attribute,
+ TypedArray sa) {
+ return sa.getNonConfigurationString(attribute, allowedChangingConfigs);
+ }
+
+ private static String nonResString(@StyleableRes int index, TypedArray sa) {
+ return sa.getNonResourceString(index);
+ }
+
+ /**
+ * Writes the keyset mapping to the provided package. {@code null} mappings are permitted.
+ */
+ public static void writeKeySetMapping(@NonNull Parcel dest,
+ @NonNull Map<String, ArraySet<PublicKey>> keySetMapping) {
+ if (keySetMapping == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ final int N = keySetMapping.size();
+ dest.writeInt(N);
+
+ for (String key : keySetMapping.keySet()) {
+ dest.writeString(key);
+ ArraySet<PublicKey> keys = keySetMapping.get(key);
+ if (keys == null) {
+ dest.writeInt(-1);
+ continue;
+ }
+
+ final int M = keys.size();
+ dest.writeInt(M);
+ for (int j = 0; j < M; j++) {
+ dest.writeSerializable(keys.valueAt(j));
+ }
+ }
+ }
+
+ /**
+ * Reads a keyset mapping from the given parcel at the given data position. May return
+ * {@code null} if the serialized mapping was {@code null}.
+ */
+ @NonNull
+ public static ArrayMap<String, ArraySet<PublicKey>> readKeySetMapping(@NonNull Parcel in) {
+ final int N = in.readInt();
+ if (N == -1) {
+ return null;
+ }
+
+ ArrayMap<String, ArraySet<PublicKey>> keySetMapping = new ArrayMap<>();
+ for (int i = 0; i < N; ++i) {
+ String key = in.readString();
+ final int M = in.readInt();
+ if (M == -1) {
+ keySetMapping.put(key, null);
+ continue;
+ }
+
+ ArraySet<PublicKey> keys = new ArraySet<>(M);
+ for (int j = 0; j < M; ++j) {
+ PublicKey pk = (PublicKey) in.readSerializable();
+ keys.add(pk);
+ }
+
+ keySetMapping.put(key, keys);
+ }
+
+ return keySetMapping;
+ }
+
+
+ /**
+ * Callback interface for retrieving information that may be needed while parsing
+ * a package.
+ */
+ public interface Callback {
+ boolean hasFeature(String feature);
+
+ ParsingPackage startParsingPackage(@NonNull String packageName,
+ @NonNull String baseApkPath, @NonNull String path,
+ @NonNull TypedArray manifestArray, boolean isCoreApp);
+ }
+}
diff --git a/android/content/pm/parsing/ParsingUtils.java b/android/content/pm/parsing/ParsingUtils.java
new file mode 100644
index 0000000..07ec6a8
--- /dev/null
+++ b/android/content/pm/parsing/ParsingUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.XmlResourceParser;
+import android.util.Slog;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide **/
+public class ParsingUtils {
+
+ public static final String TAG = "PackageParsing";
+
+ public static final String ANDROID_RES_NAMESPACE = "http://schemas.android.com/apk/res/android";
+
+ public static final int DEFAULT_MIN_SDK_VERSION = 1;
+ public static final int DEFAULT_TARGET_SDK_VERSION = 0;
+
+ @Nullable
+ public static String buildClassName(String pkg, CharSequence clsSeq) {
+ if (clsSeq == null || clsSeq.length() <= 0) {
+ return null;
+ }
+ String cls = clsSeq.toString();
+ char c = cls.charAt(0);
+ if (c == '.') {
+ return pkg + cls;
+ }
+ if (cls.indexOf('.') < 0) {
+ StringBuilder b = new StringBuilder(pkg);
+ b.append('.');
+ b.append(cls);
+ return b.toString();
+ }
+ return cls;
+ }
+
+ @NonNull
+ public static ParseResult unknownTag(String parentTag, ParsingPackage pkg,
+ XmlResourceParser parser, ParseInput input) throws IOException, XmlPullParserException {
+ if (PackageParser.RIGID_PARSER) {
+ return input.error("Bad element under " + parentTag + ": " + parser.getName());
+ }
+ Slog.w(TAG, "Unknown element under " + parentTag + ": "
+ + parser.getName() + " at " + pkg.getBaseApkPath() + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ return input.success(null); // Type doesn't matter
+ }
+}
diff --git a/android/content/pm/parsing/component/ComponentParseUtils.java b/android/content/pm/parsing/component/ComponentParseUtils.java
new file mode 100644
index 0000000..0403a25
--- /dev/null
+++ b/android/content/pm/parsing/component/ComponentParseUtils.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageUtils.validateName;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.PackageUserState;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class ComponentParseUtils {
+
+ public static boolean isImplicitlyExposedIntent(ParsedIntentInfo intentInfo) {
+ return intentInfo.hasCategory(Intent.CATEGORY_BROWSABLE)
+ || intentInfo.hasAction(Intent.ACTION_SEND)
+ || intentInfo.hasAction(Intent.ACTION_SENDTO)
+ || intentInfo.hasAction(Intent.ACTION_SEND_MULTIPLE);
+ }
+
+ static <Component extends ParsedComponent> ParseResult<Component> parseAllMetaData(
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, String tag,
+ Component component, ParseInput input) throws XmlPullParserException, IOException {
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult result;
+ if ("meta-data".equals(parser.getName())) {
+ result = ParsedComponentUtils.addMetaData(component, pkg, res, parser, input);
+ } else {
+ result = ParsingUtils.unknownTag(tag, pkg, parser, input);
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ return input.success(component);
+ }
+
+ @NonNull
+ public static ParseResult<String> buildProcessName(@NonNull String pkg, String defProc,
+ CharSequence procSeq, int flags, String[] separateProcesses, ParseInput input) {
+ if ((flags & ParsingPackageUtils.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals(
+ procSeq)) {
+ return input.success(defProc != null ? defProc : pkg);
+ }
+ if (separateProcesses != null) {
+ for (int i = separateProcesses.length - 1; i >= 0; i--) {
+ String sp = separateProcesses[i];
+ if (sp.equals(pkg) || sp.equals(defProc) || sp.contentEquals(procSeq)) {
+ return input.success(pkg);
+ }
+ }
+ }
+ if (procSeq == null || procSeq.length() <= 0) {
+ return input.success(defProc);
+ }
+
+ ParseResult<String> nameResult = ComponentParseUtils.buildCompoundName(pkg, procSeq,
+ "process", input);
+ return input.success(TextUtils.safeIntern(nameResult.getResult()));
+ }
+
+ @NonNull
+ public static ParseResult<String> buildTaskAffinityName(String pkg, String defProc,
+ CharSequence procSeq, ParseInput input) {
+ if (procSeq == null) {
+ return input.success(defProc);
+ }
+ if (procSeq.length() <= 0) {
+ return input.success(null);
+ }
+ return buildCompoundName(pkg, procSeq, "taskAffinity", input);
+ }
+
+ public static ParseResult<String> buildCompoundName(String pkg, CharSequence procSeq,
+ String type, ParseInput input) {
+ String proc = procSeq.toString();
+ char c = proc.charAt(0);
+ if (pkg != null && c == ':') {
+ if (proc.length() < 2) {
+ return input.error("Bad " + type + " name " + proc + " in package " + pkg
+ + ": must be at least two characters");
+ }
+ String subName = proc.substring(1);
+ final ParseResult<?> nameResult = validateName(input, subName, false, false);
+ if (nameResult.isError()) {
+ return input.error("Invalid " + type + " name " + proc + " in package " + pkg
+ + ": " + nameResult.getErrorMessage());
+ }
+ return input.success(pkg + proc);
+ }
+ if (!"system".equals(proc)) {
+ final ParseResult<?> nameResult = validateName(input, proc, true, false);
+ if (nameResult.isError()) {
+ return input.error("Invalid " + type + " name " + proc + " in package " + pkg
+ + ": " + nameResult.getErrorMessage());
+ }
+ }
+ return input.success(proc);
+ }
+
+ public static int flag(int flag, @AttrRes int attribute, TypedArray typedArray) {
+ return typedArray.getBoolean(attribute, false) ? flag : 0;
+ }
+
+ public static int flag(int flag, @AttrRes int attribute, boolean defaultValue,
+ TypedArray typedArray) {
+ return typedArray.getBoolean(attribute, defaultValue) ? flag : 0;
+ }
+
+ /**
+ * This is not state aware. Avoid and access through PackageInfoUtils in the system server.
+ */
+ @Nullable
+ public static CharSequence getNonLocalizedLabel(
+ ParsedComponent component) {
+ return component.nonLocalizedLabel;
+ }
+
+ /**
+ * This is not state aware. Avoid and access through PackageInfoUtils in the system server.
+ *
+ * This is a method of the utility class to discourage use.
+ */
+ public static int getIcon(ParsedComponent component) {
+ return component.icon;
+ }
+
+ public static boolean isMatch(PackageUserState state, boolean isSystem,
+ boolean isPackageEnabled, ParsedMainComponent component, int flags) {
+ return state.isMatch(isSystem, isPackageEnabled, component.isEnabled(),
+ component.isDirectBootAware(), component.getName(), flags);
+ }
+
+ public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled,
+ ParsedMainComponent parsedComponent, int flags) {
+ return state.isEnabled(isPackageEnabled, parsedComponent.isEnabled(),
+ parsedComponent.getName(), flags);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedActivity.java b/android/content/pm/parsing/component/ParsedActivity.java
new file mode 100644
index 0000000..6f478ac
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedActivity.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+
+import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide **/
+public class ParsedActivity extends ParsedMainComponent {
+
+ int theme;
+ int uiOptions;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String targetActivity;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String parentActivityName;
+ @Nullable
+ String taskAffinity;
+ int privateFlags;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String permission;
+
+ int launchMode;
+ int documentLaunchMode;
+ int maxRecents;
+ int configChanges;
+ int softInputMode;
+ int persistableMode;
+ int lockTaskLaunchMode;
+
+ int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+ @Nullable
+ private Float maxAspectRatio;
+
+ @Nullable
+ private Float minAspectRatio;
+
+ private boolean supportsSizeChanges;
+
+ @Nullable
+ String requestedVrComponent;
+ int rotationAnimation = -1;
+ int colorMode;
+
+ @Nullable
+ ActivityInfo.WindowLayout windowLayout;
+
+ public ParsedActivity(ParsedActivity other) {
+ super(other);
+ this.theme = other.theme;
+ this.uiOptions = other.uiOptions;
+ this.targetActivity = other.targetActivity;
+ this.parentActivityName = other.parentActivityName;
+ this.taskAffinity = other.taskAffinity;
+ this.privateFlags = other.privateFlags;
+ this.permission = other.permission;
+ this.launchMode = other.launchMode;
+ this.documentLaunchMode = other.documentLaunchMode;
+ this.maxRecents = other.maxRecents;
+ this.configChanges = other.configChanges;
+ this.softInputMode = other.softInputMode;
+ this.persistableMode = other.persistableMode;
+ this.lockTaskLaunchMode = other.lockTaskLaunchMode;
+ this.screenOrientation = other.screenOrientation;
+ this.resizeMode = other.resizeMode;
+ this.maxAspectRatio = other.maxAspectRatio;
+ this.minAspectRatio = other.minAspectRatio;
+ this.supportsSizeChanges = other.supportsSizeChanges;
+ this.requestedVrComponent = other.requestedVrComponent;
+ this.rotationAnimation = other.rotationAnimation;
+ this.colorMode = other.colorMode;
+ this.windowLayout = other.windowLayout;
+ }
+
+ /**
+ * Generate activity object that forwards user to App Details page automatically.
+ * This activity should be invisible to user and user should not know or see it.
+ */
+ public static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
+ int uiOptions, String taskAffinity, boolean hardwareAccelerated) {
+ ParsedActivity activity = new ParsedActivity();
+ activity.setPackageName(packageName);
+ activity.theme = android.R.style.Theme_NoDisplay;
+ activity.exported = true;
+ activity.setName(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
+ activity.setProcessName(processName);
+ activity.uiOptions = uiOptions;
+ activity.taskAffinity = taskAffinity;
+ activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
+ activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
+ activity.configChanges = ParsedActivityUtils.getActivityConfigChanges(0, 0);
+ activity.softInputMode = 0;
+ activity.persistableMode = ActivityInfo.PERSIST_NEVER;
+ activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ activity.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+ activity.lockTaskLaunchMode = 0;
+ activity.setDirectBootAware(false);
+ activity.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
+ activity.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+ if (hardwareAccelerated) {
+ activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_HARDWARE_ACCELERATED);
+ }
+ return activity;
+ }
+
+ static ParsedActivity makeAlias(String targetActivityName, ParsedActivity target) {
+ ParsedActivity alias = new ParsedActivity();
+ alias.setPackageName(target.getPackageName());
+ alias.setTargetActivity(targetActivityName);
+ alias.configChanges = target.configChanges;
+ alias.flags = target.flags;
+ alias.privateFlags = target.privateFlags;
+ alias.icon = target.icon;
+ alias.logo = target.logo;
+ alias.banner = target.banner;
+ alias.labelRes = target.labelRes;
+ alias.nonLocalizedLabel = target.nonLocalizedLabel;
+ alias.launchMode = target.launchMode;
+ alias.lockTaskLaunchMode = target.lockTaskLaunchMode;
+ alias.documentLaunchMode = target.documentLaunchMode;
+ alias.descriptionRes = target.descriptionRes;
+ alias.screenOrientation = target.screenOrientation;
+ alias.taskAffinity = target.taskAffinity;
+ alias.theme = target.theme;
+ alias.softInputMode = target.softInputMode;
+ alias.uiOptions = target.uiOptions;
+ alias.parentActivityName = target.parentActivityName;
+ alias.maxRecents = target.maxRecents;
+ alias.windowLayout = target.windowLayout;
+ alias.resizeMode = target.resizeMode;
+ alias.maxAspectRatio = target.maxAspectRatio;
+ alias.minAspectRatio = target.minAspectRatio;
+ alias.supportsSizeChanges = target.supportsSizeChanges;
+ alias.requestedVrComponent = target.requestedVrComponent;
+ alias.directBootAware = target.directBootAware;
+ alias.setProcessName(target.getProcessName());
+ return alias;
+
+ // Not all attributes from the target ParsedActivity are copied to the alias.
+ // Careful when adding an attribute and determine whether or not it should be copied.
+// alias.enabled = target.enabled;
+// alias.exported = target.exported;
+// alias.permission = target.permission;
+// alias.splitName = target.splitName;
+// alias.persistableMode = target.persistableMode;
+// alias.rotationAnimation = target.rotationAnimation;
+// alias.colorMode = target.colorMode;
+// alias.intents.addAll(target.intents);
+// alias.order = target.order;
+// alias.metaData = target.metaData;
+ }
+
+ public ParsedActivity setMaxAspectRatio(int resizeMode, float maxAspectRatio) {
+ if (resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE
+ || resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ return this;
+ }
+
+ if (maxAspectRatio < 1.0f && maxAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ return this;
+ }
+
+ this.maxAspectRatio = maxAspectRatio;
+ return this;
+ }
+
+ public ParsedActivity setMinAspectRatio(int resizeMode, float minAspectRatio) {
+ if (resizeMode == RESIZE_MODE_RESIZEABLE
+ || resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ return this;
+ }
+
+ if (minAspectRatio < 1.0f && minAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ return this;
+ }
+
+ this.minAspectRatio = minAspectRatio;
+ return this;
+ }
+
+ public ParsedActivity setSupportsSizeChanges(boolean supportsSizeChanges) {
+ this.supportsSizeChanges = supportsSizeChanges;
+ return this;
+ }
+
+ public ParsedActivity setFlags(int flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ public ParsedActivity setResizeMode(int resizeMode) {
+ this.resizeMode = resizeMode;
+ return this;
+ }
+
+ public ParsedActivity setTargetActivity(String targetActivity) {
+ this.targetActivity = TextUtils.safeIntern(targetActivity);
+ return this;
+ }
+
+ public ParsedActivity setParentActivity(String parentActivity) {
+ this.parentActivityName = TextUtils.safeIntern(parentActivity);
+ return this;
+ }
+
+ public ParsedActivity setPermission(String permission) {
+ // Empty string must be converted to null
+ this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
+ return this;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Activity{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.theme);
+ dest.writeInt(this.uiOptions);
+ dest.writeString(this.targetActivity);
+ dest.writeString(this.parentActivityName);
+ dest.writeString(this.taskAffinity);
+ dest.writeInt(this.privateFlags);
+ sForInternedString.parcel(this.permission, dest, flags);
+ dest.writeInt(this.launchMode);
+ dest.writeInt(this.documentLaunchMode);
+ dest.writeInt(this.maxRecents);
+ dest.writeInt(this.configChanges);
+ dest.writeInt(this.softInputMode);
+ dest.writeInt(this.persistableMode);
+ dest.writeInt(this.lockTaskLaunchMode);
+ dest.writeInt(this.screenOrientation);
+ dest.writeInt(this.resizeMode);
+ dest.writeValue(this.maxAspectRatio);
+ dest.writeValue(this.minAspectRatio);
+ dest.writeBoolean(this.supportsSizeChanges);
+ dest.writeString(this.requestedVrComponent);
+ dest.writeInt(this.rotationAnimation);
+ dest.writeInt(this.colorMode);
+ dest.writeBundle(this.metaData);
+
+ if (windowLayout != null) {
+ dest.writeInt(1);
+ windowLayout.writeToParcel(dest);
+ } else {
+ dest.writeBoolean(false);
+ }
+ }
+
+ public ParsedActivity() {
+ }
+
+ protected ParsedActivity(Parcel in) {
+ super(in);
+ this.theme = in.readInt();
+ this.uiOptions = in.readInt();
+ this.targetActivity = in.readString();
+ this.parentActivityName = in.readString();
+ this.taskAffinity = in.readString();
+ this.privateFlags = in.readInt();
+ this.permission = sForInternedString.unparcel(in);
+ this.launchMode = in.readInt();
+ this.documentLaunchMode = in.readInt();
+ this.maxRecents = in.readInt();
+ this.configChanges = in.readInt();
+ this.softInputMode = in.readInt();
+ this.persistableMode = in.readInt();
+ this.lockTaskLaunchMode = in.readInt();
+ this.screenOrientation = in.readInt();
+ this.resizeMode = in.readInt();
+ this.maxAspectRatio = (Float) in.readValue(Float.class.getClassLoader());
+ this.minAspectRatio = (Float) in.readValue(Float.class.getClassLoader());
+ this.supportsSizeChanges = in.readBoolean();
+ this.requestedVrComponent = in.readString();
+ this.rotationAnimation = in.readInt();
+ this.colorMode = in.readInt();
+ this.metaData = in.readBundle();
+ if (in.readBoolean()) {
+ windowLayout = new ActivityInfo.WindowLayout(in);
+ }
+ }
+
+ public static final Parcelable.Creator<ParsedActivity> CREATOR = new Creator<ParsedActivity>() {
+ @Override
+ public ParsedActivity createFromParcel(Parcel source) {
+ return new ParsedActivity(source);
+ }
+
+ @Override
+ public ParsedActivity[] newArray(int size) {
+ return new ParsedActivity[size];
+ }
+ };
+
+ public int getTheme() {
+ return theme;
+ }
+
+ public int getUiOptions() {
+ return uiOptions;
+ }
+
+ @Nullable
+ public String getTargetActivity() {
+ return targetActivity;
+ }
+
+ @Nullable
+ public String getParentActivityName() {
+ return parentActivityName;
+ }
+
+ @Nullable
+ public String getTaskAffinity() {
+ return taskAffinity;
+ }
+
+ public int getPrivateFlags() {
+ return privateFlags;
+ }
+
+ @Nullable
+ public String getPermission() {
+ return permission;
+ }
+
+ public int getLaunchMode() {
+ return launchMode;
+ }
+
+ public int getDocumentLaunchMode() {
+ return documentLaunchMode;
+ }
+
+ public int getMaxRecents() {
+ return maxRecents;
+ }
+
+ public int getConfigChanges() {
+ return configChanges;
+ }
+
+ public int getSoftInputMode() {
+ return softInputMode;
+ }
+
+ public int getPersistableMode() {
+ return persistableMode;
+ }
+
+ public int getLockTaskLaunchMode() {
+ return lockTaskLaunchMode;
+ }
+
+ public int getScreenOrientation() {
+ return screenOrientation;
+ }
+
+ public int getResizeMode() {
+ return resizeMode;
+ }
+
+ @Nullable
+ public Float getMaxAspectRatio() {
+ return maxAspectRatio;
+ }
+
+ @Nullable
+ public Float getMinAspectRatio() {
+ return minAspectRatio;
+ }
+
+ public boolean getSupportsSizeChanges() {
+ return supportsSizeChanges;
+ }
+
+ @Nullable
+ public String getRequestedVrComponent() {
+ return requestedVrComponent;
+ }
+
+ public int getRotationAnimation() {
+ return rotationAnimation;
+ }
+
+ public int getColorMode() {
+ return colorMode;
+ }
+
+ @Nullable
+ public ActivityInfo.WindowLayout getWindowLayout() {
+ return windowLayout;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedActivityUtils.java b/android/content/pm/parsing/component/ParsedActivityUtils.java
new file mode 100644
index 0000000..92a90e9
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.parsing.component.ComponentParseUtils.flag;
+
+import android.annotation.NonNull;
+import android.app.ActivityTaskManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseInput.DeferredError;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/** @hide */
+public class ParsedActivityUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ public static final boolean LOG_UNSAFE_BROADCASTS = false;
+
+ // Set of broadcast actions that are safe for manifest receivers
+ public static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
+ static {
+ SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
+ }
+
+ /**
+ * Bit mask of all the valid bits that can be set in recreateOnConfigChanges.
+ */
+ private static final int RECREATE_ON_CONFIG_CHANGES_MASK =
+ ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
+
+ @NonNull
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static ParseResult<ParsedActivity> parseActivityOrReceiver(String[] separateProcesses,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
+ boolean useRoundIcon, ParseInput input)
+ throws XmlPullParserException, IOException {
+ final String packageName = pkg.getPackageName();
+ final ParsedActivity
+ activity = new ParsedActivity();
+
+ boolean receiver = "receiver".equals(parser.getName());
+ String tag = "<" + parser.getName() + ">";
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);
+ try {
+ ParseResult<ParsedActivity> result =
+ ParsedMainComponentUtils.parseMainComponent(
+ activity, tag, separateProcesses,
+ pkg, sa, flags, useRoundIcon, input,
+ R.styleable.AndroidManifestActivity_banner,
+ R.styleable.AndroidManifestActivity_description,
+ R.styleable.AndroidManifestActivity_directBootAware,
+ R.styleable.AndroidManifestActivity_enabled,
+ R.styleable.AndroidManifestActivity_icon,
+ R.styleable.AndroidManifestActivity_label,
+ R.styleable.AndroidManifestActivity_logo,
+ R.styleable.AndroidManifestActivity_name,
+ R.styleable.AndroidManifestActivity_process,
+ R.styleable.AndroidManifestActivity_roundIcon,
+ R.styleable.AndroidManifestActivity_splitName,
+ R.styleable.AndroidManifestActivity_attributionTags);
+ if (result.isError()) {
+ return result;
+ }
+
+ if (receiver && pkg.isCantSaveState()) {
+ // A heavy-weight application can not have receivers in its main process
+ if (Objects.equals(activity.getProcessName(), packageName)) {
+ return input.error("Heavy-weight applications can not have receivers "
+ + "in main process");
+ }
+ }
+
+ // The following section has formatting off to make it easier to read the flags.
+ // Multi-lining them to fit within the column restriction makes it hard to tell what
+ // field is assigned where.
+ // @formatter:off
+ activity.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0);
+ activity.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions, pkg.getUiOptions());
+
+ activity.flags |= flag(ActivityInfo.FLAG_ALLOW_TASK_REPARENTING, R.styleable.AndroidManifestActivity_allowTaskReparenting, pkg.isAllowTaskReparenting(), sa)
+ | flag(ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE, R.styleable.AndroidManifestActivity_alwaysRetainTaskState, sa)
+ | flag(ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH, R.styleable.AndroidManifestActivity_clearTaskOnLaunch, sa)
+ | flag(ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS, R.styleable.AndroidManifestActivity_excludeFromRecents, sa)
+ | flag(ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS, R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, sa)
+ | flag(ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH, R.styleable.AndroidManifestActivity_finishOnTaskLaunch, sa)
+ | flag(ActivityInfo.FLAG_IMMERSIVE, R.styleable.AndroidManifestActivity_immersive, sa)
+ | flag(ActivityInfo.FLAG_MULTIPROCESS, R.styleable.AndroidManifestActivity_multiprocess, sa)
+ | flag(ActivityInfo.FLAG_NO_HISTORY, R.styleable.AndroidManifestActivity_noHistory, sa)
+ | flag(ActivityInfo.FLAG_SHOW_FOR_ALL_USERS, R.styleable.AndroidManifestActivity_showForAllUsers, sa)
+ | flag(ActivityInfo.FLAG_SHOW_FOR_ALL_USERS, R.styleable.AndroidManifestActivity_showOnLockScreen, sa)
+ | flag(ActivityInfo.FLAG_STATE_NOT_NEEDED, R.styleable.AndroidManifestActivity_stateNotNeeded, sa)
+ | flag(ActivityInfo.FLAG_SYSTEM_USER_ONLY, R.styleable.AndroidManifestActivity_systemUserOnly, sa);
+
+ if (!receiver) {
+ activity.flags |= flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isBaseHardwareAccelerated(), sa)
+ | flag(ActivityInfo.FLAG_ALLOW_EMBEDDED, R.styleable.AndroidManifestActivity_allowEmbedded, sa)
+ | flag(ActivityInfo.FLAG_ALWAYS_FOCUSABLE, R.styleable.AndroidManifestActivity_alwaysFocusable, sa)
+ | flag(ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS, R.styleable.AndroidManifestActivity_autoRemoveFromRecents, sa)
+ | flag(ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY, R.styleable.AndroidManifestActivity_relinquishTaskIdentity, sa)
+ | flag(ActivityInfo.FLAG_RESUME_WHILE_PAUSING, R.styleable.AndroidManifestActivity_resumeWhilePausing, sa)
+ | flag(ActivityInfo.FLAG_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_showWhenLocked, sa)
+ | flag(ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE, R.styleable.AndroidManifestActivity_supportsPictureInPicture, sa)
+ | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa)
+ | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa);
+
+ activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED,
+ R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa)
+ | flag(ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND,
+ R.styleable.AndroidManifestActivity_playHomeTransitionSound, true, sa);
+
+ activity.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT);
+ activity.documentLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE);
+ activity.launchMode = sa.getInt(R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE);
+ activity.lockTaskLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
+ activity.maxRecents = sa.getInt(R.styleable.AndroidManifestActivity_maxRecents, ActivityTaskManager.getDefaultAppRecentsLimitStatic());
+ activity.persistableMode = sa.getInteger(R.styleable.AndroidManifestActivity_persistableMode, ActivityInfo.PERSIST_ROOT_ONLY);
+ activity.requestedVrComponent = sa.getString(R.styleable.AndroidManifestActivity_enableVrMode);
+ activity.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED);
+ activity.softInputMode = sa.getInt(R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
+
+ activity.configChanges = getActivityConfigChanges(
+ sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0),
+ sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0));
+
+ int screenOrientation = sa.getInt(R.styleable.AndroidManifestActivity_screenOrientation, SCREEN_ORIENTATION_UNSPECIFIED);
+ int resizeMode = getActivityResizeMode(pkg, sa, screenOrientation);
+ activity.screenOrientation = screenOrientation;
+ activity.resizeMode = resizeMode;
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_maxAspectRatio)
+ && sa.getType(R.styleable.AndroidManifestActivity_maxAspectRatio)
+ == TypedValue.TYPE_FLOAT) {
+ activity.setMaxAspectRatio(resizeMode,
+ sa.getFloat(R.styleable.AndroidManifestActivity_maxAspectRatio,
+ 0 /*default*/));
+ }
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio)
+ && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio)
+ == TypedValue.TYPE_FLOAT) {
+ activity.setMinAspectRatio(resizeMode,
+ sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio,
+ 0 /*default*/));
+ }
+ } else {
+ activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ activity.configChanges = 0;
+ activity.flags |= flag(ActivityInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestActivity_singleUser, sa);
+ }
+ // @formatter:on
+
+ String taskAffinity = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestActivity_taskAffinity,
+ Configuration.NATIVE_CONFIG_VERSION);
+
+ ParseResult<String> affinityNameResult = ComponentParseUtils.buildTaskAffinityName(
+ packageName, pkg.getTaskAffinity(), taskAffinity, input);
+ if (affinityNameResult.isError()) {
+ return input.error(affinityNameResult);
+ }
+
+ activity.taskAffinity = affinityNameResult.getResult();
+
+ boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
+ if (visibleToEphemeral) {
+ activity.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ pkg.setVisibleToInstantApps(true);
+ }
+
+ return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
+ false /*isAlias*/, visibleToEphemeral, input,
+ R.styleable.AndroidManifestActivity_parentActivityName,
+ R.styleable.AndroidManifestActivity_permission,
+ R.styleable.AndroidManifestActivity_exported
+ );
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ public static ParseResult<ParsedActivity> parseActivityAlias(ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivityAlias);
+ try {
+ String targetActivity = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestActivityAlias_targetActivity,
+ Configuration.NATIVE_CONFIG_VERSION);
+ if (targetActivity == null) {
+ return input.error("<activity-alias> does not specify android:targetActivity");
+ }
+
+ String packageName = pkg.getPackageName();
+ targetActivity = ParsingUtils.buildClassName(packageName, targetActivity);
+ if (targetActivity == null) {
+ return input.error("Empty class name in package " + packageName);
+ }
+
+ ParsedActivity target = null;
+
+ List<ParsedActivity> activities = pkg.getActivities();
+ final int activitiesSize = ArrayUtils.size(activities);
+ for (int i = 0; i < activitiesSize; i++) {
+ ParsedActivity t = activities.get(i);
+ if (targetActivity.equals(t.getName())) {
+ target = t;
+ break;
+ }
+ }
+
+ if (target == null) {
+ return input.error("<activity-alias> target activity " + targetActivity
+ + " not found in manifest with activities = "
+ + pkg.getActivities()
+ + ", parsedActivities = " + activities);
+ }
+
+ ParsedActivity activity = ParsedActivity.makeAlias(targetActivity, target);
+ String tag = "<" + parser.getName() + ">";
+
+ ParseResult<ParsedActivity> result = ParsedMainComponentUtils.parseMainComponent(
+ activity, tag, null, pkg, sa, 0, useRoundIcon, input,
+ R.styleable.AndroidManifestActivityAlias_banner,
+ R.styleable.AndroidManifestActivityAlias_description,
+ null /*directBootAwareAttr*/,
+ R.styleable.AndroidManifestActivityAlias_enabled,
+ R.styleable.AndroidManifestActivityAlias_icon,
+ R.styleable.AndroidManifestActivityAlias_label,
+ R.styleable.AndroidManifestActivityAlias_logo,
+ R.styleable.AndroidManifestActivityAlias_name,
+ null /*processAttr*/,
+ R.styleable.AndroidManifestActivityAlias_roundIcon,
+ null /*splitNameAttr*/,
+ R.styleable.AndroidManifestActivityAlias_attributionTags);
+ if (result.isError()) {
+ return result;
+ }
+
+ // TODO add visibleToInstantApps attribute to activity alias
+ final boolean visibleToEphemeral =
+ ((activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0);
+
+ return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, false /*isReceiver*/, true /*isAlias*/,
+ visibleToEphemeral, input,
+ R.styleable.AndroidManifestActivityAlias_parentActivityName,
+ R.styleable.AndroidManifestActivityAlias_permission,
+ R.styleable.AndroidManifestActivityAlias_exported);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ /**
+ * This method shares parsing logic between Activity/Receiver/alias instances, but requires
+ * passing in booleans for isReceiver/isAlias, since there's no indicator in the other
+ * parameters.
+ *
+ * They're used to filter the parsed tags and their behavior. This makes the method rather
+ * messy, but it's more maintainable than writing 3 separate methods for essentially the same
+ * type of logic.
+ */
+ @NonNull
+ private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivity activity,
+ ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,
+ TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,
+ ParseInput input, int parentActivityNameAttr, int permissionAttr,
+ int exportedAttr) throws IOException, XmlPullParserException {
+ String parentActivityName = array.getNonConfigurationString(parentActivityNameAttr, Configuration.NATIVE_CONFIG_VERSION);
+ if (parentActivityName != null) {
+ String packageName = pkg.getPackageName();
+ String parentClassName = ParsingUtils.buildClassName(packageName, parentActivityName);
+ if (parentClassName == null) {
+ Log.e(TAG, "Activity " + activity.getName()
+ + " specified invalid parentActivityName " + parentActivityName);
+ } else {
+ activity.setParentActivity(parentClassName);
+ }
+ }
+
+ String permission = array.getNonConfigurationString(permissionAttr, 0);
+ if (isAlias) {
+ // An alias will override permissions to allow referencing an Activity through its alias
+ // without needing the original permission. If an alias needs the same permission,
+ // it must be re-declared.
+ activity.setPermission(permission);
+ } else {
+ activity.setPermission(permission != null ? permission : pkg.getPermission());
+ }
+
+ final boolean setExported = array.hasValue(exportedAttr);
+ if (setExported) {
+ activity.exported = array.getBoolean(exportedAttr, false);
+ }
+
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult result;
+ if (parser.getName().equals("intent-filter")) {
+ ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+ !isReceiver, visibleToEphemeral, resources, parser, input);
+ if (intentResult.isSuccess()) {
+ ParsedIntentInfo intent = intentResult.getResult();
+ if (intent != null) {
+ activity.order = Math.max(intent.getOrder(), activity.order);
+ activity.addIntent(intent);
+ if (LOG_UNSAFE_BROADCASTS && isReceiver
+ && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
+ int actionCount = intent.countActions();
+ for (int i = 0; i < actionCount; i++) {
+ final String action = intent.getAction(i);
+ if (action == null || !action.startsWith("android.")) {
+ continue;
+ }
+
+ if (!SAFE_BROADCASTS.contains(action)) {
+ Slog.w(TAG,
+ "Broadcast " + action + " may never be delivered to "
+ + pkg.getPackageName() + " as requested at: "
+ + parser.getPositionDescription());
+ }
+ }
+ }
+ }
+ }
+ result = intentResult;
+ } else if (parser.getName().equals("meta-data")) {
+ result = ParsedComponentUtils.addMetaData(activity, pkg, resources, parser, input);
+ } else if (parser.getName().equals("property")) {
+ result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
+ } else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
+ ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+ true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
+ resources, parser, input);
+ if (intentResult.isSuccess()) {
+ ParsedIntentInfo intent = intentResult.getResult();
+ if (intent != null) {
+ pkg.addPreferredActivityFilter(activity.getClassName(), intent);
+ }
+ }
+ result = intentResult;
+ } else if (!isReceiver && !isAlias && parser.getName().equals("layout")) {
+ ParseResult<ActivityInfo.WindowLayout> layoutResult =
+ parseActivityWindowLayout(resources, parser, input);
+ if (layoutResult.isSuccess()) {
+ activity.windowLayout = layoutResult.getResult();
+ }
+ result = layoutResult;
+ } else {
+ result = ParsingUtils.unknownTag(tag, pkg, parser, input);
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ if (!isAlias && activity.launchMode != LAUNCH_SINGLE_INSTANCE_PER_TASK
+ && activity.metaData != null && activity.metaData.containsKey(
+ ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {
+ final String launchMode = activity.metaData.getString(
+ ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);
+ if (launchMode != null && launchMode.equals("singleInstancePerTask")) {
+ activity.launchMode = LAUNCH_SINGLE_INSTANCE_PER_TASK;
+ }
+ }
+
+ ParseResult<ActivityInfo.WindowLayout> layoutResult =
+ resolveActivityWindowLayout(activity, input);
+ if (layoutResult.isError()) {
+ return input.error(layoutResult);
+ }
+ activity.windowLayout = layoutResult.getResult();
+
+ if (!setExported) {
+ boolean hasIntentFilters = activity.getIntents().size() > 0;
+ if (hasIntentFilters) {
+ final ParseResult exportedCheckResult = input.deferError(
+ activity.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S
+ + " and above) requires that an explicit value for android:exported be"
+ + " defined when intent filters are present",
+ DeferredError.MISSING_EXPORTED_FLAG);
+ if (exportedCheckResult.isError()) {
+ return input.error(exportedCheckResult);
+ }
+ }
+ activity.exported = hasIntentFilters;
+ }
+
+ return input.success(activity);
+ }
+
+ @NonNull
+ private static ParseResult<ParsedIntentInfo> parseIntentFilter(ParsingPackage pkg,
+ ParsedActivity activity, boolean allowImplicitEphemeralVisibility,
+ boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
+ ParseInput input) throws IOException, XmlPullParserException {
+ ParseResult<ParsedIntentInfo> result = ParsedMainComponentUtils.parseIntentFilter(activity,
+ pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/,
+ true /*allowAutoVerify*/, allowImplicitEphemeralVisibility,
+ true /*failOnNoActions*/, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ ParsedIntentInfo intent = result.getResult();
+ if (intent != null) {
+ if (intent.isVisibleToInstantApp()) {
+ activity.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ }
+ if (intent.isImplicitlyVisibleToInstantApp()) {
+ activity.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
+ }
+ }
+
+ return input.success(intent);
+ }
+
+ private static int getActivityResizeMode(ParsingPackage pkg, TypedArray sa,
+ int screenOrientation) {
+ Boolean resizeableActivity = pkg.getResizeableActivity();
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
+ || resizeableActivity != null) {
+ // Activity or app explicitly set if it is resizeable or not;
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
+ resizeableActivity != null && resizeableActivity)) {
+ return ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ } else {
+ return ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ }
+ }
+
+ if (pkg.isResizeableActivityViaSdkVersion()) {
+ // The activity or app didn't explicitly set the resizing option, however we want to
+ // make it resize due to the sdk version it is targeting.
+ return ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+ }
+
+ // resize preference isn't set and target sdk version doesn't support resizing apps by
+ // default. For the app to be resizeable if it isn't fixed orientation or immersive.
+ if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) {
+ return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+ } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) {
+ return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+ } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ } else {
+ return ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ActivityInfo.WindowLayout> parseActivityWindowLayout(Resources res,
+ AttributeSet attrs, ParseInput input) {
+ TypedArray sw = res.obtainAttributes(attrs, R.styleable.AndroidManifestLayout);
+ try {
+ int width = -1;
+ float widthFraction = -1f;
+ int height = -1;
+ float heightFraction = -1f;
+ final int widthType = sw.getType(R.styleable.AndroidManifestLayout_defaultWidth);
+ if (widthType == TypedValue.TYPE_FRACTION) {
+ widthFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultWidth, 1, 1,
+ -1);
+ } else if (widthType == TypedValue.TYPE_DIMENSION) {
+ width = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultWidth,
+ -1);
+ }
+ final int heightType = sw.getType(R.styleable.AndroidManifestLayout_defaultHeight);
+ if (heightType == TypedValue.TYPE_FRACTION) {
+ heightFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultHeight, 1,
+ 1, -1);
+ } else if (heightType == TypedValue.TYPE_DIMENSION) {
+ height = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultHeight,
+ -1);
+ }
+ int gravity = sw.getInt(R.styleable.AndroidManifestLayout_gravity, Gravity.CENTER);
+ int minWidth = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minWidth, -1);
+ int minHeight = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minHeight,
+ -1);
+ String windowLayoutAffinity =
+ sw.getNonConfigurationString(
+ R.styleable.AndroidManifestLayout_windowLayoutAffinity, 0);
+ final ActivityInfo.WindowLayout windowLayout = new ActivityInfo.WindowLayout(width,
+ widthFraction, height, heightFraction, gravity, minWidth, minHeight,
+ windowLayoutAffinity);
+ return input.success(windowLayout);
+ } finally {
+ sw.recycle();
+ }
+ }
+
+ /**
+ * Resolves values in {@link ActivityInfo.WindowLayout}.
+ *
+ * <p>{@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in
+ * Android R and some variants of pre-R.
+ */
+ private static ParseResult<ActivityInfo.WindowLayout> resolveActivityWindowLayout(
+ ParsedActivity activity, ParseInput input) {
+ // There isn't a metadata for us to fall back. Whatever is in layout is correct.
+ if (activity.metaData == null || !activity.metaData.containsKey(
+ ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
+ return input.success(activity.windowLayout);
+ }
+
+ // Layout already specifies a value. We should just use that one.
+ if (activity.windowLayout != null && activity.windowLayout.windowLayoutAffinity != null) {
+ return input.success(activity.windowLayout);
+ }
+
+ String windowLayoutAffinity = activity.metaData.getString(
+ ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
+ ActivityInfo.WindowLayout layout = activity.windowLayout;
+ if (layout == null) {
+ layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */,
+ -1 /* height */, -1 /* heightFraction */, Gravity.NO_GRAVITY,
+ -1 /* minWidth */, -1 /* minHeight */, windowLayoutAffinity);
+ } else {
+ layout.windowLayoutAffinity = windowLayoutAffinity;
+ }
+ return input.success(layout);
+ }
+
+ /**
+ * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
+ * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
+ * AndroidManifest.xml.
+ * @hide
+ */
+ static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) {
+ return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedAttribution.java b/android/content/pm/parsing/component/ParsedAttribution.java
new file mode 100644
index 0000000..4ec2e73
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedAttribution.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link android.R.styleable#AndroidManifestAttribution <attribution>} tag parsed from the
+ * manifest.
+ *
+ * @hide
+ */
+@DataClass(genAidl = false)
+public class ParsedAttribution implements Parcelable {
+ /** Maximum length of attribution tag */
+ public static final int MAX_ATTRIBUTION_TAG_LEN = 50;
+
+ /** Maximum amount of attributions per package */
+ private static final int MAX_NUM_ATTRIBUTIONS = 10000;
+
+ /** Tag of the attribution */
+ public final @NonNull String tag;
+
+ /** User visible label fo the attribution */
+ public final @StringRes int label;
+
+ /** Ids of previously declared attributions this attribution inherits from */
+ public final @NonNull List<String> inheritFrom;
+
+ /**
+ * @return Is this set of attributions a valid combination for a single package?
+ */
+ public static boolean isCombinationValid(@Nullable List<ParsedAttribution> attributions) {
+ if (attributions == null) {
+ return true;
+ }
+
+ ArraySet<String> attributionTags = new ArraySet<>(attributions.size());
+ ArraySet<String> inheritFromAttributionTags = new ArraySet<>();
+
+ int numAttributions = attributions.size();
+ if (numAttributions > MAX_NUM_ATTRIBUTIONS) {
+ return false;
+ }
+
+ for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
+ boolean wasAdded = attributionTags.add(attributions.get(attributionNum).tag);
+ if (!wasAdded) {
+ // feature id is not unique
+ return false;
+ }
+ }
+
+ for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
+ ParsedAttribution feature = attributions.get(attributionNum);
+
+ int numInheritFrom = feature.inheritFrom.size();
+ for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; inheritFromNum++) {
+ String inheritFrom = feature.inheritFrom.get(inheritFromNum);
+
+ if (attributionTags.contains(inheritFrom)) {
+ // Cannot inherit from a attribution that is still defined
+ return false;
+ }
+
+ boolean wasAdded = inheritFromAttributionTags.add(inheritFrom);
+ if (!wasAdded) {
+ // inheritFrom is not unique
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @android.annotation.IntDef(prefix = "MAX_", value = {
+ MAX_ATTRIBUTION_TAG_LEN,
+ MAX_NUM_ATTRIBUTIONS
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Max {}
+
+ @DataClass.Generated.Member
+ public static String maxToString(@Max int value) {
+ switch (value) {
+ case MAX_ATTRIBUTION_TAG_LEN:
+ return "MAX_ATTRIBUTION_TAG_LEN";
+ case MAX_NUM_ATTRIBUTIONS:
+ return "MAX_NUM_ATTRIBUTIONS";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new ParsedAttribution.
+ *
+ * @param tag
+ * Tag of the attribution
+ * @param label
+ * User visible label fo the attribution
+ * @param inheritFrom
+ * Ids of previously declared attributions this attribution inherits from
+ */
+ @DataClass.Generated.Member
+ public ParsedAttribution(
+ @NonNull String tag,
+ @StringRes int label,
+ @NonNull List<String> inheritFrom) {
+ this.tag = tag;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, tag);
+ this.label = label;
+ com.android.internal.util.AnnotationValidations.validate(
+ StringRes.class, null, label);
+ this.inheritFrom = inheritFrom;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, inheritFrom);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(tag);
+ dest.writeInt(label);
+ dest.writeStringList(inheritFrom);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedAttribution(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String _tag = in.readString();
+ int _label = in.readInt();
+ List<String> _inheritFrom = new ArrayList<>();
+ in.readStringList(_inheritFrom);
+
+ this.tag = _tag;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, tag);
+ this.label = _label;
+ com.android.internal.util.AnnotationValidations.validate(
+ StringRes.class, null, label);
+ this.inheritFrom = _inheritFrom;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, inheritFrom);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ParsedAttribution> CREATOR
+ = new Parcelable.Creator<ParsedAttribution>() {
+ @Override
+ public ParsedAttribution[] newArray(int size) {
+ return new ParsedAttribution[size];
+ }
+
+ @Override
+ public ParsedAttribution createFromParcel(@NonNull Parcel in) {
+ return new ParsedAttribution(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1618351459610L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java",
+ inputSignatures = "public static final int MAX_ATTRIBUTION_TAG_LEN\nprivate static final int MAX_NUM_ATTRIBUTIONS\npublic final @android.annotation.NonNull java.lang.String tag\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static boolean isCombinationValid(java.util.List<android.content.pm.parsing.component.ParsedAttribution>)\nclass ParsedAttribution extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/parsing/component/ParsedAttributionUtils.java b/android/content/pm/parsing/component/ParsedAttributionUtils.java
new file mode 100644
index 0000000..dccc49a
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedAttributionUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+public class ParsedAttributionUtils {
+
+ @NonNull
+ public static ParseResult<ParsedAttribution> parseAttribution(Resources res,
+ XmlResourceParser parser, ParseInput input)
+ throws IOException, XmlPullParserException {
+ String attributionTag;
+ int label;
+ List<String> inheritFrom = null;
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAttribution);
+ if (sa == null) {
+ return input.error("<attribution> could not be parsed");
+ }
+
+ try {
+ attributionTag = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestAttribution_tag, 0);
+ if (attributionTag == null) {
+ return input.error("<attribution> does not specify android:tag");
+ }
+ if (attributionTag.length() > ParsedAttribution.MAX_ATTRIBUTION_TAG_LEN) {
+ return input.error("android:tag is too long. Max length is "
+ + ParsedAttribution.MAX_ATTRIBUTION_TAG_LEN);
+ }
+
+ label = sa.getResourceId(R.styleable.AndroidManifestAttribution_label, 0);
+ if (label == Resources.ID_NULL) {
+ return input.error("<attribution> does not specify android:label");
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("inherit-from")) {
+ sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestAttributionInheritFrom);
+ if (sa == null) {
+ return input.error("<inherit-from> could not be parsed");
+ }
+
+ try {
+ String inheritFromId = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestAttributionInheritFrom_tag, 0);
+
+ if (inheritFrom == null) {
+ inheritFrom = new ArrayList<>();
+ }
+ inheritFrom.add(inheritFromId);
+ } finally {
+ sa.recycle();
+ }
+ } else {
+ return input.error("Bad element under <attribution>: " + tagName);
+ }
+ }
+
+ if (inheritFrom == null) {
+ inheritFrom = Collections.emptyList();
+ } else {
+ ((ArrayList) inheritFrom).trimToSize();
+ }
+
+ return input.success(new ParsedAttribution(attributionTag, label, inheritFrom));
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedComponent.java b/android/content/pm/parsing/component/ParsedComponent.java
new file mode 100644
index 0000000..9d830ec
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedComponent.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import static java.util.Collections.emptyMap;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.PackageManager.Property;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/** @hide */
+public abstract class ParsedComponent implements Parcelable {
+
+ private static ParsedIntentInfo.ListParceler sForIntentInfos = Parcelling.Cache.getOrCreate(
+ ParsedIntentInfo.ListParceler.class);
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String name;
+ int icon;
+ int labelRes;
+ @Nullable
+ CharSequence nonLocalizedLabel;
+ int logo;
+ int banner;
+ int descriptionRes;
+
+ // TODO(b/135203078): Replace flags with individual booleans, scoped by subclass
+ int flags;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String packageName;
+
+ @Nullable
+ @DataClass.PluralOf("intent")
+ @DataClass.ParcelWith(ParsedIntentInfo.ListParceler.class)
+ private List<ParsedIntentInfo> intents;
+
+ private ComponentName componentName;
+
+ @Nullable
+ protected Bundle metaData;
+
+ private Map<String, Property> mProperties = emptyMap();
+
+ ParsedComponent() {
+
+ }
+
+ @SuppressWarnings("IncompleteCopyConstructor")
+ public ParsedComponent(ParsedComponent other) {
+ this.metaData = other.metaData;
+ this.name = other.name;
+ this.icon = other.getIcon();
+ this.labelRes = other.getLabelRes();
+ this.nonLocalizedLabel = other.getNonLocalizedLabel();
+ this.logo = other.getLogo();
+ this.banner = other.getBanner();
+
+ this.descriptionRes = other.getDescriptionRes();
+
+ this.flags = other.getFlags();
+
+ this.setPackageName(other.packageName);
+ this.intents = new ArrayList<>(other.getIntents());
+ }
+
+ public void addIntent(ParsedIntentInfo intent) {
+ this.intents = CollectionUtils.add(this.intents, intent);
+ }
+
+ /** Add a property to the component */
+ public void addProperty(@NonNull Property property) {
+ this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
+ }
+
+ @NonNull
+ public List<ParsedIntentInfo> getIntents() {
+ return intents != null ? intents : Collections.emptyList();
+ }
+
+ public ParsedComponent setName(String name) {
+ this.name = TextUtils.safeIntern(name);
+ return this;
+ }
+
+ @CallSuper
+ public void setPackageName(@NonNull String packageName) {
+ this.packageName = TextUtils.safeIntern(packageName);
+ //noinspection ConstantConditions
+ this.componentName = null;
+
+ // Note: this method does not edit name (which can point to a class), because this package
+ // name change is not changing the package in code, but the identifier used by the system.
+ }
+
+ @NonNull
+ public ComponentName getComponentName() {
+ if (componentName == null) {
+ componentName = new ComponentName(getPackageName(), getName());
+ }
+ return componentName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(this.name);
+ dest.writeInt(this.getIcon());
+ dest.writeInt(this.getLabelRes());
+ dest.writeCharSequence(this.getNonLocalizedLabel());
+ dest.writeInt(this.getLogo());
+ dest.writeInt(this.getBanner());
+ dest.writeInt(this.getDescriptionRes());
+ dest.writeInt(this.getFlags());
+ sForInternedString.parcel(this.packageName, dest, flags);
+ sForIntentInfos.parcel(this.getIntents(), dest, flags);
+ dest.writeBundle(this.metaData);
+ dest.writeMap(this.mProperties);
+ }
+
+ protected ParsedComponent(Parcel in) {
+ // We use the boot classloader for all classes that we load.
+ final ClassLoader boot = Object.class.getClassLoader();
+ //noinspection ConstantConditions
+ this.name = in.readString();
+ this.icon = in.readInt();
+ this.labelRes = in.readInt();
+ this.nonLocalizedLabel = in.readCharSequence();
+ this.logo = in.readInt();
+ this.banner = in.readInt();
+ this.descriptionRes = in.readInt();
+ this.flags = in.readInt();
+ //noinspection ConstantConditions
+ this.packageName = sForInternedString.unparcel(in);
+ this.intents = sForIntentInfos.unparcel(in);
+ this.metaData = in.readBundle(boot);
+ this.mProperties = in.readHashMap(boot);
+ }
+
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ public int getIcon() {
+ return icon;
+ }
+
+ public int getLabelRes() {
+ return labelRes;
+ }
+
+ @Nullable
+ public CharSequence getNonLocalizedLabel() {
+ return nonLocalizedLabel;
+ }
+
+ public int getLogo() {
+ return logo;
+ }
+
+ public int getBanner() {
+ return banner;
+ }
+
+ public int getDescriptionRes() {
+ return descriptionRes;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return packageName;
+ }
+
+ @Nullable
+ public Bundle getMetaData() {
+ return metaData;
+ }
+
+ @NonNull
+ public Map<String, Property> getProperties() {
+ return mProperties;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedComponentUtils.java b/android/content/pm/parsing/component/ParsedComponentUtils.java
new file mode 100644
index 0000000..46b9419
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedComponentUtils.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.TypedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** @hide */
+class ParsedComponentUtils {
+
+ @NonNull
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ static <Component extends ParsedComponent> ParseResult<Component> parseComponent(
+ Component component, String tag, ParsingPackage pkg, TypedArray array,
+ boolean useRoundIcon, ParseInput input, int bannerAttr,
+ @Nullable Integer descriptionAttr, int iconAttr, int labelAttr, int logoAttr,
+ int nameAttr, int roundIconAttr) {
+ String name = array.getNonConfigurationString(nameAttr, 0);
+ if (TextUtils.isEmpty(name)) {
+ return input.error(tag + " does not specify android:name");
+ }
+
+ String packageName = pkg.getPackageName();
+ String className = ParsingUtils.buildClassName(packageName, name);
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(className)) {
+ return input.error(tag + " invalid android:name");
+ }
+
+ //noinspection ConstantConditions; null check done above with isEmpty
+ component.setName(className);
+ component.setPackageName(packageName);
+
+ int roundIconVal = useRoundIcon ? array.getResourceId(roundIconAttr, 0) : 0;
+ if (roundIconVal != 0) {
+ component.icon = roundIconVal;
+ component.nonLocalizedLabel = null;
+ } else {
+ int iconVal = array.getResourceId(iconAttr, 0);
+ if (iconVal != 0) {
+ component.icon = iconVal;
+ component.nonLocalizedLabel = null;
+ }
+ }
+
+ int logoVal = array.getResourceId(logoAttr, 0);
+ if (logoVal != 0) {
+ component.logo = logoVal;
+ }
+
+ int bannerVal = array.getResourceId(bannerAttr, 0);
+ if (bannerVal != 0) {
+ component.banner = bannerVal;
+ }
+
+ if (descriptionAttr != null) {
+ component.descriptionRes = array.getResourceId(descriptionAttr, 0);
+ }
+
+ TypedValue v = array.peekValue(labelAttr);
+ if (v != null) {
+ component.labelRes = v.resourceId;
+ if (v.resourceId == 0) {
+ component.nonLocalizedLabel = v.coerceToString();
+ }
+ }
+
+ return input.success(component);
+ }
+
+ static ParseResult<Bundle> addMetaData(ParsedComponent component, ParsingPackage pkg,
+ Resources resources, XmlResourceParser parser, ParseInput input) {
+ ParseResult<Property> result = ParsingPackageUtils.parseMetaData(pkg, component,
+ resources, parser, "<meta-data>", input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ final Property property = result.getResult();
+ if (property != null) {
+ component.metaData = property.toBundle(component.metaData);
+ }
+ return input.success(component.metaData);
+ }
+
+ static ParseResult<Property> addProperty(ParsedComponent component, ParsingPackage pkg,
+ Resources resources, XmlResourceParser parser, ParseInput input) {
+ ParseResult<Property> result = ParsingPackageUtils.parseMetaData(pkg, component,
+ resources, parser, "<property>", input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ final Property property = result.getResult();
+ if (property != null) {
+ component.addProperty(property);
+ }
+ return input.success(property);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedInstrumentation.java b/android/content/pm/parsing/component/ParsedInstrumentation.java
new file mode 100644
index 0000000..aa33e79
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedInstrumentation.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide */
+public class ParsedInstrumentation extends ParsedComponent {
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String targetPackage;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String targetProcesses;
+ boolean handleProfiling;
+ boolean functionalTest;
+
+ public ParsedInstrumentation() {
+ }
+
+ public void setTargetPackage(@Nullable String targetPackage) {
+ this.targetPackage = TextUtils.safeIntern(targetPackage);
+ }
+
+ public void setTargetProcesses(@Nullable String targetProcesses) {
+ this.targetProcesses = TextUtils.safeIntern(targetProcesses);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Instrumentation{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ sForInternedString.parcel(this.targetPackage, dest, flags);
+ sForInternedString.parcel(this.targetProcesses, dest, flags);
+ dest.writeBoolean(this.handleProfiling);
+ dest.writeBoolean(this.functionalTest);
+ }
+
+ protected ParsedInstrumentation(Parcel in) {
+ super(in);
+ this.targetPackage = sForInternedString.unparcel(in);
+ this.targetProcesses = sForInternedString.unparcel(in);
+ this.handleProfiling = in.readByte() != 0;
+ this.functionalTest = in.readByte() != 0;
+ }
+
+ public static final Parcelable.Creator<ParsedInstrumentation> CREATOR =
+ new Parcelable.Creator<ParsedInstrumentation>() {
+ @Override
+ public ParsedInstrumentation createFromParcel(Parcel source) {
+ return new ParsedInstrumentation(source);
+ }
+
+ @Override
+ public ParsedInstrumentation[] newArray(int size) {
+ return new ParsedInstrumentation[size];
+ }
+ };
+
+ @Nullable
+ public String getTargetPackage() {
+ return targetPackage;
+ }
+
+ @Nullable
+ public String getTargetProcesses() {
+ return targetProcesses;
+ }
+
+ public boolean isHandleProfiling() {
+ return handleProfiling;
+ }
+
+ public boolean isFunctionalTest() {
+ return functionalTest;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedInstrumentationUtils.java b/android/content/pm/parsing/component/ParsedInstrumentationUtils.java
new file mode 100644
index 0000000..89645fc
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedInstrumentationUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+
+import com.android.internal.R;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class ParsedInstrumentationUtils {
+
+ @NonNull
+ public static ParseResult<ParsedInstrumentation> parseInstrumentation(ParsingPackage pkg,
+ Resources res, XmlResourceParser parser, boolean useRoundIcon,
+ ParseInput input) throws IOException, XmlPullParserException {
+ ParsedInstrumentation
+ instrumentation = new ParsedInstrumentation();
+ String tag = "<" + parser.getName() + ">";
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestInstrumentation);
+ try {
+ ParseResult<ParsedInstrumentation> result = ParsedComponentUtils.parseComponent(
+ instrumentation, tag, pkg, sa, useRoundIcon, input,
+ R.styleable.AndroidManifestInstrumentation_banner,
+ null /*descriptionAttr*/,
+ R.styleable.AndroidManifestInstrumentation_icon,
+ R.styleable.AndroidManifestInstrumentation_label,
+ R.styleable.AndroidManifestInstrumentation_logo,
+ R.styleable.AndroidManifestInstrumentation_name,
+ R.styleable.AndroidManifestInstrumentation_roundIcon);
+ if (result.isError()) {
+ return result;
+ }
+
+ // @formatter:off
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ instrumentation.setTargetPackage(sa.getNonResourceString(R.styleable.AndroidManifestInstrumentation_targetPackage));
+ instrumentation.setTargetProcesses(sa.getNonResourceString(R.styleable.AndroidManifestInstrumentation_targetProcesses));
+ instrumentation.handleProfiling = sa.getBoolean(R.styleable.AndroidManifestInstrumentation_handleProfiling, false);
+ instrumentation.functionalTest = sa.getBoolean(R.styleable.AndroidManifestInstrumentation_functionalTest, false);
+ // @formatter:on
+ } finally {
+ sa.recycle();
+ }
+
+ return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, instrumentation,
+ input);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedIntentInfo.java b/android/content/pm/parsing/component/ParsedIntentInfo.java
new file mode 100644
index 0000000..463a181
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedIntentInfo.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.Nullable;
+import android.content.IntentFilter;
+import android.os.Parcel;
+import android.util.Pair;
+
+import com.android.internal.util.Parcelling;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** @hide **/
+public final class ParsedIntentInfo extends IntentFilter {
+
+ public static final Parceler PARCELER = new Parceler();
+
+ public static class Parceler implements Parcelling<ParsedIntentInfo> {
+
+ @Override
+ public void parcel(ParsedIntentInfo item, Parcel dest, int parcelFlags) {
+ item.writeIntentInfoToParcel(dest, parcelFlags);
+ }
+
+ @Override
+ public ParsedIntentInfo unparcel(Parcel source) {
+ return new ParsedIntentInfo(source);
+ }
+ }
+
+ public static class ListParceler implements Parcelling<List<ParsedIntentInfo>> {
+
+ /**
+ * <p>
+ * Implementation note: The serialized form for the intent list also contains the name
+ * of the concrete class that's stored in the list, and assumes that every element of the
+ * list is of the same type. This is very similar to the original parcelable mechanism.
+ * We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable
+ * and is public API. It also declares Parcelable related methods as final which means
+ * we can't extend them. The approach of using composition instead of inheritance leads to
+ * a large set of cascading changes in the PackageManagerService, which seem undesirable.
+ *
+ * <p>
+ * <b>WARNING: </b> The list of objects returned by this function might need to be fixed up
+ * to make sure their owner fields are consistent. See {@code fixupOwner}.
+ */
+ @Override
+ public void parcel(List<ParsedIntentInfo> item, Parcel dest, int parcelFlags) {
+ if (item == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ final int size = item.size();
+ dest.writeInt(size);
+
+ for (int index = 0; index < size; index++) {
+ PARCELER.parcel(item.get(index), dest, parcelFlags);
+ }
+ }
+
+ @Override
+ public List<ParsedIntentInfo> unparcel(Parcel source) {
+ int size = source.readInt();
+ if (size == -1) {
+ return null;
+ }
+
+ if (size == 0) {
+ return new ArrayList<>(0);
+ }
+
+ final ArrayList<ParsedIntentInfo> intentsList = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ intentsList.add(PARCELER.unparcel(source));
+ }
+
+ return intentsList;
+ }
+ }
+
+ public static class StringPairListParceler implements Parcelling<List<Pair<String, ParsedIntentInfo>>> {
+
+ @Override
+ public void parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest,
+ int parcelFlags) {
+ if (item == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ final int size = item.size();
+ dest.writeInt(size);
+
+ for (int index = 0; index < size; index++) {
+ Pair<String, ParsedIntentInfo> pair = item.get(index);
+ dest.writeString(pair.first);
+ PARCELER.parcel(pair.second, dest, parcelFlags);
+ }
+ }
+
+ @Override
+ public List<Pair<String, ParsedIntentInfo>> unparcel(Parcel source) {
+ int size = source.readInt();
+ if (size == -1) {
+ return null;
+ }
+
+ if (size == 0) {
+ return new ArrayList<>(0);
+ }
+
+ final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ list.add(Pair.create(source.readString(), PARCELER.unparcel(source)));
+ }
+
+ return list;
+ }
+ }
+
+ boolean hasDefault;
+ int labelRes;
+ @Nullable
+ CharSequence nonLocalizedLabel;
+ int icon;
+
+ public ParsedIntentInfo() {
+ }
+
+ public ParsedIntentInfo(Parcel in) {
+ super(in);
+ hasDefault = in.readBoolean();
+ labelRes = in.readInt();
+ nonLocalizedLabel = in.readCharSequence();
+ icon = in.readInt();
+ }
+
+ public void writeIntentInfoToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeBoolean(hasDefault);
+ dest.writeInt(labelRes);
+ dest.writeCharSequence(nonLocalizedLabel);
+ dest.writeInt(icon);
+ }
+
+ public String toString() {
+ return "ParsedIntentInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + '}';
+ }
+
+ public boolean isHasDefault() {
+ return hasDefault;
+ }
+
+ public int getLabelRes() {
+ return labelRes;
+ }
+
+ @Nullable
+ public CharSequence getNonLocalizedLabel() {
+ return nonLocalizedLabel;
+ }
+
+ public int getIcon() {
+ return icon;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
new file mode 100644
index 0000000..939e77f
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageParser;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.PatternMatcher;
+import android.util.Slog;
+import android.util.TypedValue;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+/** @hide */
+public class ParsedIntentInfoUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ public static final boolean DEBUG = false;
+
+ @NonNull
+ public static ParseResult<ParsedIntentInfo> parseIntentInfo(String className,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
+ boolean allowAutoVerify, ParseInput input)
+ throws XmlPullParserException, IOException {
+ ParsedIntentInfo intentInfo = new ParsedIntentInfo();
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestIntentFilter);
+ try {
+ intentInfo.setPriority(sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0));
+ intentInfo.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0));
+
+ TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label);
+ if (v != null) {
+ intentInfo.labelRes = v.resourceId;
+ if (v.resourceId == 0) {
+ intentInfo.nonLocalizedLabel = v.coerceToString();
+ }
+ }
+
+ if (ParsingPackageUtils.sUseRoundIcon) {
+ intentInfo.icon = sa.getResourceId(
+ R.styleable.AndroidManifestIntentFilter_roundIcon, 0);
+ }
+
+ if (intentInfo.icon == 0) {
+ intentInfo.icon = sa.getResourceId(R.styleable.AndroidManifestIntentFilter_icon, 0);
+ }
+
+ if (allowAutoVerify) {
+ intentInfo.setAutoVerify(sa.getBoolean(
+ R.styleable.AndroidManifestIntentFilter_autoVerify,
+ false));
+ }
+ } finally {
+ sa.recycle();
+ }
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult result;
+ String nodeName = parser.getName();
+ switch (nodeName) {
+ case "action": {
+ String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES,
+ "name");
+ if (value == null) {
+ result = input.error("No value supplied for <android:name>");
+ } else if (value.isEmpty()) {
+ intentInfo.addAction(value);
+ // Prior to R, this was not a failure
+ result = input.deferError("No value supplied for <android:name>",
+ ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
+ } else {
+ intentInfo.addAction(value);
+ result = input.success(null);
+ }
+ break;
+ }
+ case "category": {
+ String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES,
+ "name");
+ if (value == null) {
+ result = input.error("No value supplied for <android:name>");
+ } else if (value.isEmpty()) {
+ intentInfo.addCategory(value);
+ // Prior to R, this was not a failure
+ result = input.deferError("No value supplied for <android:name>",
+ ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
+ } else {
+ intentInfo.addCategory(value);
+ result = input.success(null);
+ }
+ break;
+ }
+ case "data":
+ result = parseData(intentInfo, res, parser, allowGlobs, input);
+ break;
+ default:
+ result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ intentInfo.hasDefault = intentInfo.hasCategory(Intent.CATEGORY_DEFAULT);
+
+ if (DEBUG) {
+ final StringBuilder cats = new StringBuilder("Intent d=");
+ cats.append(intentInfo.isHasDefault());
+ cats.append(", cat=");
+
+ final Iterator<String> it = intentInfo.categoriesIterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ cats.append(' ');
+ cats.append(it.next());
+ }
+ }
+ Slog.d(TAG, cats.toString());
+ }
+
+ return input.success(intentInfo);
+ }
+
+ @NonNull
+ private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo,
+ Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
+ TypedArray sa = resources.obtainAttributes(parser, R.styleable.AndroidManifestData);
+ try {
+ String str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_mimeType, 0);
+ if (str != null) {
+ try {
+ intentInfo.addDataType(str);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ return input.error(e.toString());
+ }
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_mimeGroup, 0);
+ if (str != null) {
+ intentInfo.addMimeGroup(str);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_scheme, 0);
+ if (str != null) {
+ intentInfo.addDataScheme(str);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_ssp, 0);
+ if (str != null) {
+ intentInfo.addDataSchemeSpecificPart(str,
+ PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_sspPrefix, 0);
+ if (str != null) {
+ intentInfo.addDataSchemeSpecificPart(str,
+ PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_sspPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "sspPattern not allowed here; ssp must be literal");
+ }
+ intentInfo.addDataSchemeSpecificPart(str,
+ PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_sspAdvancedPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "sspAdvancedPattern not allowed here; ssp must be literal");
+ }
+ intentInfo.addDataSchemeSpecificPart(str,
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_sspSuffix, 0);
+ if (str != null) {
+ intentInfo.addDataSchemeSpecificPart(str,
+ PatternMatcher.PATTERN_SUFFIX);
+ }
+
+
+ String host = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_host, 0);
+ String port = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_port, 0);
+ if (host != null) {
+ intentInfo.addDataAuthority(host, port);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_path, 0);
+ if (str != null) {
+ intentInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathPrefix, 0);
+ if (str != null) {
+ intentInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "pathPattern not allowed here; path must be literal");
+ }
+ intentInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "pathAdvancedPattern not allowed here; path must be literal");
+ }
+ intentInfo.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathSuffix, 0);
+ if (str != null) {
+ intentInfo.addDataPath(str, PatternMatcher.PATTERN_SUFFIX);
+ }
+
+
+ return input.success(null);
+ } finally {
+ sa.recycle();
+ }
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedMainComponent.java b/android/content/pm/parsing/component/ParsedMainComponent.java
new file mode 100644
index 0000000..033e30f
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedMainComponent.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide */
+public class ParsedMainComponent extends ParsedComponent {
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String processName;
+ boolean directBootAware;
+ boolean enabled = true;
+ boolean exported;
+ int order;
+
+ @Nullable
+ String splitName;
+ @Nullable
+ String[] attributionTags;
+
+ public ParsedMainComponent() {
+ }
+
+ public ParsedMainComponent(ParsedMainComponent other) {
+ super(other);
+ this.processName = other.processName;
+ this.directBootAware = other.directBootAware;
+ this.enabled = other.enabled;
+ this.exported = other.exported;
+ this.order = other.order;
+ this.splitName = other.splitName;
+ this.attributionTags = other.attributionTags;
+ }
+
+ public ParsedMainComponent setProcessName(String processName) {
+ this.processName = TextUtils.safeIntern(processName);
+ return this;
+ }
+
+ public ParsedMainComponent setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ /**
+ * A main component's name is a class name. This makes code slightly more readable.
+ */
+ public String getClassName() {
+ return getName();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ sForInternedString.parcel(this.processName, dest, flags);
+ dest.writeBoolean(this.directBootAware);
+ dest.writeBoolean(this.enabled);
+ dest.writeBoolean(this.exported);
+ dest.writeInt(this.order);
+ dest.writeString(this.splitName);
+ dest.writeString8Array(this.attributionTags);
+ }
+
+ protected ParsedMainComponent(Parcel in) {
+ super(in);
+ this.processName = sForInternedString.unparcel(in);
+ this.directBootAware = in.readBoolean();
+ this.enabled = in.readBoolean();
+ this.exported = in.readBoolean();
+ this.order = in.readInt();
+ this.splitName = in.readString();
+ this.attributionTags = in.createString8Array();
+ }
+
+ public static final Parcelable.Creator<ParsedMainComponent> CREATOR =
+ new Parcelable.Creator<ParsedMainComponent>() {
+ @Override
+ public ParsedMainComponent createFromParcel(Parcel source) {
+ return new ParsedMainComponent(source);
+ }
+
+ @Override
+ public ParsedMainComponent[] newArray(int size) {
+ return new ParsedMainComponent[size];
+ }
+ };
+
+ @Nullable
+ public String getProcessName() {
+ return processName;
+ }
+
+ public boolean isDirectBootAware() {
+ return directBootAware;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public boolean isExported() {
+ return exported;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ @Nullable
+ public String getSplitName() {
+ return splitName;
+ }
+
+ @Nullable
+ public String[] getAttributionTags() {
+ return attributionTags;
+ }
+
+ public ParsedMainComponent setDirectBootAware(boolean value) {
+ directBootAware = value;
+ return this;
+ }
+
+ public ParsedMainComponent setExported(boolean value) {
+ exported = value;
+ return this;
+ }
+
+ public ParsedMainComponent setSplitName(@Nullable String value) {
+ splitName = value;
+ return this;
+ }
+
+ public ParsedMainComponent setAttributionTags(@Nullable String[] value) {
+ attributionTags = value;
+ return this;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/android/content/pm/parsing/component/ParsedMainComponentUtils.java
new file mode 100644
index 0000000..54bcbdd
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedMainComponentUtils.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentFilter;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+class ParsedMainComponentUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ @NonNull
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ static <Component extends ParsedMainComponent> ParseResult<Component> parseMainComponent(
+ Component component, String tag, String[] separateProcesses, ParsingPackage pkg,
+ TypedArray array, int flags, boolean useRoundIcon, ParseInput input,
+ int bannerAttr, int descriptionAttr, @Nullable Integer directBootAwareAttr,
+ @Nullable Integer enabledAttr, int iconAttr, int labelAttr, int logoAttr, int nameAttr,
+ @Nullable Integer processAttr, int roundIconAttr, @Nullable Integer splitNameAttr,
+ @Nullable Integer attributionTagsAttr) {
+ ParseResult<Component> result = ParsedComponentUtils.parseComponent(component, tag, pkg,
+ array, useRoundIcon, input, bannerAttr, descriptionAttr, iconAttr, labelAttr,
+ logoAttr, nameAttr, roundIconAttr);
+ if (result.isError()) {
+ return result;
+ }
+
+ if (directBootAwareAttr != null) {
+ component.directBootAware = array.getBoolean(directBootAwareAttr, false);
+ if (component.isDirectBootAware()) {
+ pkg.setPartiallyDirectBootAware(true);
+ }
+ }
+
+ if (enabledAttr != null) {
+ component.enabled = array.getBoolean(enabledAttr, true);
+ }
+
+ if (processAttr != null) {
+ CharSequence processName;
+ if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.FROYO) {
+ processName = array.getNonConfigurationString(processAttr,
+ Configuration.NATIVE_CONFIG_VERSION);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ processName = array.getNonResourceString(processAttr);
+ }
+
+ // Backwards-compat, ignore error
+ ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
+ pkg.getPackageName(), pkg.getProcessName(), processName, flags,
+ separateProcesses, input);
+ if (processNameResult.isError()) {
+ return input.error(processNameResult);
+ }
+
+ component.setProcessName(processNameResult.getResult());
+ }
+
+ if (splitNameAttr != null) {
+ component.splitName = array.getNonConfigurationString(splitNameAttr, 0);
+ }
+
+ if (attributionTagsAttr != null) {
+ final String attributionTags = array.getNonConfigurationString(attributionTagsAttr, 0);
+ if (attributionTags != null) {
+ component.attributionTags = attributionTags.split("\\|");
+ }
+ }
+
+ return input.success(component);
+ }
+
+ static ParseResult<ParsedIntentInfo> parseIntentFilter(
+ ParsedMainComponent mainComponent,
+ ParsingPackage pkg, Resources resources, XmlResourceParser parser,
+ boolean visibleToEphemeral, boolean allowGlobs, boolean allowAutoVerify,
+ boolean allowImplicitEphemeralVisibility, boolean failOnNoActions,
+ ParseInput input) throws IOException, XmlPullParserException {
+ ParseResult<ParsedIntentInfo> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
+ mainComponent.getName(), pkg, resources, parser, allowGlobs,
+ allowAutoVerify, input);
+ if (intentResult.isError()) {
+ return input.error(intentResult);
+ }
+
+ ParsedIntentInfo intent = intentResult.getResult();
+ int actionCount = intent.countActions();
+ if (actionCount == 0 && failOnNoActions) {
+ Slog.w(TAG, "No actions in " + parser.getName() + " at " + pkg.getBaseApkPath() + " "
+ + parser.getPositionDescription());
+ // Backward-compat, do not actually fail
+ return input.success(null);
+ }
+
+ int intentVisibility;
+ if (visibleToEphemeral) {
+ intentVisibility = IntentFilter.VISIBILITY_EXPLICIT;
+ } else if (allowImplicitEphemeralVisibility
+ && ComponentParseUtils.isImplicitlyExposedIntent(intent)){
+ intentVisibility = IntentFilter.VISIBILITY_IMPLICIT;
+ } else {
+ intentVisibility = IntentFilter.VISIBILITY_NONE;
+ }
+ intent.setVisibilityToInstantApp(intentVisibility);
+
+ return input.success(intentResult.getResult());
+ }
+
+}
diff --git a/android/content/pm/parsing/component/ParsedPermission.java b/android/content/pm/parsing/component/ParsedPermission.java
new file mode 100644
index 0000000..37e0e87
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedPermission.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.Nullable;
+import android.content.pm.PermissionInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.util.Locale;
+import java.util.Set;
+
+/** @hide */
+public class ParsedPermission extends ParsedComponent {
+
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
+ @Nullable
+ String backgroundPermission;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String group;
+ int requestRes;
+ int protectionLevel;
+ boolean tree;
+ @Nullable
+ private ParsedPermissionGroup parsedPermissionGroup;
+ @Nullable
+ Set<String> knownCerts;
+
+ @VisibleForTesting
+ public ParsedPermission() {
+ }
+
+ public ParsedPermission(ParsedPermission other) {
+ super(other);
+ this.backgroundPermission = other.backgroundPermission;
+ this.group = other.group;
+ this.requestRes = other.requestRes;
+ this.protectionLevel = other.protectionLevel;
+ this.tree = other.tree;
+ this.parsedPermissionGroup = other.parsedPermissionGroup;
+ }
+
+ public ParsedPermission setGroup(String group) {
+ this.group = TextUtils.safeIntern(group);
+ return this;
+ }
+
+ public ParsedPermission setFlags(int flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ public boolean isRuntime() {
+ return getProtection() == PermissionInfo.PROTECTION_DANGEROUS;
+ }
+
+ public boolean isAppOp() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+ }
+
+ @PermissionInfo.Protection
+ public int getProtection() {
+ return protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ }
+
+ public int getProtectionFlags() {
+ return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
+ }
+
+ public @Nullable Set<String> getKnownCerts() {
+ return knownCerts;
+ }
+
+ protected void setKnownCert(String knownCert) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+ }
+
+ protected void setKnownCerts(String[] knownCerts) {
+ this.knownCerts = new ArraySet<>();
+ for (String knownCert : knownCerts) {
+ this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
+ public int calculateFootprint() {
+ int size = getName().length();
+ if (getNonLocalizedLabel() != null) {
+ size += getNonLocalizedLabel().length();
+ }
+ return size;
+ }
+
+ public String toString() {
+ return "Permission{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + getName() + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(this.backgroundPermission);
+ dest.writeString(this.group);
+ dest.writeInt(this.requestRes);
+ dest.writeInt(this.protectionLevel);
+ dest.writeBoolean(this.tree);
+ dest.writeParcelable(this.parsedPermissionGroup, flags);
+ sForStringSet.parcel(knownCerts, dest, flags);
+ }
+
+ protected ParsedPermission(Parcel in) {
+ super(in);
+ // We use the boot classloader for all classes that we load.
+ final ClassLoader boot = Object.class.getClassLoader();
+ this.backgroundPermission = in.readString();
+ this.group = in.readString();
+ this.requestRes = in.readInt();
+ this.protectionLevel = in.readInt();
+ this.tree = in.readBoolean();
+ this.parsedPermissionGroup = in.readParcelable(boot);
+ this.knownCerts = sForStringSet.unparcel(in);
+ }
+
+ public static final Parcelable.Creator<ParsedPermission> CREATOR =
+ new Parcelable.Creator<ParsedPermission>() {
+ @Override
+ public ParsedPermission createFromParcel(Parcel source) {
+ return new ParsedPermission(source);
+ }
+
+ @Override
+ public ParsedPermission[] newArray(int size) {
+ return new ParsedPermission[size];
+ }
+ };
+
+ @Nullable
+ public String getBackgroundPermission() {
+ return backgroundPermission;
+ }
+
+ @Nullable
+ public String getGroup() {
+ return group;
+ }
+
+ public int getRequestRes() {
+ return requestRes;
+ }
+
+ public int getProtectionLevel() {
+ return protectionLevel;
+ }
+
+ public boolean isTree() {
+ return tree;
+ }
+
+ @Nullable
+ public ParsedPermissionGroup getParsedPermissionGroup() {
+ return parsedPermissionGroup;
+ }
+
+ public ParsedPermission setProtectionLevel(int value) {
+ protectionLevel = value;
+ return this;
+ }
+
+ public ParsedPermission setParsedPermissionGroup(@Nullable ParsedPermissionGroup value) {
+ parsedPermissionGroup = value;
+ return this;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedPermissionGroup.java b/android/content/pm/parsing/component/ParsedPermissionGroup.java
new file mode 100644
index 0000000..741c00c
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedPermissionGroup.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/** @hide */
+public class ParsedPermissionGroup extends ParsedComponent {
+
+ int requestDetailResourceId;
+ int backgroundRequestResourceId;
+ int backgroundRequestDetailResourceId;
+ int requestRes;
+ int priority;
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public String toString() {
+ return "PermissionGroup{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + getName() + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.requestDetailResourceId);
+ dest.writeInt(this.backgroundRequestResourceId);
+ dest.writeInt(this.backgroundRequestDetailResourceId);
+ dest.writeInt(this.requestRes);
+ dest.writeInt(this.priority);
+ }
+
+ public ParsedPermissionGroup() {
+ }
+
+ protected ParsedPermissionGroup(Parcel in) {
+ super(in);
+ this.requestDetailResourceId = in.readInt();
+ this.backgroundRequestResourceId = in.readInt();
+ this.backgroundRequestDetailResourceId = in.readInt();
+ this.requestRes = in.readInt();
+ this.priority = in.readInt();
+ }
+
+ public static final Parcelable.Creator<ParsedPermissionGroup> CREATOR =
+ new Parcelable.Creator<ParsedPermissionGroup>() {
+ @Override
+ public ParsedPermissionGroup createFromParcel(Parcel source) {
+ return new ParsedPermissionGroup(source);
+ }
+
+ @Override
+ public ParsedPermissionGroup[] newArray(int size) {
+ return new ParsedPermissionGroup[size];
+ }
+ };
+
+ public int getRequestDetailResourceId() {
+ return requestDetailResourceId;
+ }
+
+ public int getBackgroundRequestResourceId() {
+ return backgroundRequestResourceId;
+ }
+
+ public int getBackgroundRequestDetailResourceId() {
+ return backgroundRequestDetailResourceId;
+ }
+
+ public int getRequestRes() {
+ return requestRes;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedPermissionUtils.java b/android/content/pm/parsing/component/ParsedPermissionUtils.java
new file mode 100644
index 0000000..8afa70e
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.content.pm.PermissionInfo;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class ParsedPermissionUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ @NonNull
+ public static ParseResult<ParsedPermission> parsePermission(ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
+ throws IOException, XmlPullParserException {
+ String packageName = pkg.getPackageName();
+ ParsedPermission
+ permission = new ParsedPermission();
+ String tag = "<" + parser.getName() + ">";
+ final ParseResult<ParsedPermission> result;
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission);
+ try {
+ result = ParsedComponentUtils.parseComponent(
+ permission, tag, pkg, sa, useRoundIcon, input,
+ R.styleable.AndroidManifestPermission_banner,
+ R.styleable.AndroidManifestPermission_description,
+ R.styleable.AndroidManifestPermission_icon,
+ R.styleable.AndroidManifestPermission_label,
+ R.styleable.AndroidManifestPermission_logo,
+ R.styleable.AndroidManifestPermission_name,
+ R.styleable.AndroidManifestPermission_roundIcon);
+ if (result.isError()) {
+ return result;
+ }
+
+ if (sa.hasValue(
+ R.styleable.AndroidManifestPermission_backgroundPermission)) {
+ if ("android".equals(packageName)) {
+ permission.backgroundPermission = sa.getNonResourceString(
+ R.styleable
+ .AndroidManifestPermission_backgroundPermission);
+ } else {
+ Slog.w(TAG, packageName + " defines a background permission. Only the "
+ + "'android' package can do that.");
+ }
+ }
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ permission.setGroup(sa.getNonResourceString(
+ R.styleable.AndroidManifestPermission_permissionGroup));
+
+ permission.requestRes = sa.getResourceId(
+ R.styleable.AndroidManifestPermission_request, 0);
+
+ permission.protectionLevel = sa.getInt(
+ R.styleable.AndroidManifestPermission_protectionLevel,
+ PermissionInfo.PROTECTION_NORMAL);
+
+ permission.flags = sa.getInt(
+ R.styleable.AndroidManifestPermission_permissionFlags, 0);
+
+ final int knownCertsResource = sa.getResourceId(
+ R.styleable.AndroidManifestPermission_knownCerts, 0);
+ if (knownCertsResource != 0) {
+ // The knownCerts attribute supports both a string array resource as well as a
+ // string resource for the case where the permission should only be granted to a
+ // single known signer.
+ final String resourceType = res.getResourceTypeName(knownCertsResource);
+ if (resourceType.equals("array")) {
+ final String[] knownCerts = res.getStringArray(knownCertsResource);
+ if (knownCerts != null) {
+ permission.setKnownCerts(knownCerts);
+ }
+ } else {
+ final String knownCert = res.getString(knownCertsResource);
+ if (knownCert != null) {
+ permission.setKnownCert(knownCert);
+ }
+ }
+ if (permission.knownCerts == null) {
+ Slog.w(TAG, packageName + " defines a knownSigner permission but"
+ + " the provided knownCerts resource is null");
+ }
+ } else {
+ // If the knownCerts resource ID is null check if the app specified a string
+ // value for the attribute representing a single trusted signer.
+ final String knownCert = sa.getString(
+ R.styleable.AndroidManifestPermission_knownCerts);
+ if (knownCert != null) {
+ permission.setKnownCert(knownCert);
+ }
+ }
+
+ // For now only platform runtime permissions can be restricted
+ if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
+ permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
+ permission.flags &= ~PermissionInfo.FLAG_SOFT_RESTRICTED;
+ } else {
+ // The platform does not get to specify conflicting permissions
+ if ((permission.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0
+ && (permission.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) {
+ throw new IllegalStateException("Permission cannot be both soft and hard"
+ + " restricted: " + permission.getName());
+ }
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ permission.protectionLevel = PermissionInfo.fixProtectionLevel(permission.protectionLevel);
+
+ final int otherProtectionFlags = permission.getProtectionFlags()
+ & ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT
+ | PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY);
+ if (otherProtectionFlags != 0
+ && permission.getProtection() != PermissionInfo.PROTECTION_SIGNATURE
+ && permission.getProtection() != PermissionInfo.PROTECTION_INTERNAL) {
+ return input.error("<permission> protectionLevel specifies a non-instant, non-appop,"
+ + " non-runtimeOnly flag but is not based on signature or internal type");
+ }
+
+ return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
+ }
+
+ @NonNull
+ public static ParseResult<ParsedPermission> parsePermissionTree(ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
+ throws IOException, XmlPullParserException {
+ ParsedPermission permission = new ParsedPermission();
+ String tag = "<" + parser.getName() + ">";
+ final ParseResult<ParsedPermission> result;
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionTree);
+ try {
+ result = ParsedComponentUtils.parseComponent(
+ permission, tag, pkg, sa, useRoundIcon, input,
+ R.styleable.AndroidManifestPermissionTree_banner,
+ null /*descriptionAttr*/,
+ R.styleable.AndroidManifestPermissionTree_icon,
+ R.styleable.AndroidManifestPermissionTree_label,
+ R.styleable.AndroidManifestPermissionTree_logo,
+ R.styleable.AndroidManifestPermissionTree_name,
+ R.styleable.AndroidManifestPermissionTree_roundIcon);
+ if (result.isError()) {
+ return result;
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ int index = permission.getName().indexOf('.');
+ if (index > 0) {
+ index = permission.getName().indexOf('.', index + 1);
+ }
+ if (index < 0) {
+ return input.error("<permission-tree> name has less than three segments: "
+ + permission.getName());
+ }
+
+ permission.protectionLevel = PermissionInfo.PROTECTION_NORMAL;
+ permission.tree = true;
+
+ return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission,
+ input);
+ }
+
+ @NonNull
+ public static ParseResult<ParsedPermissionGroup> parsePermissionGroup(ParsingPackage pkg,
+ Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
+ throws IOException, XmlPullParserException {
+ ParsedPermissionGroup
+ permissionGroup = new ParsedPermissionGroup();
+ String tag = "<" + parser.getName() + ">";
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionGroup);
+ try {
+ ParseResult<ParsedPermissionGroup> result = ParsedComponentUtils.parseComponent(
+ permissionGroup, tag, pkg, sa, useRoundIcon, input,
+ R.styleable.AndroidManifestPermissionGroup_banner,
+ R.styleable.AndroidManifestPermissionGroup_description,
+ R.styleable.AndroidManifestPermissionGroup_icon,
+ R.styleable.AndroidManifestPermissionGroup_label,
+ R.styleable.AndroidManifestPermissionGroup_logo,
+ R.styleable.AndroidManifestPermissionGroup_name,
+ R.styleable.AndroidManifestPermissionGroup_roundIcon);
+ if (result.isError()) {
+ return result;
+ }
+
+ // @formatter:off
+ permissionGroup.requestDetailResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0);
+ permissionGroup.backgroundRequestResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0);
+ permissionGroup.backgroundRequestDetailResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0);
+ permissionGroup.requestRes = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0);
+ permissionGroup.flags = sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0);
+ permissionGroup.priority = sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0);
+ // @formatter:on
+ } finally {
+ sa.recycle();
+ }
+
+ return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permissionGroup,
+ input);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedProcess.java b/android/content/pm/parsing/component/ParsedProcess.java
new file mode 100644
index 0000000..54a60d3
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProcess.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static java.util.Collections.emptySet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Set;
+
+/** @hide */
+@DataClass(genGetters = true, genSetters = false, genParcelable = true, genAidl = false,
+ genBuilder = false)
+public class ParsedProcess implements Parcelable {
+
+ @NonNull
+ protected String name;
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+ protected Set<String> deniedPermissions = emptySet();
+
+ @ApplicationInfo.GwpAsanMode
+ protected int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT;
+ @ApplicationInfo.MemtagMode
+ protected int memtagMode = ApplicationInfo.MEMTAG_DEFAULT;
+ @ApplicationInfo.NativeHeapZeroInitialized
+ protected int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
+
+ public ParsedProcess() {
+ }
+
+ public ParsedProcess(@NonNull ParsedProcess other) {
+ name = other.name;
+ deniedPermissions = new ArraySet<>(other.deniedPermissions);
+ }
+
+ public void addStateFrom(@NonNull ParsedProcess other) {
+ deniedPermissions = CollectionUtils.addAll(deniedPermissions, other.deniedPermissions);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public ParsedProcess(
+ @NonNull String name,
+ @NonNull Set<String> deniedPermissions,
+ @ApplicationInfo.GwpAsanMode int gwpAsanMode,
+ @ApplicationInfo.MemtagMode int memtagMode,
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ this.name = name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.deniedPermissions = deniedPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deniedPermissions);
+ this.gwpAsanMode = gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getName() {
+ return name;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getDeniedPermissions() {
+ return deniedPermissions;
+ }
+
+ @DataClass.Generated.Member
+ public @ApplicationInfo.GwpAsanMode int getGwpAsanMode() {
+ return gwpAsanMode;
+ }
+
+ @DataClass.Generated.Member
+ public @ApplicationInfo.MemtagMode int getMemtagMode() {
+ return memtagMode;
+ }
+
+ @DataClass.Generated.Member
+ public @ApplicationInfo.NativeHeapZeroInitialized int getNativeHeapZeroInitialized() {
+ return nativeHeapZeroInitialized;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Set<String>> sParcellingForDeniedPermissions =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForInternedStringSet.class);
+ static {
+ if (sParcellingForDeniedPermissions == null) {
+ sParcellingForDeniedPermissions = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForInternedStringSet());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(name);
+ sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
+ dest.writeInt(gwpAsanMode);
+ dest.writeInt(memtagMode);
+ dest.writeInt(nativeHeapZeroInitialized);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ParsedProcess(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String _name = in.readString();
+ Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
+ int _gwpAsanMode = in.readInt();
+ int _memtagMode = in.readInt();
+ int _nativeHeapZeroInitialized = in.readInt();
+
+ this.name = _name;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, name);
+ this.deniedPermissions = _deniedPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deniedPermissions);
+ this.gwpAsanMode = _gwpAsanMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+ this.memtagMode = _memtagMode;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.MemtagMode.class, null, memtagMode);
+ this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
+ com.android.internal.util.AnnotationValidations.validate(
+ ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ParsedProcess> CREATOR
+ = new Parcelable.Creator<ParsedProcess>() {
+ @Override
+ public ParsedProcess[] newArray(int size) {
+ return new ParsedProcess[size];
+ }
+
+ @Override
+ public ParsedProcess createFromParcel(@NonNull Parcel in) {
+ return new ParsedProcess(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1615850515058L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java",
+ inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprotected @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprotected @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\[email protected](genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/parsing/component/ParsedProcessUtils.java b/android/content/pm/parsing/component/ParsedProcessUtils.java
new file mode 100644
index 0000000..e417e74
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.R;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+
+/** @hide */
+public class ParsedProcessUtils {
+
+ @NonNull
+ private static ParseResult<Set<String>> parseDenyPermission(Set<String> perms,
+ Resources res, XmlResourceParser parser, ParseInput input)
+ throws IOException, XmlPullParserException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestDenyPermission);
+ try {
+ String perm = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestDenyPermission_name, 0);
+ if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) {
+ perms = CollectionUtils.add(perms, perm);
+ }
+ } finally {
+ sa.recycle();
+ }
+ XmlUtils.skipCurrentTag(parser);
+ return input.success(perms);
+ }
+
+ @NonNull
+ private static ParseResult<Set<String>> parseAllowPermission(Set<String> perms, Resources res,
+ XmlResourceParser parser, ParseInput input)
+ throws IOException, XmlPullParserException {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAllowPermission);
+ try {
+ String perm = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestAllowPermission_name, 0);
+ if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) {
+ perms = CollectionUtils.remove(perms, perm);
+ }
+ } finally {
+ sa.recycle();
+ }
+ XmlUtils.skipCurrentTag(parser);
+ return input.success(perms);
+ }
+
+ @NonNull
+ private static ParseResult<ParsedProcess> parseProcess(Set<String> perms, String[] separateProcesses,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
+ ParseInput input) throws IOException, XmlPullParserException {
+ ParsedProcess proc = new ParsedProcess();
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProcess);
+ try {
+ if (perms != null) {
+ proc.deniedPermissions = new ArraySet<>(perms);
+ }
+
+ proc.name = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestProcess_process, 0);
+ ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
+ pkg.getPackageName(), pkg.getPackageName(), proc.name, flags, separateProcesses,
+ input);
+ if (processNameResult.isError()) {
+ return input.error(processNameResult);
+ }
+
+ proc.name = processNameResult.getResult();
+
+ if (proc.name == null || proc.name.length() <= 0) {
+ return input.error("<process> does not specify android:process");
+ }
+
+ proc.gwpAsanMode = sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1);
+ proc.memtagMode = sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1);
+ if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized)) {
+ Boolean v = sa.getBoolean(
+ R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized, false);
+ proc.nativeHeapZeroInitialized =
+ v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED;
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ ParseResult<?> result;
+
+ String tagName = parser.getName();
+ switch (tagName) {
+ case "deny-permission":
+ ParseResult<Set<String>> denyResult = parseDenyPermission(
+ proc.deniedPermissions, res, parser, input);
+ result = denyResult;
+ if (denyResult.isSuccess()) {
+ proc.deniedPermissions = denyResult.getResult();
+ }
+ break;
+ case "allow-permission":
+ ParseResult<Set<String>> allowResult = parseAllowPermission(
+ proc.deniedPermissions, res, parser, input);
+ result = allowResult;
+ if (allowResult.isSuccess()) {
+ proc.deniedPermissions = allowResult.getResult();
+ }
+ break;
+ default:
+ result = ParsingUtils.unknownTag("<process>", pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ return input.success(proc);
+ }
+
+ @NonNull
+ public static ParseResult<ArrayMap<String, ParsedProcess>> parseProcesses(
+ String[] separateProcesses, ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, int flags, ParseInput input)
+ throws IOException, XmlPullParserException {
+ Set<String> deniedPerms = null;
+ ArrayMap<String, ParsedProcess> processes = new ArrayMap<>();
+
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ ParseResult<?> result;
+
+ String tagName = parser.getName();
+ switch (tagName) {
+ case "deny-permission":
+ ParseResult<Set<String>> denyResult = parseDenyPermission(deniedPerms, res,
+ parser, input);
+ result = denyResult;
+ if (denyResult.isSuccess()) {
+ deniedPerms = denyResult.getResult();
+ }
+ break;
+ case "allow-permission":
+ ParseResult<Set<String>> allowResult = parseAllowPermission(deniedPerms, res,
+ parser, input);
+ result = allowResult;
+ if (allowResult.isSuccess()) {
+ deniedPerms = allowResult.getResult();
+ }
+ break;
+ case "process":
+ ParseResult<ParsedProcess> processResult = parseProcess(deniedPerms,
+ separateProcesses, pkg, res, parser, flags, input);
+ result = processResult;
+ if (processResult.isSuccess()) {
+ ParsedProcess process = processResult.getResult();
+ if (processes.put(process.name, process) != null) {
+ result = input.error(
+ "<process> specified existing name '" + process.name + "'");
+ }
+ }
+ break;
+ default:
+ result = ParsingUtils.unknownTag("<processes>", pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ }
+
+ return input.success(processes);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedProvider.java b/android/content/pm/parsing/component/ParsedProvider.java
new file mode 100644
index 0000000..fcf6e87
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProvider.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.PathPermission;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.text.TextUtils;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide **/
+public class ParsedProvider extends ParsedMainComponent {
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String authority;
+ boolean syncable;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String readPermission;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String writePermission;
+ boolean grantUriPermissions;
+ boolean forceUriPermissions;
+ boolean multiProcess;
+ int initOrder;
+ @Nullable
+ PatternMatcher[] uriPermissionPatterns;
+ @Nullable
+ PathPermission[] pathPermissions;
+
+ public ParsedProvider(ParsedProvider other) {
+ super(other);
+
+ this.authority = other.authority;
+ this.syncable = other.syncable;
+ this.readPermission = other.readPermission;
+ this.writePermission = other.writePermission;
+ this.grantUriPermissions = other.grantUriPermissions;
+ this.forceUriPermissions = other.forceUriPermissions;
+ this.multiProcess = other.multiProcess;
+ this.initOrder = other.initOrder;
+ this.uriPermissionPatterns = other.uriPermissionPatterns;
+ this.pathPermissions = other.pathPermissions;
+ }
+
+ public void setAuthority(String authority) {
+ this.authority = TextUtils.safeIntern(authority);
+ }
+
+ public void setSyncable(boolean syncable) {
+ this.syncable = syncable;
+ }
+
+ public void setReadPermission(String readPermission) {
+ // Empty string must be converted to null
+ this.readPermission = TextUtils.isEmpty(readPermission)
+ ? null : readPermission.intern();
+ }
+
+ public void setWritePermission(String writePermission) {
+ // Empty string must be converted to null
+ this.writePermission = TextUtils.isEmpty(writePermission)
+ ? null : writePermission.intern();
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Provider{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(this.authority);
+ dest.writeBoolean(this.syncable);
+ sForInternedString.parcel(this.readPermission, dest, flags);
+ sForInternedString.parcel(this.writePermission, dest, flags);
+ dest.writeBoolean(this.grantUriPermissions);
+ dest.writeBoolean(this.forceUriPermissions);
+ dest.writeBoolean(this.multiProcess);
+ dest.writeInt(this.initOrder);
+ dest.writeTypedArray(this.uriPermissionPatterns, flags);
+ dest.writeTypedArray(this.pathPermissions, flags);
+ }
+
+ public ParsedProvider() {
+ }
+
+ protected ParsedProvider(Parcel in) {
+ super(in);
+ //noinspection ConstantConditions
+ this.authority = in.readString();
+ this.syncable = in.readBoolean();
+ this.readPermission = sForInternedString.unparcel(in);
+ this.writePermission = sForInternedString.unparcel(in);
+ this.grantUriPermissions = in.readBoolean();
+ this.forceUriPermissions = in.readBoolean();
+ this.multiProcess = in.readBoolean();
+ this.initOrder = in.readInt();
+ this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+ this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
+ }
+
+ public static final Parcelable.Creator<ParsedProvider> CREATOR = new Creator<ParsedProvider>() {
+ @Override
+ public ParsedProvider createFromParcel(Parcel source) {
+ return new ParsedProvider(source);
+ }
+
+ @Override
+ public ParsedProvider[] newArray(int size) {
+ return new ParsedProvider[size];
+ }
+ };
+
+ @NonNull
+ public String getAuthority() {
+ return authority;
+ }
+
+ public boolean isSyncable() {
+ return syncable;
+ }
+
+ @Nullable
+ public String getReadPermission() {
+ return readPermission;
+ }
+
+ @Nullable
+ public String getWritePermission() {
+ return writePermission;
+ }
+
+ public boolean isGrantUriPermissions() {
+ return grantUriPermissions;
+ }
+
+ public boolean isForceUriPermissions() {
+ return forceUriPermissions;
+ }
+
+ public boolean isMultiProcess() {
+ return multiProcess;
+ }
+
+ public int getInitOrder() {
+ return initOrder;
+ }
+
+ @Nullable
+ public PatternMatcher[] getUriPermissionPatterns() {
+ return uriPermissionPatterns;
+ }
+
+ @Nullable
+ public PathPermission[] getPathPermissions() {
+ return pathPermissions;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedProviderUtils.java b/android/content/pm/parsing/component/ParsedProviderUtils.java
new file mode 100644
index 0000000..28fd919
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProviderUtils.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.component.ComponentParseUtils.flag;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageParser;
+import android.content.pm.PathPermission;
+import android.content.pm.ProviderInfo;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+import android.os.PatternMatcher;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/** @hide */
+public class ParsedProviderUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ @NonNull
+ public static ParseResult<ParsedProvider> parseProvider(String[] separateProcesses,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
+ boolean useRoundIcon, ParseInput input)
+ throws IOException, XmlPullParserException {
+ String authority;
+ boolean visibleToEphemeral;
+
+ final int targetSdkVersion = pkg.getTargetSdkVersion();
+ final String packageName = pkg.getPackageName();
+ final ParsedProvider provider = new ParsedProvider();
+ final String tag = parser.getName();
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProvider);
+ try {
+ ParseResult<ParsedProvider> result =
+ ParsedMainComponentUtils.parseMainComponent(provider, tag, separateProcesses,
+ pkg, sa, flags, useRoundIcon, input,
+ R.styleable.AndroidManifestProvider_banner,
+ R.styleable.AndroidManifestProvider_description,
+ R.styleable.AndroidManifestProvider_directBootAware,
+ R.styleable.AndroidManifestProvider_enabled,
+ R.styleable.AndroidManifestProvider_icon,
+ R.styleable.AndroidManifestProvider_label,
+ R.styleable.AndroidManifestProvider_logo,
+ R.styleable.AndroidManifestProvider_name,
+ R.styleable.AndroidManifestProvider_process,
+ R.styleable.AndroidManifestProvider_roundIcon,
+ R.styleable.AndroidManifestProvider_splitName,
+ R.styleable.AndroidManifestProvider_attributionTags);
+ if (result.isError()) {
+ return result;
+ }
+
+ authority = sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_authorities, 0);
+
+ // For compatibility, applications targeting API level 16 or lower
+ // should have their content providers exported by default, unless they
+ // specify otherwise.
+ provider.exported = sa.getBoolean(R.styleable.AndroidManifestProvider_exported,
+ targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1);
+
+ provider.syncable = sa.getBoolean(R.styleable.AndroidManifestProvider_syncable, false);
+
+ String permission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestProvider_permission, 0);
+ String readPermission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestProvider_readPermission, 0);
+ if (readPermission == null) {
+ readPermission = permission;
+ }
+ if (readPermission == null) {
+ provider.setReadPermission(pkg.getPermission());
+ } else {
+ provider.setReadPermission(readPermission);
+ }
+ String writePermission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestProvider_writePermission, 0);
+ if (writePermission == null) {
+ writePermission = permission;
+ }
+ if (writePermission == null) {
+ provider.setWritePermission(pkg.getPermission());
+ } else {
+ provider.setWritePermission(writePermission);
+ }
+
+ provider.grantUriPermissions = sa.getBoolean(R.styleable.AndroidManifestProvider_grantUriPermissions, false);
+ provider.forceUriPermissions = sa.getBoolean(R.styleable.AndroidManifestProvider_forceUriPermissions, false);
+ provider.multiProcess = sa.getBoolean(R.styleable.AndroidManifestProvider_multiprocess, false);
+ provider.initOrder = sa.getInt(R.styleable.AndroidManifestProvider_initOrder, 0);
+
+ provider.flags |= flag(ProviderInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestProvider_singleUser, sa);
+
+ visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
+ if (visibleToEphemeral) {
+ provider.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ pkg.setVisibleToInstantApps(true);
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ if (pkg.isCantSaveState()) {
+ // A heavy-weight application can not have providers in its main process
+ if (Objects.equals(provider.getProcessName(), packageName)) {
+ return input.error("Heavy-weight applications can not have providers"
+ + " in main process");
+ }
+ }
+
+ if (authority == null) {
+ return input.error("<provider> does not include authorities attribute");
+ }
+ if (authority.length() <= 0) {
+ return input.error("<provider> has empty authorities attribute");
+ }
+ provider.setAuthority(authority);
+
+ return parseProviderTags(pkg, tag, res, parser, visibleToEphemeral, provider, input);
+ }
+
+ @NonNull
+ private static ParseResult<ParsedProvider> parseProviderTags(ParsingPackage pkg, String tag,
+ Resources res, XmlResourceParser parser, boolean visibleToEphemeral,
+ ParsedProvider provider, ParseInput input)
+ throws XmlPullParserException, IOException {
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ final ParseResult result;
+ switch (name) {
+ case "intent-filter":
+ ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+ .parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral,
+ true /*allowGlobs*/, false /*allowAutoVerify*/,
+ false /*allowImplicitEphemeralVisibility*/,
+ false /*failOnNoActions*/, input);
+ result = intentResult;
+ if (intentResult.isSuccess()) {
+ ParsedIntentInfo intent = intentResult.getResult();
+ provider.order = Math.max(intent.getOrder(), provider.order);
+ provider.addIntent(intent);
+ }
+ break;
+ case "meta-data":
+ result = ParsedComponentUtils.addMetaData(provider, pkg, res, parser, input);
+ break;
+ case "property":
+ result = ParsedComponentUtils.addProperty(provider, pkg, res, parser, input);
+ break;
+ case "grant-uri-permission": {
+ result = parseGrantUriPermission(provider, pkg, res, parser, input);
+ break;
+ }
+ case "path-permission": {
+ result = parsePathPermission(provider, pkg, res, parser, input);
+ break;
+ }
+ default:
+ result = ParsingUtils.unknownTag(tag, pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ return input.success(provider);
+ }
+
+ @NonNull
+ private static ParseResult<ParsedProvider> parseGrantUriPermission(ParsedProvider provider,
+ ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
+ TypedArray sa = resources.obtainAttributes(parser,
+ R.styleable.AndroidManifestGrantUriPermission);
+ try {
+ String name = parser.getName();
+ // Pattern has priority over pre/suffix over literal path
+ PatternMatcher pa = null;
+ String str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestGrantUriPermission_pathAdvancedPattern, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ } else {
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ } else {
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
+ } else {
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestGrantUriPermission_pathSuffix, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_SUFFIX);
+ } else {
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestGrantUriPermission_path, 0);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
+ }
+ }
+ }
+ }
+ }
+
+ if (pa != null) {
+ if (provider.uriPermissionPatterns == null) {
+ provider.uriPermissionPatterns = new PatternMatcher[1];
+ provider.uriPermissionPatterns[0] = pa;
+ } else {
+ final int N = provider.uriPermissionPatterns.length;
+ PatternMatcher[] newp = new PatternMatcher[N + 1];
+ System.arraycopy(provider.uriPermissionPatterns, 0, newp, 0, N);
+ newp[N] = pa;
+ provider.uriPermissionPatterns = newp;
+ }
+ provider.grantUriPermissions = true;
+ } else {
+ if (PackageParser.RIGID_PARSER) {
+ return input.error("No path, pathPrefix, or pathPattern for <path-permission>");
+ }
+
+ Slog.w(TAG, "Unknown element under <path-permission>: " + name + " at "
+ + pkg.getBaseApkPath() + " " + parser.getPositionDescription());
+ }
+
+ return input.success(provider);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult<ParsedProvider> parsePathPermission(ParsedProvider provider,
+ ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
+ TypedArray sa = resources.obtainAttributes(parser,
+ R.styleable.AndroidManifestPathPermission);
+ try {
+ String name = parser.getName();
+
+ String permission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestPathPermission_permission, 0);
+ String readPermission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestPathPermission_readPermission, 0);
+ if (readPermission == null) {
+ readPermission = permission;
+ }
+ String writePermission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestPathPermission_writePermission, 0);
+ if (writePermission == null) {
+ writePermission = permission;
+ }
+
+ boolean havePerm = false;
+ if (readPermission != null) {
+ readPermission = readPermission.intern();
+ havePerm = true;
+ }
+ if (writePermission != null) {
+ writePermission = writePermission.intern();
+ havePerm = true;
+ }
+
+ if (!havePerm) {
+ if (PackageParser.RIGID_PARSER) {
+ return input.error(
+ "No readPermission or writePermission for <path-permission>");
+ }
+ Slog.w(TAG, "No readPermission or writePermission for <path-permission>: "
+ + name + " at " + pkg.getBaseApkPath() + " "
+ + parser.getPositionDescription());
+ return input.success(provider);
+ }
+
+ // Advanced has priority over simply over prefix over literal
+ PathPermission pa = null;
+ String path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathAdvancedPattern, 0);
+ if (path != null) {
+ pa = new PathPermission(path, PatternMatcher.PATTERN_ADVANCED_GLOB, readPermission,
+ writePermission);
+ } else {
+ path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathPattern, 0);
+ if (path != null) {
+ pa = new PathPermission(path, PatternMatcher.PATTERN_SIMPLE_GLOB,
+ readPermission, writePermission);
+ } else {
+ path = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestPathPermission_pathPrefix, 0);
+ if (path != null) {
+ pa = new PathPermission(path, PatternMatcher.PATTERN_PREFIX, readPermission,
+ writePermission);
+ } else {
+ path = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestPathPermission_pathSuffix, 0);
+ if (path != null) {
+ pa = new PathPermission(path, PatternMatcher.PATTERN_SUFFIX,
+ readPermission, writePermission);
+ } else {
+ path = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestPathPermission_path, 0);
+ if (path != null) {
+ pa = new PathPermission(path, PatternMatcher.PATTERN_LITERAL,
+ readPermission, writePermission);
+ }
+ }
+ }
+ }
+ }
+
+ if (pa != null) {
+ if (provider.pathPermissions == null) {
+ provider.pathPermissions = new PathPermission[1];
+ provider.pathPermissions[0] = pa;
+ } else {
+ final int N = provider.pathPermissions.length;
+ PathPermission[] newp = new PathPermission[N + 1];
+ System.arraycopy(provider.pathPermissions, 0, newp, 0, N);
+ newp[N] = pa;
+ provider.pathPermissions = newp;
+ }
+ } else {
+ if (PackageParser.RIGID_PARSER) {
+ return input.error(
+ "No path, pathPrefix, or pathPattern for <path-permission>");
+ }
+
+ Slog.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>: "
+ + name + " at " + pkg.getBaseApkPath()
+ + " "
+ + parser.getPositionDescription());
+ }
+
+ return input.success(provider);
+ } finally {
+ sa.recycle();
+ }
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedService.java b/android/content/pm/parsing/component/ParsedService.java
new file mode 100644
index 0000000..7adb262
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedService.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide **/
+public class ParsedService extends ParsedMainComponent {
+
+ int foregroundServiceType;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String permission;
+
+ public ParsedService(ParsedService other) {
+ super(other);
+ this.foregroundServiceType = other.foregroundServiceType;
+ this.permission = other.permission;
+ }
+
+ public ParsedMainComponent setPermission(String permission) {
+ // Empty string must be converted to null
+ this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
+ return this;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Service{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ ComponentName.appendShortString(sb, getPackageName(), getName());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(this.foregroundServiceType);
+ sForInternedString.parcel(this.permission, dest, flags);
+ }
+
+ public ParsedService() {
+ }
+
+ protected ParsedService(Parcel in) {
+ super(in);
+ this.foregroundServiceType = in.readInt();
+ this.permission = sForInternedString.unparcel(in);
+ }
+
+ public static final Parcelable.Creator<ParsedService> CREATOR = new Creator<ParsedService>() {
+ @Override
+ public ParsedService createFromParcel(Parcel source) {
+ return new ParsedService(source);
+ }
+
+ @Override
+ public ParsedService[] newArray(int size) {
+ return new ParsedService[size];
+ }
+ };
+
+ public int getForegroundServiceType() {
+ return foregroundServiceType;
+ }
+
+ @Nullable
+ public String getPermission() {
+ return permission;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedServiceUtils.java b/android/content/pm/parsing/component/ParsedServiceUtils.java
new file mode 100644
index 0000000..ae107ce
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedServiceUtils.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 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.parsing.component;
+
+import static android.content.pm.parsing.component.ComponentParseUtils.flag;
+
+import android.annotation.NonNull;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseInput.DeferredError;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/** @hide */
+public class ParsedServiceUtils {
+
+ @NonNull
+ public static ParseResult<ParsedService> parseService(String[] separateProcesses,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
+ boolean useRoundIcon, ParseInput input)
+ throws XmlPullParserException, IOException {
+ boolean visibleToEphemeral;
+ boolean setExported;
+
+ final String packageName = pkg.getPackageName();
+ final ParsedService service = new ParsedService();
+ String tag = parser.getName();
+
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestService);
+ try {
+ ParseResult<ParsedService> result = ParsedMainComponentUtils.parseMainComponent(
+ service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, input,
+ R.styleable.AndroidManifestService_banner,
+ R.styleable.AndroidManifestService_description,
+ R.styleable.AndroidManifestService_directBootAware,
+ R.styleable.AndroidManifestService_enabled,
+ R.styleable.AndroidManifestService_icon,
+ R.styleable.AndroidManifestService_label,
+ R.styleable.AndroidManifestService_logo,
+ R.styleable.AndroidManifestService_name,
+ R.styleable.AndroidManifestService_process,
+ R.styleable.AndroidManifestService_roundIcon,
+ R.styleable.AndroidManifestService_splitName,
+ R.styleable.AndroidManifestService_attributionTags
+ );
+
+ if (result.isError()) {
+ return result;
+ }
+
+ setExported = sa.hasValue(R.styleable.AndroidManifestService_exported);
+ if (setExported) {
+ service.exported = sa.getBoolean(R.styleable.AndroidManifestService_exported,
+ false);
+ }
+
+ String permission = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestService_permission, 0);
+ service.setPermission(permission != null ? permission : pkg.getPermission());
+
+ service.foregroundServiceType = sa.getInt(
+ R.styleable.AndroidManifestService_foregroundServiceType,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+
+ service.flags |= flag(ServiceInfo.FLAG_STOP_WITH_TASK,
+ R.styleable.AndroidManifestService_stopWithTask, sa)
+ | flag(ServiceInfo.FLAG_ISOLATED_PROCESS,
+ R.styleable.AndroidManifestService_isolatedProcess, sa)
+ | flag(ServiceInfo.FLAG_EXTERNAL_SERVICE,
+ R.styleable.AndroidManifestService_externalService, sa)
+ | flag(ServiceInfo.FLAG_USE_APP_ZYGOTE,
+ R.styleable.AndroidManifestService_useAppZygote, sa)
+ | flag(ServiceInfo.FLAG_SINGLE_USER,
+ R.styleable.AndroidManifestService_singleUser, sa);
+
+ visibleToEphemeral = sa.getBoolean(
+ R.styleable.AndroidManifestService_visibleToInstantApps, false);
+ if (visibleToEphemeral) {
+ service.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ pkg.setVisibleToInstantApps(true);
+ }
+ } finally {
+ sa.recycle();
+ }
+
+ if (pkg.isCantSaveState()) {
+ // A heavy-weight application can not have services in its main process
+ // We can do direct compare because we intern all strings.
+ if (Objects.equals(service.getProcessName(), packageName)) {
+ return input.error("Heavy-weight applications can not have services "
+ + "in main process");
+ }
+ }
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult parseResult;
+ switch (parser.getName()) {
+ case "intent-filter":
+ ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+ .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
+ true /*allowGlobs*/, false /*allowAutoVerify*/,
+ false /*allowImplicitEphemeralVisibility*/,
+ false /*failOnNoActions*/, input);
+ parseResult = intentResult;
+ if (intentResult.isSuccess()) {
+ ParsedIntentInfo intent = intentResult.getResult();
+ service.order = Math.max(intent.getOrder(), service.order);
+ service.addIntent(intent);
+ }
+ break;
+ case "meta-data":
+ parseResult = ParsedComponentUtils.addMetaData(service, pkg, res, parser, input);
+ break;
+ case "property":
+ parseResult =
+ ParsedComponentUtils.addProperty(service, pkg, res, parser, input);
+ break;
+ default:
+ parseResult = ParsingUtils.unknownTag(tag, pkg, parser, input);
+ break;
+ }
+
+ if (parseResult.isError()) {
+ return input.error(parseResult);
+ }
+ }
+
+ if (!setExported) {
+ boolean hasIntentFilters = service.getIntents().size() > 0;
+ if (hasIntentFilters) {
+ final ParseResult exportedCheckResult = input.deferError(
+ service.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S
+ + " and above) requires that an explicit value for android:exported be"
+ + " defined when intent filters are present",
+ DeferredError.MISSING_EXPORTED_FLAG);
+ if (exportedCheckResult.isError()) {
+ return input.error(exportedCheckResult);
+ }
+ }
+ service.exported = hasIntentFilters;
+ }
+
+ return input.success(service);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedUsesPermission.java b/android/content/pm/parsing/component/ParsedUsesPermission.java
new file mode 100644
index 0000000..adf8da0
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedUsesPermission.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A {@link android.R.styleable#AndroidManifestUsesPermission
+ * <uses-permission>} tag parsed from the manifest.
+ *
+ * @hide
+ */
+public class ParsedUsesPermission implements Parcelable {
+ /** Name of the permission requested */
+ public @NonNull String name;
+
+ /** Set of flags that should apply to this permission request. */
+ public @UsesPermissionFlags int usesPermissionFlags;
+
+ /**
+ * Strong assertion by a developer that they will never use this permission
+ * to derive the physical location of the device, regardless of
+ * ACCESS_FINE_LOCATION and/or ACCESS_COARSE_LOCATION being granted.
+ */
+ public static final int FLAG_NEVER_FOR_LOCATION =
+ PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_NEVER_FOR_LOCATION
+ })
+ public @interface UsesPermissionFlags {}
+
+ public ParsedUsesPermission(@NonNull String name,
+ @UsesPermissionFlags int usesPermissionFlags) {
+ this.name = name.intern();
+ this.usesPermissionFlags = usesPermissionFlags;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ sForInternedString.parcel(this.name, dest, flags);
+ dest.writeInt(usesPermissionFlags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ protected ParsedUsesPermission(@NonNull Parcel in) {
+ this.name = sForInternedString.unparcel(in);
+ this.usesPermissionFlags = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<ParsedUsesPermission> CREATOR
+ = new Parcelable.Creator<ParsedUsesPermission>() {
+ @Override
+ public ParsedUsesPermission[] newArray(int size) {
+ return new ParsedUsesPermission[size];
+ }
+
+ @Override
+ public ParsedUsesPermission createFromParcel(@NonNull Parcel in) {
+ return new ParsedUsesPermission(in);
+ }
+ };
+}
diff --git a/android/content/pm/parsing/result/ParseInput.java b/android/content/pm/parsing/result/ParseInput.java
new file mode 100644
index 0000000..1ca28cb
--- /dev/null
+++ b/android/content/pm/parsing/result/ParseInput.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 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.parsing.result;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+/**
+ * Used as a method parameter which is then transformed into a {@link ParseResult}. This is
+ * generalized as it doesn't matter what type this input is for. It's simply to hide the
+ * methods of {@link ParseResult}.
+ *
+ * @hide
+ */
+public interface ParseInput {
+
+ /**
+ * Errors encountered during parsing may rely on the targetSDK version of the application to
+ * determine whether or not to fail. These are passed into {@link #deferError(String, long)}
+ * when encountered, and the implementation will handle how to defer the errors until the
+ * targetSdkVersion is known and sent to {@link #enableDeferredError(String, int)}.
+ *
+ * All of these must be marked {@link ChangeId}, as that is the mechanism used to check if the
+ * error must be propagated. This framework also allows developers to pre-disable specific
+ * checks if they wish to target a newer SDK version in a development environment without
+ * having to migrate their entire app to validate on a newer platform.
+ */
+ final class DeferredError {
+ /**
+ * Missing an "application" or "instrumentation" tag.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ public static final long MISSING_APP_TAG = 150776642;
+
+ /**
+ * An intent filter's actor or category is an empty string. A bug in the platform before R
+ * allowed this to pass through without an error. This does not include cases when the
+ * attribute is null/missing, as that has always been a failure.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ public static final long EMPTY_INTENT_ACTION_CATEGORY = 151163173;
+
+ /**
+ * The {@code resources.arsc} of one of the APKs being installed is compressed or not
+ * aligned on a 4-byte boundary. Resource tables that cannot be memory mapped exert excess
+ * memory pressure on the system and drastically slow down construction of
+ * {@link android.content.res.Resources} objects.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ public static final long RESOURCES_ARSC_COMPRESSED = 132742131;
+
+ /**
+ * Missing `android:exported` flag. When an intent filter is defined, an explicit value
+ * for the android:exported flag is required.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long MISSING_EXPORTED_FLAG = 150232615;
+
+ /**
+ * TODO(chiuwinson): This is required because PackageManager#getPackageArchiveInfo
+ * cannot read the targetSdk info from the changeId because it requires the
+ * READ_COMPAT_CHANGE_CONFIG which cannot be obtained automatically without entering the
+ * server process. This should be removed once an alternative is found, or if the API
+ * is removed.
+ * @return the targetSdk that this change is gated on (> check), or -1 if disabled
+ */
+ @IntRange(from = -1, to = Integer.MAX_VALUE)
+ public static int getTargetSdkForChange(long changeId) {
+ if (changeId == MISSING_APP_TAG
+ || changeId == EMPTY_INTENT_ACTION_CATEGORY
+ || changeId == RESOURCES_ARSC_COMPRESSED) {
+ return Build.VERSION_CODES.Q;
+ }
+
+ if (changeId == MISSING_EXPORTED_FLAG) {
+ return Build.VERSION_CODES.R;
+ }
+
+ return -1;
+ }
+ }
+
+ <ResultType> ParseResult<ResultType> success(ResultType result);
+
+ /**
+ * Used for errors gated by {@link DeferredError}. Will return an error result if the
+ * targetSdkVersion is already known and this must be returned as a real error. The result
+ * contains null and should not be unwrapped.
+ *
+ * @see #error(String)
+ */
+ ParseResult<?> deferError(@NonNull String parseError, long deferredError);
+
+ /**
+ * Called after targetSdkVersion is known. Returns an error result if a previously deferred
+ * error was registered. The result contains null and should not be unwrapped.
+ */
+ ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion);
+
+ /**
+ * This will assign errorCode to {@link PackageManager#INSTALL_PARSE_FAILED_SKIPPED, used for
+ * packages which should be ignored by the caller.
+ *
+ * @see #error(int, String, Exception)
+ */
+ <ResultType> ParseResult<ResultType> skip(@NonNull String parseError);
+
+ /** @see #error(int, String, Exception) */
+ <ResultType> ParseResult<ResultType> error(int parseError);
+
+ /**
+ * This will assign errorCode to {@link PackageManager#INSTALL_PARSE_FAILED_MANIFEST_MALFORMED}.
+ * @see #error(int, String, Exception)
+ */
+ <ResultType> ParseResult<ResultType> error(@NonNull String parseError);
+
+ /** @see #error(int, String, Exception) */
+ <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage);
+
+ /**
+ * Marks this as an error result. When this method is called, the return value <b>must</b>
+ * be returned to the exit of the parent method that took in this {@link ParseInput} as a
+ * parameter.
+ *
+ * The calling site of that method is then expected to check the result for error, and
+ * continue to bubble up if it is an error.
+ *
+ * If the result {@link ParseResult#isSuccess()}, then it can be used as-is, as
+ * overlapping/consecutive successes are allowed.
+ */
+ <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage,
+ @Nullable Exception exception);
+
+ /**
+ * Moves the error in {@param result} to this input's type. In practice this does nothing
+ * but cast the type of the {@link ParseResult} for type safety, since the parameter
+ * and the receiver should be the same object.
+ */
+ <ResultType> ParseResult<ResultType> error(ParseResult<?> result);
+
+ /**
+ * Implemented instead of a direct reference to
+ * {@link com.android.internal.compat.IPlatformCompat}, allowing caching and testing logic to
+ * be separated out.
+ */
+ interface Callback {
+ /**
+ * @return true if the changeId should be enabled
+ */
+ boolean isChangeEnabled(long changeId, @NonNull String packageName, int targetSdkVersion);
+ }
+}
diff --git a/android/content/pm/parsing/result/ParseResult.java b/android/content/pm/parsing/result/ParseResult.java
new file mode 100644
index 0000000..518395d
--- /dev/null
+++ b/android/content/pm/parsing/result/ParseResult.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.parsing.result;
+
+import android.annotation.Nullable;
+
+/**
+ * The output side of {@link ParseInput}, which must result from a method call on
+ * {@link ParseInput}.
+ *
+ * When using this class, keep in mind that all {@link ParseInput}s and {@link ParseResult}s
+ * are the exact same object, scoped per thread, thrown around and casted for type safety.
+ *
+ * @hide
+ */
+public interface ParseResult<ResultType> {
+
+ /**
+ * Returns true if the result is not an error and thus contains a valid object.
+ *
+ * For backwards-compat reasons, it's possible to have a successful result with a null
+ * result object, depending on the behavior of the parsing method.
+ *
+ * It is expected that every method calls this to check for an error state to bubble up
+ * the error to its parent method after every parse method call.
+ *
+ * It is not always necessary to check this, as it is valid to return any ParseResult from
+ * a method so long as the type matches <b>without casting it</b>.
+ *
+ * The infrastructure is set up such that as long as a result is the proper type and
+ * the right side of success vs. error, it can be bubble up through all its parent methods.
+ */
+ boolean isSuccess();
+
+ /**
+ * Opposite of {@link #isSuccess()} for readability.
+ */
+ boolean isError();
+
+ ResultType getResult();
+
+ int getErrorCode();
+
+ @Nullable
+ String getErrorMessage();
+
+ @Nullable
+ Exception getException();
+}
diff --git a/android/content/pm/parsing/result/ParseTypeImpl.java b/android/content/pm/parsing/result/ParseTypeImpl.java
new file mode 100644
index 0000000..7ac78b7
--- /dev/null
+++ b/android/content/pm/parsing/result/ParseTypeImpl.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2020 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.parsing.result;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.ParsingUtils;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.util.CollectionUtils;
+
+/** @hide */
+public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ public static final boolean DEBUG_FILL_STACK_TRACE = false;
+
+ public static final boolean DEBUG_LOG_ON_ERROR = false;
+
+ public static final boolean DEBUG_THROW_ALL_ERRORS = false;
+
+ @NonNull
+ private Callback mCallback;
+
+ private Object mResult;
+
+ private int mErrorCode = PackageManager.INSTALL_SUCCEEDED;
+
+ @Nullable
+ private String mErrorMessage;
+
+ @Nullable
+ private Exception mException;
+
+ /**
+ * Errors encountered before targetSdkVersion is known.
+ * The size upper bound is the number of longs in {@link DeferredError}
+ */
+ @Nullable
+ private ArrayMap<Long, String> mDeferredErrors = null;
+
+ private String mPackageName;
+ private Integer mTargetSdkVersion;
+
+ /**
+ * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where
+ * {@link IPlatformCompat} cannot be used because the cross-package READ_COMPAT_CHANGE_CONFIG
+ * permission cannot be obtained.
+ */
+ public static ParseTypeImpl forParsingWithoutPlatformCompat() {
+ return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> {
+ int gateSdkVersion = DeferredError.getTargetSdkForChange(changeId);
+ if (gateSdkVersion == -1) {
+ return false;
+ }
+ return targetSdkVersion > gateSdkVersion;
+ });
+ }
+
+ /**
+ * Assumes {@link Context#PLATFORM_COMPAT_SERVICE} is available to the caller. For use
+ * with {@link android.content.pm.parsing.ApkLiteParseUtils} or similar where parsing is
+ * done outside of {@link com.android.server.pm.PackageManagerService}.
+ */
+ public static ParseTypeImpl forDefaultParsing() {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = packageName;
+ appInfo.targetSdkVersion = targetSdkVersion;
+ try {
+ return platformCompat.isChangeEnabled(changeId, appInfo);
+ } catch (Exception e) {
+ // This shouldn't happen, but assume enforcement if it does
+ Slog.wtf(TAG, "IPlatformCompat query failed", e);
+ return true;
+ }
+ });
+ }
+
+ /**
+ * @param callback if nullable, fallback to manual targetSdk > Q check
+ */
+ public ParseTypeImpl(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ public ParseInput reset() {
+ mResult = null;
+ mErrorCode = PackageManager.INSTALL_SUCCEEDED;
+ mErrorMessage = null;
+ mException = null;
+ if (mDeferredErrors != null) {
+ // If the memory was already allocated, don't bother freeing and re-allocating,
+ // as this could occur hundreds of times depending on what the caller is doing and
+ // how many APKs they're going through.
+ mDeferredErrors.erase();
+ }
+ mPackageName = null;
+ mTargetSdkVersion = null;
+ return this;
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> success(ResultType result) {
+ if (mErrorCode != PackageManager.INSTALL_SUCCEEDED) {
+ Slog.wtf(TAG, "Cannot set to success after set to error, was "
+ + mErrorMessage, mException);
+ }
+ mResult = result;
+ //noinspection unchecked
+ return (ParseResult<ResultType>) this;
+ }
+
+ @Override
+ public ParseResult<?> deferError(@NonNull String parseError, long deferredError) {
+ if (DEBUG_THROW_ALL_ERRORS) {
+ return error(parseError);
+ }
+ if (mTargetSdkVersion != null) {
+ if (mDeferredErrors != null && mDeferredErrors.containsKey(deferredError)) {
+ // If the map already contains the key, that means it's already been checked and
+ // found to be disabled. Otherwise it would've failed when mTargetSdkVersion was
+ // set to non-null.
+ return success(null);
+ }
+
+ if (mCallback.isChangeEnabled(deferredError, mPackageName, mTargetSdkVersion)) {
+ return error(parseError);
+ } else {
+ if (mDeferredErrors == null) {
+ mDeferredErrors = new ArrayMap<>();
+ }
+ mDeferredErrors.put(deferredError, null);
+ return success(null);
+ }
+ }
+
+ if (mDeferredErrors == null) {
+ mDeferredErrors = new ArrayMap<>();
+ }
+
+ // Only save the first occurrence of any particular error
+ mDeferredErrors.putIfAbsent(deferredError, parseError);
+ return success(null);
+ }
+
+ @Override
+ public ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion) {
+ mPackageName = packageName;
+ mTargetSdkVersion = targetSdkVersion;
+
+ int size = CollectionUtils.size(mDeferredErrors);
+ for (int index = size - 1; index >= 0; index--) {
+ long changeId = mDeferredErrors.keyAt(index);
+ String errorMessage = mDeferredErrors.valueAt(index);
+ if (mCallback.isChangeEnabled(changeId, mPackageName, mTargetSdkVersion)) {
+ return error(errorMessage);
+ } else {
+ // No point holding onto the string, but need to maintain the key to signal
+ // that the error was checked with isChangeEnabled and found to be disabled.
+ mDeferredErrors.setValueAt(index, null);
+ }
+ }
+
+ return success(null);
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> skip(@NonNull String parseError) {
+ return error(PackageManager.INSTALL_PARSE_FAILED_SKIPPED, parseError);
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> error(int parseError) {
+ return error(parseError, null);
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> error(@NonNull String parseError) {
+ return error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, parseError);
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> error(int errorCode,
+ @Nullable String errorMessage) {
+ return error(errorCode, errorMessage, null);
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> error(ParseResult<?> intentResult) {
+ return error(intentResult.getErrorCode(), intentResult.getErrorMessage(),
+ intentResult.getException());
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> error(int errorCode, @Nullable String errorMessage,
+ Exception exception) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mException = exception;
+
+ if (DEBUG_FILL_STACK_TRACE) {
+ if (exception == null) {
+ mException = new Exception();
+ }
+ }
+
+ if (DEBUG_LOG_ON_ERROR) {
+ Exception exceptionToLog = mException != null ? mException : new Exception();
+ Log.w(TAG, "ParseInput set to error " + errorCode + ", " + errorMessage,
+ exceptionToLog);
+ }
+
+ //noinspection unchecked
+ return (ParseResult<ResultType>) this;
+ }
+
+ @Override
+ public Object getResult() {
+ return mResult;
+ }
+
+ @Override
+ public boolean isSuccess() {
+ return mErrorCode == PackageManager.INSTALL_SUCCEEDED;
+ }
+
+ @Override
+ public boolean isError() {
+ return !isSuccess();
+ }
+
+ @Override
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ @Nullable
+ @Override
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ @Nullable
+ @Override
+ public Exception getException() {
+ return mException;
+ }
+}
diff --git a/android/content/pm/permission/RuntimePermissionPresentationInfo.java b/android/content/pm/permission/RuntimePermissionPresentationInfo.java
new file mode 100644
index 0000000..97312c4
--- /dev/null
+++ b/android/content/pm/permission/RuntimePermissionPresentationInfo.java
@@ -0,0 +1,114 @@
+/*
+ * 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.permission;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains information about how a runtime permission
+ * is to be presented in the UI. A single runtime permission
+ * presented to the user may correspond to multiple platform defined
+ * permissions, e.g. the location permission may control both the
+ * coarse and fine platform permissions.
+ *
+ * @hide
+ *
+ * @deprecated Not used anymore. Use {@link android.permission.RuntimePermissionPresentationInfo}
+ * instead
+ */
+@Deprecated
+@SystemApi
+public final class RuntimePermissionPresentationInfo implements Parcelable {
+ private static final int FLAG_GRANTED = 1 << 0;
+ private static final int FLAG_STANDARD = 1 << 1;
+
+ private final CharSequence mLabel;
+ private final int mFlags;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param label The permission label.
+ * @param granted Whether the permission is granted.
+ * @param standard Whether this is a platform-defined permission.
+ */
+ public RuntimePermissionPresentationInfo(CharSequence label,
+ boolean granted, boolean standard) {
+ mLabel = label;
+ int flags = 0;
+ if (granted) {
+ flags |= FLAG_GRANTED;
+ }
+ if (standard) {
+ flags |= FLAG_STANDARD;
+ }
+ mFlags = flags;
+ }
+
+ private RuntimePermissionPresentationInfo(Parcel parcel) {
+ mLabel = parcel.readCharSequence();
+ mFlags = parcel.readInt();
+ }
+
+ /**
+ * @return Whether the permission is granted.
+ */
+ public boolean isGranted() {
+ return (mFlags & FLAG_GRANTED) != 0;
+ }
+
+ /**
+ * @return Whether the permission is platform-defined.
+ */
+ public boolean isStandard() {
+ return (mFlags & FLAG_STANDARD) != 0;
+ }
+
+ /**
+ * Gets the permission label.
+ *
+ * @return The label.
+ */
+ public @NonNull CharSequence getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeCharSequence(mLabel);
+ parcel.writeInt(mFlags);
+ }
+
+ public static final @android.annotation.NonNull Creator<RuntimePermissionPresentationInfo> CREATOR =
+ new Creator<RuntimePermissionPresentationInfo>() {
+ public RuntimePermissionPresentationInfo createFromParcel(Parcel source) {
+ return new RuntimePermissionPresentationInfo(source);
+ }
+
+ public RuntimePermissionPresentationInfo[] newArray(int size) {
+ return new RuntimePermissionPresentationInfo[size];
+ }
+ };
+}
diff --git a/android/content/pm/permission/SplitPermissionInfoParcelable.java b/android/content/pm/permission/SplitPermissionInfoParcelable.java
new file mode 100644
index 0000000..aea69ad
--- /dev/null
+++ b/android/content/pm/permission/SplitPermissionInfoParcelable.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 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.permission;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Parcelable version of {@link android.permission.PermissionManager.SplitPermissionInfo}
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true)
+public class SplitPermissionInfoParcelable implements Parcelable {
+
+ /**
+ * The permission that is split.
+ */
+ @NonNull
+ private final String mSplitPermission;
+
+ /**
+ * The permissions that are added.
+ */
+ @NonNull
+ private final List<String> mNewPermissions;
+
+ /**
+ * The target API level when the permission was split.
+ */
+ @IntRange(from = 0)
+ private final int mTargetSdk;
+
+ private void onConstructed() {
+ Preconditions.checkCollectionElementsNotNull(mNewPermissions, "newPermissions");
+ }
+
+
+
+ // Code below generated by codegen v1.0.20.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new SplitPermissionInfoParcelable.
+ *
+ * @param splitPermission
+ * The permission that is split.
+ * @param newPermissions
+ * The permissions that are added.
+ * @param targetSdk
+ * The target API level when the permission was split.
+ */
+ @DataClass.Generated.Member
+ public SplitPermissionInfoParcelable(
+ @NonNull String splitPermission,
+ @NonNull List<String> newPermissions,
+ @IntRange(from = 0) int targetSdk) {
+ this.mSplitPermission = splitPermission;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSplitPermission);
+ this.mNewPermissions = newPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mNewPermissions);
+ this.mTargetSdk = targetSdk;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTargetSdk,
+ "from", 0);
+
+ onConstructed();
+ }
+
+ /**
+ * The permission that is split.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getSplitPermission() {
+ return mSplitPermission;
+ }
+
+ /**
+ * The permissions that are added.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getNewPermissions() {
+ return mNewPermissions;
+ }
+
+ /**
+ * The target API level when the permission was split.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getTargetSdk() {
+ return mTargetSdk;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(SplitPermissionInfoParcelable other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ SplitPermissionInfoParcelable that = (SplitPermissionInfoParcelable) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mSplitPermission, that.mSplitPermission)
+ && Objects.equals(mNewPermissions, that.mNewPermissions)
+ && mTargetSdk == that.mTargetSdk;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mSplitPermission);
+ _hash = 31 * _hash + Objects.hashCode(mNewPermissions);
+ _hash = 31 * _hash + mTargetSdk;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeString(mSplitPermission);
+ dest.writeStringList(mNewPermissions);
+ dest.writeInt(mTargetSdk);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected SplitPermissionInfoParcelable(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ String splitPermission = in.readString();
+ List<String> newPermissions = new java.util.ArrayList<>();
+ in.readStringList(newPermissions);
+ int targetSdk = in.readInt();
+
+ this.mSplitPermission = splitPermission;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSplitPermission);
+ this.mNewPermissions = newPermissions;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mNewPermissions);
+ this.mTargetSdk = targetSdk;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTargetSdk,
+ "from", 0);
+
+ onConstructed();
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<SplitPermissionInfoParcelable> CREATOR
+ = new Parcelable.Creator<SplitPermissionInfoParcelable>() {
+ @Override
+ public SplitPermissionInfoParcelable[] newArray(int size) {
+ return new SplitPermissionInfoParcelable[size];
+ }
+
+ @Override
+ public SplitPermissionInfoParcelable createFromParcel(@NonNull Parcel in) {
+ return new SplitPermissionInfoParcelable(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1604456266666L,
+ codegenVersion = "1.0.20",
+ sourceFile = "frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mSplitPermission\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mNewPermissions\nprivate final @android.annotation.IntRange int mTargetSdk\nprivate void onConstructed()\nclass SplitPermissionInfoParcelable extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/split/DefaultSplitAssetLoader.java b/android/content/pm/split/DefaultSplitAssetLoader.java
new file mode 100644
index 0000000..c1a8396
--- /dev/null
+++ b/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -0,0 +1,113 @@
+/*
+ * 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.split;
+
+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.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.os.Build;
+
+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
+ */
+public class DefaultSplitAssetLoader implements SplitAssetLoader {
+ private final String mBaseApkPath;
+ private final String[] mSplitApkPaths;
+ private final @ParseFlags int mFlags;
+ private AssetManager mCachedAssetManager;
+
+ private ApkAssets mBaseApkAssets;
+
+ public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
+ mBaseApkPath = pkg.getBaseApkPath();
+ mSplitApkPaths = pkg.getSplitApkPaths();
+ mFlags = flags;
+ }
+
+ private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+ throws PackageParserException {
+ if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0
+ && !ApkLiteParseUtils.isApkPath(path)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + path);
+ }
+
+ 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 PackageParserException {
+ if (mCachedAssetManager != null) {
+ return mCachedAssetManager;
+ }
+
+ ApkAssets[] apkAssets = new ApkAssets[(mSplitApkPaths != null
+ ? mSplitApkPaths.length : 0) + 1];
+
+ // Load the base.
+ int splitIdx = 0;
+ apkAssets[splitIdx++] = mBaseApkAssets = loadApkAssets(mBaseApkPath, mFlags);
+
+ // Load any splits.
+ if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
+ for (String apkPath : mSplitApkPaths) {
+ 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 PackageParserException {
+ return getBaseAssetManager();
+ }
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mBaseApkAssets;
+ }
+
+ @Override
+ public void close() throws Exception {
+ IoUtils.closeQuietly(mCachedAssetManager);
+ }
+}
diff --git a/android/content/pm/split/SplitAssetDependencyLoader.java b/android/content/pm/split/SplitAssetDependencyLoader.java
new file mode 100644
index 0000000..e5c2158
--- /dev/null
+++ b/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -0,0 +1,141 @@
+/*
+ * 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.split;
+
+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.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.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;
+
+/**
+ * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+ * is to be used when an application opts-in to isolated split loading.
+ * @hide
+ */
+public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException>
+ implements SplitAssetLoader {
+ private final String[] mSplitPaths;
+ private final @ParseFlags int mFlags;
+ private final ApkAssets[][] mCachedSplitApks;
+ private final AssetManager[] mCachedAssetManagers;
+
+ public SplitAssetDependencyLoader(PackageLite pkg,
+ 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.
+ mSplitPaths = new String[pkg.getSplitApkPaths().length + 1];
+ mSplitPaths[0] = pkg.getBaseApkPath();
+ System.arraycopy(pkg.getSplitApkPaths(), 0, mSplitPaths, 1, pkg.getSplitApkPaths().length);
+
+ mFlags = flags;
+ mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
+ mCachedAssetManagers = new AssetManager[mSplitPaths.length];
+ }
+
+ @Override
+ protected boolean isSplitCached(int splitIdx) {
+ return mCachedAssetManagers[splitIdx] != null;
+ }
+
+ private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+ throws PackageParserException {
+ if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0
+ && !ApkLiteParseUtils.isApkPath(path)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + path);
+ }
+
+ try {
+ 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 PackageParserException {
+ final ArrayList<ApkAssets> assets = new ArrayList<>();
+
+ // Include parent ApkAssets.
+ if (parentSplitIdx >= 0) {
+ Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
+ }
+
+ // Include this ApkAssets.
+ assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
+
+ // Load and include all config splits for this feature.
+ for (int configSplitIdx : configSplitIndices) {
+ assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
+ }
+
+ // Cache the results.
+ mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
+ mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]);
+ }
+
+ @Override
+ public AssetManager getBaseAssetManager() throws PackageParserException {
+ loadDependenciesForSplit(0);
+ return mCachedAssetManagers[0];
+ }
+
+ @Override
+ 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);
+ return mCachedAssetManagers[idx + 1];
+ }
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mCachedSplitApks[0][0];
+ }
+
+ @Override
+ public void close() throws Exception {
+ for (AssetManager assets : mCachedAssetManagers) {
+ IoUtils.closeQuietly(assets);
+ }
+ }
+}
diff --git a/android/content/pm/split/SplitAssetLoader.java b/android/content/pm/split/SplitAssetLoader.java
new file mode 100644
index 0000000..7584e15
--- /dev/null
+++ b/android/content/pm/split/SplitAssetLoader.java
@@ -0,0 +1,33 @@
+/*
+ * 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.split;
+
+import android.content.pm.PackageParser;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+
+/**
+ * Simple interface for loading base Assets and Splits. Used by PackageParser when parsing
+ * split APKs.
+ *
+ * @hide
+ */
+public interface SplitAssetLoader extends AutoCloseable {
+ AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
+ AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+
+ ApkAssets getBaseApkAssets();
+}
diff --git a/android/content/pm/split/SplitDependencyLoader.java b/android/content/pm/split/SplitDependencyLoader.java
new file mode 100644
index 0000000..3e68132
--- /dev/null
+++ b/android/content/pm/split/SplitDependencyLoader.java
@@ -0,0 +1,251 @@
+/*
+ * 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.split;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.content.pm.parsing.PackageLite;
+import android.util.IntArray;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * A helper class that implements the dependency tree traversal for splits. Callbacks
+ * are implemented by subclasses to notify whether a split has already been constructed
+ * and is cached, and to actually create the split requested.
+ *
+ * This helper is meant to be subclassed so as to reduce the number of allocations
+ * needed to make use of it.
+ *
+ * All inputs and outputs are assumed to be indices into an array of splits.
+ *
+ * @hide
+ */
+public abstract class SplitDependencyLoader<E extends Exception> {
+ private final @NonNull SparseArray<int[]> mDependencies;
+
+ /**
+ * Construct a new SplitDependencyLoader. Meant to be called from the
+ * subclass constructor.
+ * @param dependencies The dependency tree of splits.
+ */
+ protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) {
+ mDependencies = dependencies;
+ }
+
+ /**
+ * Traverses the dependency tree and constructs any splits that are not already
+ * cached. This routine short-circuits and skips the creation of splits closer to the
+ * root if they are cached, as reported by the subclass implementation of
+ * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+ * implementation of {@link #constructSplit(int, int[], int)}.
+ * @param splitIdx The index of the split to load. 0 represents the base Application.
+ */
+ protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+ // Quick check before any allocations are done.
+ if (isSplitCached(splitIdx)) {
+ return;
+ }
+
+ // Special case the base, since it has no dependencies.
+ if (splitIdx == 0) {
+ final int[] configSplitIndices = collectConfigSplitIndices(0);
+ constructSplit(0, configSplitIndices, -1);
+ return;
+ }
+
+ // Build up the dependency hierarchy.
+ final IntArray linearDependencies = new IntArray();
+ linearDependencies.add(splitIdx);
+
+ // Collect all the dependencies that need to be constructed.
+ // They will be listed from leaf to root.
+ while (true) {
+ // Only follow the first index into the array. The others are config splits and
+ // get loaded with the split.
+ final int[] deps = mDependencies.get(splitIdx);
+ if (deps != null && deps.length > 0) {
+ splitIdx = deps[0];
+ } else {
+ splitIdx = -1;
+ }
+
+ if (splitIdx < 0 || isSplitCached(splitIdx)) {
+ break;
+ }
+
+ linearDependencies.add(splitIdx);
+ }
+
+ // Visit each index, from right to left (root to leaf).
+ int parentIdx = splitIdx;
+ for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+ final int idx = linearDependencies.get(i);
+ final int[] configSplitIndices = collectConfigSplitIndices(idx);
+ constructSplit(idx, configSplitIndices, parentIdx);
+ parentIdx = idx;
+ }
+ }
+
+ private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+ // The config splits appear after the first element.
+ final int[] deps = mDependencies.get(splitIdx);
+ if (deps == null || deps.length <= 1) {
+ return EmptyArray.INT;
+ }
+ return Arrays.copyOfRange(deps, 1, deps.length);
+ }
+
+ /**
+ * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+ * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+ * @param splitIdx The index of the split to check for in the cache.
+ * @return true if the split is cached and does not need to be constructed.
+ */
+ protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx);
+
+ /**
+ * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+ * The result is expected to be cached by the subclass in its own structures.
+ * @param splitIdx The index of the split to construct. 0 represents the base Application.
+ * @param configSplitIndices The array of configuration splits to load along with this split.
+ * May be empty (length == 0) but never null.
+ * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+ * @throws E Subclass defined exception representing failure to construct a split.
+ */
+ protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+ @NonNull @IntRange(from = 1) int[] configSplitIndices,
+ @IntRange(from = -1) int parentSplitIdx) throws E;
+
+ public static class IllegalDependencyException extends Exception {
+ private IllegalDependencyException(String message) {
+ super(message);
+ }
+ }
+
+ private static int[] append(int[] src, int elem) {
+ if (src == null) {
+ return new int[] { elem };
+ }
+ int[] dst = Arrays.copyOf(src, src.length + 1);
+ dst[src.length] = elem;
+ return dst;
+ }
+
+ /**
+ * Build the split dependency tree by the given package
+ *
+ * @param pkg The package to retrieve the dependency tree
+ * @return The dependency tree of splits
+ * @throws IllegalDependencyException if the requires split is missing, targets split is
+ * missing, it declares itself as configuration split for a non-feature split, or
+ * cycle detected in split dependencies.
+ */
+ public static @NonNull SparseArray<int[]> createDependenciesFromPackage(PackageLite pkg)
+ throws IllegalDependencyException {
+ // The data structure that holds the dependencies. In ParsingPackageUtils, splits are
+ // stored in their own array, separate from the base. We treat all paths as equals, so
+ // we need to insert the base as index 0, and shift all other splits.
+ final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+ // The base depends on nothing.
+ splitDependencies.put(0, new int[] {-1});
+
+ // First write out the <uses-split> dependencies. These must appear first in the
+ // array of ints, as is convention in this class.
+ for (int splitIdx = 0; splitIdx < pkg.getSplitNames().length; splitIdx++) {
+ if (!pkg.getIsFeatureSplits()[splitIdx]) {
+ // Non-feature splits don't have dependencies.
+ continue;
+ }
+
+ // Implicit dependency on the base.
+ final int targetIdx;
+ final String splitDependency = pkg.getUsesSplitNames()[splitIdx];
+ if (splitDependency != null) {
+ final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), splitDependency);
+ if (depIdx < 0) {
+ throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
+ + "' requires split '" + splitDependency + "', which is missing.");
+ }
+ targetIdx = depIdx + 1;
+ } else {
+ // Implicitly depend on the base.
+ targetIdx = 0;
+ }
+ splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+ }
+
+ // Write out the configForSplit reverse-dependencies. These appear after the <uses-split>
+ // dependencies and are considered leaves.
+ //
+ // At this point, all splits in splitDependencies have the first element in their array set.
+ for (int splitIdx = 0, size = pkg.getSplitNames().length; splitIdx < size; splitIdx++) {
+ if (pkg.getIsFeatureSplits()[splitIdx]) {
+ // Feature splits are not configForSplits.
+ continue;
+ }
+
+ // Implicit feature for the base.
+ final int targetSplitIdx;
+ final String configForSplit = pkg.getConfigForSplit()[splitIdx];
+ if (configForSplit != null) {
+ final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), configForSplit);
+ if (depIdx < 0) {
+ throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
+ + "' targets split '" + configForSplit + "', which is missing.");
+ }
+
+ if (!pkg.getIsFeatureSplits()[depIdx]) {
+ throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
+ + "' declares itself as configuration split for a non-feature split '"
+ + pkg.getSplitNames()[depIdx] + "'");
+ }
+ targetSplitIdx = depIdx + 1;
+ } else {
+ targetSplitIdx = 0;
+ }
+ splitDependencies.put(targetSplitIdx,
+ append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+ }
+
+ // Verify that there are no cycles.
+ final BitSet bitset = new BitSet();
+ for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+ int splitIdx = splitDependencies.keyAt(i);
+
+ bitset.clear();
+ while (splitIdx != -1) {
+ // Check if this split has been visited yet.
+ if (bitset.get(splitIdx)) {
+ throw new IllegalDependencyException("Cycle detected in split dependencies.");
+ }
+
+ // Mark the split so that if we visit it again, we no there is a cycle.
+ bitset.set(splitIdx);
+
+ // Follow the first dependency only, the others are leaves by definition.
+ final int[] deps = splitDependencies.get(splitIdx);
+ splitIdx = deps != null ? deps[0] : -1;
+ }
+ }
+ return splitDependencies;
+ }
+}
diff --git a/android/content/pm/verify/domain/DomainOwner.java b/android/content/pm/verify/domain/DomainOwner.java
new file mode 100644
index 0000000..5bf2c09
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainOwner.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+@SystemApi
+@DataClass(genParcelable = true, genEqualsHashCode = true, genAidl = true, genToString = true)
+public final class DomainOwner implements Parcelable {
+
+ /**
+ * Package name of that owns the domain.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * Whether or not this owner can be automatically overridden.
+ *
+ * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)
+ */
+ private final boolean mOverrideable;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainOwner.
+ *
+ * @param packageName
+ * Package name of that owns the domain.
+ * @param overrideable
+ * Whether or not this owner can be automatically overridden.
+ */
+ @DataClass.Generated.Member
+ public DomainOwner(
+ @NonNull String packageName,
+ boolean overrideable) {
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mOverrideable = overrideable;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Package name of that owns the domain.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Whether or not this owner can be automatically overridden.
+ *
+ * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)
+ */
+ @DataClass.Generated.Member
+ public boolean isOverrideable() {
+ return mOverrideable;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainOwner { " +
+ "packageName = " + mPackageName + ", " +
+ "overrideable = " + mOverrideable +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainOwner other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainOwner that = (DomainOwner) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && mOverrideable == that.mOverrideable;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + Boolean.hashCode(mOverrideable);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mOverrideable) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeString(mPackageName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainOwner(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean overrideable = (flg & 0x2) != 0;
+ String packageName = in.readString();
+
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mOverrideable = overrideable;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainOwner> CREATOR
+ = new Parcelable.Creator<DomainOwner>() {
+ @Override
+ public DomainOwner[] newArray(int size) {
+ return new DomainOwner[size];
+ }
+
+ @Override
+ public DomainOwner createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainOwner(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1614721802044L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\[email protected](genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/verify/domain/DomainSet.java b/android/content/pm/verify/domain/DomainSet.java
new file mode 100644
index 0000000..243ff08
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainSet.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+
+/**
+ * Wraps an input set of domains from the client process, to be sent to the server. Handles cases
+ * where the data size is too large by writing data using {@link Parcel#writeBlob(byte[])}.
+ *
+ * @hide
+ */
+@DataClass(genParcelable = true, genAidl = true, genEqualsHashCode = true)
+public class DomainSet implements Parcelable {
+
+ @NonNull
+ private final Set<String> mDomains;
+
+ private void parcelDomains(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostSet(dest, mDomains);
+ }
+
+ private Set<String> unparcelDomains(@NonNull Parcel in) {
+ return DomainVerificationUtils.readHostSet(in);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+ // /DomainSet.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public DomainSet(
+ @NonNull Set<String> domains) {
+ this.mDomains = domains;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDomains);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getDomains() {
+ return mDomains;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainSet other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainSet that = (DomainSet) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mDomains, that.mDomains);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mDomains);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ parcelDomains(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected DomainSet(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Set<String> domains = unparcelDomains(in);
+
+ this.mDomains = domains;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDomains);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainSet> CREATOR
+ = new Parcelable.Creator<DomainSet>() {
+ @Override
+ public DomainSet[] newArray(int size) {
+ return new DomainSet[size];
+ }
+
+ @Override
+ public DomainSet createFromParcel(@NonNull Parcel in) {
+ return new DomainSet(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1613169242020L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainSet.java",
+ inputSignatures = "private final @android.annotation.NonNull java.util.Set<java.lang.String> mDomains\nprivate void parcelDomains(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelDomains(android.os.Parcel)\nclass DomainSet extends java.lang.Object implements [android.os.Parcelable]\[email protected](genParcelable=true, genAidl=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/verify/domain/DomainVerificationInfo.java b/android/content/pm/verify/domain/DomainVerificationInfo.java
new file mode 100644
index 0000000..62277ef
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the state of all domains for a given package on device. Used by the domain verification
+ * agent to determine the domains declared by a package that need to be verified by comparing
+ * against the digital asset links response from the server hosting that domain.
+ * <p>
+ * These values for each domain can be modified through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID,
+ * Set, int)}.
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+ genEqualsHashCode = true, genHiddenConstDefs = true)
+public final class DomainVerificationInfo implements Parcelable {
+
+ // Implementation note: the following states are OUTPUT only. Any value that is synonymous with
+ // a value in DomainVerificationState must be the EXACT same integer, so that state
+ // transformation does not have to occur when sending input into the system, assuming that the
+ // system only accepts those synonymous values. The public API values declared here are only
+ // used when exiting the system server to prepare this data object for consumption by the
+ // verification agent. These constants should only be referenced inside public API classes.
+ // The server must use DomainVerificationState.
+
+ /**
+ * No response has been recorded by either the system or any verification agent.
+ */
+ public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
+
+ /**
+ * The domain has been explicitly verified.
+ */
+ public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+
+ /**
+ * Indicates the host cannot be modified by the verification agent.
+ */
+ public static final int STATE_UNMODIFIABLE = 2;
+
+ /**
+ * Indicates the host can be modified by the verification agent and is not considered verified.
+ */
+ public static final int STATE_MODIFIABLE_UNVERIFIED = 3;
+
+ /**
+ * Indicates the host can be modified by the verification agent and is considered verified.
+ */
+ public static final int STATE_MODIFIABLE_VERIFIED = 4;
+
+ /**
+ * The first available custom response code. This and any greater integer, along with {@link
+ * #STATE_SUCCESS} are the only values settable by the verification agent. All custom values
+ * will be treated as if the domain is unverified.
+ */
+ public static final int STATE_FIRST_VERIFIER_DEFINED =
+ DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
+ /**
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+ private final UUID mIdentifier;
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * Map of host names to their current state. State is an integer, which defaults to {@link
+ * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
+ * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
+ * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
+ * <p>
+ * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
+ * that the agent attempt to verify all domains that it can modify the state of.
+ */
+ @NonNull
+ private final Map<String, Integer> mHostToStateMap;
+
+ private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostMap(dest, mHostToStateMap);
+ }
+
+ private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
+ return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
+ DomainVerificationUserState.class.getClassLoader());
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @android.annotation.IntDef(prefix = "STATE_", value = {
+ STATE_NO_RESPONSE,
+ STATE_SUCCESS,
+ STATE_UNMODIFIABLE,
+ STATE_MODIFIABLE_UNVERIFIED,
+ STATE_MODIFIABLE_VERIFIED,
+ STATE_FIRST_VERIFIER_DEFINED
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface State {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String stateToString(@State int value) {
+ switch (value) {
+ case STATE_NO_RESPONSE:
+ return "STATE_NO_RESPONSE";
+ case STATE_SUCCESS:
+ return "STATE_SUCCESS";
+ case STATE_UNMODIFIABLE:
+ return "STATE_UNMODIFIABLE";
+ case STATE_MODIFIABLE_UNVERIFIED:
+ return "STATE_MODIFIABLE_UNVERIFIED";
+ case STATE_MODIFIABLE_VERIFIED:
+ return "STATE_MODIFIABLE_VERIFIED";
+ case STATE_FIRST_VERIFIER_DEFINED:
+ return "STATE_FIRST_VERIFIER_DEFINED";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new DomainVerificationInfo.
+ *
+ * @param identifier
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ * @param packageName
+ * The package name that this data corresponds to.
+ * @param hostToStateMap
+ * Map of host names to their current state. State is an integer, which defaults to {@link
+ * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
+ * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
+ * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
+ * <p>
+ * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
+ * that the agent attempt to verify all domains that it can modify the state of.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationInfo(
+ @NonNull UUID identifier,
+ @NonNull String packageName,
+ @NonNull Map<String,Integer> hostToStateMap) {
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Map of host names to their current state. State is an integer, which defaults to {@link
+ * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
+ * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
+ * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
+ * <p>
+ * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
+ * that the agent attempt to verify all domains that it can modify the state of.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Integer> getHostToStateMap() {
+ return mHostToStateMap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationInfo { " +
+ "identifier = " + mIdentifier + ", " +
+ "packageName = " + mPackageName + ", " +
+ "hostToStateMap = " + mHostToStateMap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationInfo that = (DomainVerificationInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIdentifier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<UUID> sParcellingForIdentifier =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForUUID.class);
+ static {
+ if (sParcellingForIdentifier == null) {
+ sParcellingForIdentifier = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForUUID());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+ dest.writeString(mPackageName);
+ parcelHostToStateMap(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationInfo(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ UUID identifier = sParcellingForIdentifier.unparcel(in);
+ String packageName = in.readString();
+ Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in);
+
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationInfo> CREATOR
+ = new Parcelable.Creator<DomainVerificationInfo>() {
+ @Override
+ public DomainVerificationInfo[] newArray(int size) {
+ return new DomainVerificationInfo[size];
+ }
+
+ @Override
+ public DomainVerificationInfo createFromParcel(@NonNull Parcel in) {
+ return new DomainVerificationInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1615317187669L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
+ inputSignatures = "public static final int STATE_NO_RESPONSE\npublic static final int STATE_SUCCESS\npublic static final int STATE_UNMODIFIABLE\npublic static final int STATE_MODIFIABLE_UNVERIFIED\npublic static final int STATE_MODIFIABLE_VERIFIED\npublic static final int STATE_FIRST_VERIFIER_DEFINED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/verify/domain/DomainVerificationManager.java b/android/content/pm/verify/domain/DomainVerificationManager.java
new file mode 100644
index 0000000..77bd147
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.CheckResult;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+
+import com.android.internal.util.CollectionUtils;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+/**
+ * System service to access domain verification APIs.
+ *
+ * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to
+ * check if/how they are verified for a domain, which is required starting from platform
+ * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare
+ * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against
+ * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an
+ * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of
+ * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the
+ * caller when using {@link Context#startActivity(Intent)} and similar.
+ */
+@SystemService(Context.DOMAIN_VERIFICATION_SERVICE)
+public final class DomainVerificationManager {
+
+ /**
+ * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed
+ * to an the domain verification agent that handles
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_VERIFICATION_REQUEST =
+ "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+
+ /**
+ * Default return code for when a method has succeeded.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int STATUS_OK = 0;
+
+ /**
+ * The provided domain set ID was invalid, probably due to the package being updated between
+ * the initial request that provided the ID and the method call that used it. This usually
+ * means the work being processed by the verification agent is outdated and a new request
+ * should be scheduled, which should already be in progress as part of the
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_DOMAIN_SET_ID_INVALID = 1;
+
+ /**
+ * The provided set of domains contains a domain not declared by the target package. This
+ * usually means the work being processed by the verification agent is outdated and a new
+ * request should be scheduled, which should already be in progress as part of the
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_UNKNOWN_DOMAIN = 2;
+
+ /**
+ * The system was unable to select the domain for approval. This indicates another application
+ * has been granted a higher approval, usually through domain verification, and the target
+ * package is unable to override it.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_UNABLE_TO_APPROVE = 3;
+
+ /**
+ * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API.
+ *
+ * @hide
+ */
+ public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ERROR_"}, value = {
+ ERROR_DOMAIN_SET_ID_INVALID,
+ ERROR_UNKNOWN_DOMAIN,
+ ERROR_UNABLE_TO_APPROVE,
+ })
+ public @interface Error {
+ }
+
+ private final Context mContext;
+
+ private final IDomainVerificationManager mDomainVerificationManager;
+
+ /**
+ * System service to access the domain verification APIs.
+ * <p>
+ * Allows the approved domain verification agent on the device (the sole holder of {@link
+ * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of
+ * domains declared by applications in their AndroidManifest.xml, to allow them to open those
+ * links inside the app when selected by the user. This is done through querying {@link
+ * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID,
+ * Set, int)}.
+ * <p>
+ * Also allows the domain preference settings (holder of
+ * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION})
+ * to update the preferences of the user, when they have chosen to explicitly allow an
+ * application to open links. This is done through querying
+ * {@link #getDomainVerificationUserState(String)} and calling
+ * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
+ * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ *
+ * @hide
+ */
+ public DomainVerificationManager(Context context,
+ IDomainVerificationManager domainVerificationManager) {
+ mContext = context;
+ mDomainVerificationManager = domainVerificationManager;
+ }
+
+ /**
+ * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
+ * usually a heavy workload and should be done infrequently.
+ *
+ * @return the current snapshot of package names with valid autoVerify URLs.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ public List<String> queryValidVerificationPackageNames() {
+ try {
+ return mDomainVerificationManager.queryValidVerificationPackageNames();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieves the domain verification state for a given package.
+ *
+ * @return the data for the package, or null if it does not declare any autoVerify domains
+ * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error
+ * and should not be re-tried except on a time scheduled basis.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationInfo(packageName);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ /**
+ * Change the verification status of the {@param domains} of the package associated with {@param
+ * domainSetId}.
+ *
+ * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+ * @param domains List of host names to change the state of.
+ * @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
+ * @return error code or {@link #STATUS_OK} if successful
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is installed on
+ * a volume that is no longer mounted. This error is
+ * unrecoverable until the package is available again, and
+ * should not be re-tried except on a time scheduled basis.
+ * @hide
+ */
+ @CheckResult
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws NameNotFoundException {
+ validateInput(domainSetId, domains);
+
+ try {
+ return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+ new DomainSet(domains), state);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, null);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ /**
+ * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web
+ * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with
+ * the verification status for the specific domain being opened and other system state. An app
+ * with this enabled is not guaranteed to be the sole link handler for its domains.
+ * <p>
+ * By default, all apps are allowed to open links. Users must disable them explicitly.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed) throws NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
+ allowed, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, null);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ /**
+ * Update the recorded user selection for the given {@param domains} for the given {@param
+ * domainSetId}. This state is recorded for the lifetime of a domain for a package on device,
+ * and will never be reset by the system short of an app data clear.
+ * <p>
+ * This state is stored per device user. If another user needs to be changed, the appropriate
+ * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should
+ * be used.
+ * <p>
+ * Enabling an unverified domain will allow an application to open it, but this can only occur
+ * if no other app on the device is approved for a higher approval level. This can queried
+ * using {@link #getOwnersForDomain(String)}.
+ *
+ * If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to
+ * enable that domain will disable all other owners.
+ *
+ * On the other hand, if any of the owners are non-overrideable, then this must be called with
+ * false for all of the other owners to disable them before the domain can be taken by a new
+ * owner.
+ *
+ * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+ * @param domains The domains to toggle the state of.
+ * @param enabled Whether or not the app should automatically open the domains specified.
+ * @return error code or {@link #STATUS_OK} if successful
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is installed on
+ * a volume that is no longer mounted. This error is
+ * unrecoverable until the package is available again, and
+ * should not be re-tried except on a time scheduled basis.
+ * @hide
+ */
+ @CheckResult
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
+ validateInput(domainSetId, domains);
+
+ try {
+ return mDomainVerificationManager.setDomainVerificationUserSelection(
+ domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, null);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the user state for the given package and the {@link Context}'s user.
+ *
+ * @param packageName The app to query state for.
+ * @return The user selection verification data for the given package for the user, or null if
+ * the package does not declare any HTTP/HTTPS domains.
+ */
+ @Nullable
+ public DomainVerificationUserState getDomainVerificationUserState(
+ @NonNull String packageName) throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationUserState(packageName,
+ mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ /**
+ * For the given domain, return all apps which are approved to open it in a
+ * greater than 0 priority. This does not mean that all apps can actually open
+ * an Intent with that domain. That will be decided by the set of apps which
+ * are the highest priority level, ignoring all lower priority levels.
+ *
+ * The set will be ordered from lowest to highest priority.
+ *
+ * @param domain The host to query for. An invalid domain will result in an empty set.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ public SortedSet<DomainOwner> getOwnersForDomain(@NonNull String domain) {
+ try {
+ Objects.requireNonNull(domain);
+ final List<DomainOwner> orderedList = mDomainVerificationManager.getOwnersForDomain(
+ domain, mContext.getUserId());
+ SortedSet<DomainOwner> set = new TreeSet<>(
+ Comparator.comparingInt(orderedList::indexOf));
+ set.addAll(orderedList);
+ return set;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private Exception rethrow(Exception exception, @Nullable String packageName) {
+ if (exception instanceof ServiceSpecificException) {
+ int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode;
+ if (packageName == null) {
+ packageName = exception.getMessage();
+ }
+
+ if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) {
+ return new NameNotFoundException(packageName);
+ }
+
+ return exception;
+ } else if (exception instanceof RemoteException) {
+ return ((RemoteException) exception).rethrowFromSystemServer();
+ } else {
+ return exception;
+ }
+ }
+
+ private void validateInput(@Nullable UUID domainSetId, @Nullable Set<String> domains) {
+ if (domainSetId == null) {
+ throw new IllegalArgumentException("domainSetId cannot be null");
+ } else if (CollectionUtils.isEmpty(domains)) {
+ throw new IllegalArgumentException("Provided domain set cannot be empty");
+ }
+ }
+}
diff --git a/android/content/pm/verify/domain/DomainVerificationRequest.java b/android/content/pm/verify/domain/DomainVerificationRequest.java
new file mode 100644
index 0000000..65f6d7c
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainVerificationRequest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Set;
+
+/**
+ * Request object sent in the {@link Intent} that's broadcast to the domain verification agent,
+ * retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}.
+ * <p>
+ * This contains the set of packages which have been invalidated and will require re-verification.
+ * The exact domains can be retrieved with
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)}
+ *
+ * @hide
+ */
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genHiddenConstructor = true, genAidl = false, genEqualsHashCode = true)
+@SystemApi
+public final class DomainVerificationRequest implements Parcelable {
+
+ /**
+ * The package names of the apps that need to be verified. The receiver should call {@link
+ * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get
+ * the actual set of domains that need to be acted on.
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class)
+ private final Set<String> mPackageNames;
+
+ private void parcelPackageNames(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostSet(dest, mPackageNames);
+ }
+
+ private Set<String> unparcelPackageNames(@NonNull Parcel in) {
+ return DomainVerificationUtils.readHostSet(in);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+ // /DomainVerificationRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationRequest.
+ *
+ * @param packageNames
+ * The package names of the apps that need to be verified. The receiver should call {@link
+ * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get
+ * the actual set of domains that need to be acted on.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationRequest(
+ @NonNull Set<String> packageNames) {
+ this.mPackageNames = packageNames;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageNames);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The package names of the apps that need to be verified. The receiver should call {@link
+ * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get
+ * the actual set of domains that need to be acted on.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getPackageNames() {
+ return mPackageNames;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationRequest other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationRequest that = (DomainVerificationRequest) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageNames, that.mPackageNames);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageNames);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Set<String>> sParcellingForPackageNames =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForStringSet.class);
+ static {
+ if (sParcellingForPackageNames == null) {
+ sParcellingForPackageNames = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForStringSet());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ parcelPackageNames(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Set<String> packageNames = unparcelPackageNames(in);
+
+ this.mPackageNames = packageNames;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageNames);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationRequest> CREATOR
+ = new Parcelable.Creator<DomainVerificationRequest>() {
+ @Override
+ public DomainVerificationRequest[] newArray(int size) {
+ return new DomainVerificationRequest[size];
+ }
+
+ @Override
+ public DomainVerificationRequest createFromParcel(@NonNull Parcel in) {
+ return new DomainVerificationRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1613169505495L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nprivate void parcelPackageNames(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelPackageNames(android.os.Parcel)\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/verify/domain/DomainVerificationState.java b/android/content/pm/verify/domain/DomainVerificationState.java
new file mode 100644
index 0000000..8e28042
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainVerificationState.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2021 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.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+/**
+ * @hide
+ */
+public interface DomainVerificationState {
+
+ @IntDef({
+ STATE_NO_RESPONSE,
+ STATE_SUCCESS,
+ STATE_MIGRATED,
+ STATE_RESTORED,
+ STATE_APPROVED,
+ STATE_DENIED,
+ STATE_LEGACY_FAILURE,
+ STATE_SYS_CONFIG,
+ STATE_FIRST_VERIFIER_DEFINED
+ })
+ @interface State {
+ }
+
+ // TODO(b/159952358): Document all the places that states need to be updated when one is added
+ /**
+ * @see DomainVerificationInfo#STATE_NO_RESPONSE
+ */
+ int STATE_NO_RESPONSE = 0;
+
+ /**
+ * @see DomainVerificationInfo#STATE_SUCCESS
+ */
+ int STATE_SUCCESS = 1;
+
+ /**
+ * The system has chosen to ignore the verification agent's opinion on whether the domain should
+ * be verified. This will treat the domain as verified.
+ */
+ int STATE_APPROVED = 2;
+
+ /**
+ * The system has chosen to ignore the verification agent's opinion on whether the domain should
+ * be verified. This will treat the domain as unverified.
+ */
+ int STATE_DENIED = 3;
+
+ /**
+ * The state was migrated from the previous intent filter verification API. This will treat the
+ * domain as verified, but it should be updated by the verification agent. The older API's
+ * collection and handling of verifying domains may lead to improperly migrated state.
+ */
+ int STATE_MIGRATED = 4;
+
+ /**
+ * The state was restored from a user backup or by the system. This is treated as if the domain
+ * was verified, but the verification agent may choose to re-verify this domain to be certain
+ * nothing has changed since the snapshot.
+ */
+ int STATE_RESTORED = 5;
+
+ /**
+ * The domain was failed by a legacy intent filter verification agent from v1 of the API. This
+ * is made distinct from {@link #STATE_FIRST_VERIFIER_DEFINED} to prevent any v2 verification
+ * agent from misinterpreting the result, since {@link #STATE_FIRST_VERIFIER_DEFINED} is agent
+ * specific and can be defined as a special error code.
+ */
+ int STATE_LEGACY_FAILURE = 6;
+
+ /**
+ * The application has been granted auto verification for all domains by configuration on the
+ * system image.
+ *
+ * TODO: Can be stored per-package rather than for all domains for a package to save memory.
+ */
+ int STATE_SYS_CONFIG = 7;
+
+ /**
+ * @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED
+ */
+ int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
+
+ @NonNull
+ static String stateToDebugString(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ return "none";
+ case DomainVerificationState.STATE_SUCCESS:
+ return "verified";
+ case DomainVerificationState.STATE_APPROVED:
+ return "approved";
+ case DomainVerificationState.STATE_DENIED:
+ return "denied";
+ case DomainVerificationState.STATE_MIGRATED:
+ return "migrated";
+ case DomainVerificationState.STATE_RESTORED:
+ return "restored";
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return "legacy_failure";
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return "system_configured";
+ default:
+ return String.valueOf(state);
+ }
+ }
+
+ /**
+ * For determining re-verify policy. This is hidden from the domain verification agent so that
+ * no behavior is made based on the result.
+ */
+ static boolean isDefault(@State int state) {
+ switch (state) {
+ case STATE_NO_RESPONSE:
+ case STATE_MIGRATED:
+ case STATE_RESTORED:
+ return true;
+ case STATE_SUCCESS:
+ case STATE_APPROVED:
+ case STATE_DENIED:
+ case STATE_LEGACY_FAILURE:
+ case STATE_SYS_CONFIG:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a state considers the corresponding domain to be successfully verified. The domain
+ * verification agent may use this to determine whether or not to re-verify a domain.
+ */
+ static boolean isVerified(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return true;
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a state is modifiable by the domain verification agent. This is useful as the
+ * platform may add new state codes in newer versions, and older verification agents can use
+ * this method to determine if a state can be changed without having to be aware of what the new
+ * state means.
+ */
+ static boolean isModifiable(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return true;
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return false;
+ default:
+ return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ }
+ }
+
+ /**
+ * Whether the state is migrated when updating a package. Generally this is only for states
+ * that maintain verification state or were set by an explicit user or developer action.
+ */
+ static boolean shouldMigrate(@State int state) {
+ switch (state) {
+ case STATE_SUCCESS:
+ case STATE_MIGRATED:
+ case STATE_RESTORED:
+ case STATE_APPROVED:
+ case STATE_DENIED:
+ return true;
+ case STATE_NO_RESPONSE:
+ case STATE_LEGACY_FAILURE:
+ case STATE_SYS_CONFIG:
+ case STATE_FIRST_VERIFIER_DEFINED:
+ default:
+ return false;
+ }
+ }
+
+ @DomainVerificationInfo.State
+ static int convertToInfoState(@State int internalState) {
+ if (internalState >= STATE_FIRST_VERIFIER_DEFINED) {
+ return internalState;
+ } else if (internalState == STATE_NO_RESPONSE) {
+ return DomainVerificationInfo.STATE_NO_RESPONSE;
+ } else if (internalState == STATE_SUCCESS) {
+ return DomainVerificationInfo.STATE_SUCCESS;
+ } else if (!isModifiable(internalState)) {
+ return DomainVerificationInfo.STATE_UNMODIFIABLE;
+ } else if (isVerified(internalState)) {
+ return DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED;
+ } else {
+ return DomainVerificationInfo.STATE_MODIFIABLE_UNVERIFIED;
+ }
+ }
+}
diff --git a/android/content/pm/verify/domain/DomainVerificationUserState.java b/android/content/pm/verify/domain/DomainVerificationUserState.java
new file mode 100644
index 0000000..1e60abb
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainVerificationUserState.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
+ * package in its manifest, whether or not they were marked for auto verification.
+ * <p>
+ * Applications should use {@link #getHostToStateMap()} if necessary to
+ * check if/how they are verified for a domain, which is required starting from platform
+ * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare
+ * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against
+ * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an
+ * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of
+ * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the
+ * caller when using {@link Context#startActivity(Intent)} and similar.
+ * <p>
+ * By default, all apps are allowed to automatically open links for the above case for domains that
+ * they've successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
+ * The user can decide to disable this, disallowing the application from opening all links. Note
+ * that the toggle affects <b>all</b> links and is not based on the verification state of the
+ * domains.
+ * <p>
+ * Assuming the toggle is enabled, the user can also select additional unverified domains to grant
+ * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single
+ * application can be approved for a domain unless the applications are both approved. If another
+ * application is approved, the user will not be allowed to enable the domain.
+ */
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+ genEqualsHashCode = true, genHiddenConstDefs = true)
+public final class DomainVerificationUserState implements Parcelable {
+
+ /**
+ * The domain is unverified and unselected, and the application is unable to open web links
+ * that resolve to the domain.
+ */
+ public static final int DOMAIN_STATE_NONE = 0;
+
+ /**
+ * The domain has been selected by the user. This may be reset to {@link #DOMAIN_STATE_NONE} if
+ * another application is selected or verified for the same domain.
+ */
+ public static final int DOMAIN_STATE_SELECTED = 1;
+
+ /**
+ * The domain has been previously verified by the domain verification agent.
+ */
+ public static final int DOMAIN_STATE_VERIFIED = 2;
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+ private final UUID mIdentifier;
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * The user that this data corresponds to.
+ */
+ @NonNull
+ private final UserHandle mUser;
+
+ /**
+ * Whether or not this package is allowed to open links.
+ */
+ @NonNull
+ private final boolean mLinkHandlingAllowed;
+
+ /**
+ * Mapping of domain host to state, as defined by {@link DomainState}.
+ */
+ @NonNull
+ private final Map<String, Integer> mHostToStateMap;
+
+ private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostMap(dest, mHostToStateMap);
+ }
+
+ @NonNull
+ private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
+ return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
+ DomainVerificationUserState.class.getClassLoader());
+ }
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ * @hide
+ */
+ @SystemApi
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @android.annotation.IntDef(prefix = "DOMAIN_STATE_", value = {
+ DOMAIN_STATE_NONE,
+ DOMAIN_STATE_SELECTED,
+ DOMAIN_STATE_VERIFIED
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface DomainState {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String domainStateToString(@DomainState int value) {
+ switch (value) {
+ case DOMAIN_STATE_NONE:
+ return "DOMAIN_STATE_NONE";
+ case DOMAIN_STATE_SELECTED:
+ return "DOMAIN_STATE_SELECTED";
+ case DOMAIN_STATE_VERIFIED:
+ return "DOMAIN_STATE_VERIFIED";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new DomainVerificationUserState.
+ *
+ * @param packageName
+ * The package name that this data corresponds to.
+ * @param user
+ * The user that this data corresponds to.
+ * @param linkHandlingAllowed
+ * Whether or not this package is allowed to open links.
+ * @param hostToStateMap
+ * Mapping of domain host to state, as defined by {@link DomainState}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationUserState(
+ @NonNull UUID identifier,
+ @NonNull String packageName,
+ @NonNull UserHandle user,
+ @NonNull boolean linkHandlingAllowed,
+ @NonNull Map<String,Integer> hostToStateMap) {
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUser = user;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUser);
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLinkHandlingAllowed);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The user that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Whether or not this package is allowed to open links.
+ */
+ @DataClass.Generated.Member
+ public @NonNull boolean isLinkHandlingAllowed() {
+ return mLinkHandlingAllowed;
+ }
+
+ /**
+ * Mapping of domain host to state, as defined by {@link DomainState}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Integer> getHostToStateMap() {
+ return mHostToStateMap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationUserState { " +
+ "identifier = " + mIdentifier + ", " +
+ "packageName = " + mPackageName + ", " +
+ "user = " + mUser + ", " +
+ "linkHandlingAllowed = " + mLinkHandlingAllowed + ", " +
+ "hostToStateMap = " + mHostToStateMap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationUserState that = (DomainVerificationUserState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mUser, that.mUser)
+ && mLinkHandlingAllowed == that.mLinkHandlingAllowed
+ && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIdentifier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mUser);
+ _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<UUID> sParcellingForIdentifier =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForUUID.class);
+ static {
+ if (sParcellingForIdentifier == null) {
+ sParcellingForIdentifier = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForUUID());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mLinkHandlingAllowed) flg |= 0x8;
+ dest.writeByte(flg);
+ sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+ dest.writeString(mPackageName);
+ dest.writeTypedObject(mUser, flags);
+ parcelHostToStateMap(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationUserState(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean linkHandlingAllowed = (flg & 0x8) != 0;
+ UUID identifier = sParcellingForIdentifier.unparcel(in);
+ String packageName = in.readString();
+ UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+ Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in);
+
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUser = user;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUser);
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLinkHandlingAllowed);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationUserState> CREATOR
+ = new Parcelable.Creator<DomainVerificationUserState>() {
+ @Override
+ public DomainVerificationUserState[] newArray(int size) {
+ return new DomainVerificationUserState[size];
+ }
+
+ @Override
+ public DomainVerificationUserState createFromParcel(@NonNull Parcel in) {
+ return new DomainVerificationUserState(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1614721840152L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java",
+ inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\npublic @android.annotation.SystemApi @android.annotation.NonNull java.util.UUID getIdentifier()\nclass DomainVerificationUserState extends java.lang.Object implements [android.os.Parcelable]\[email protected](genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/android/content/pm/verify/domain/DomainVerificationUtils.java b/android/content/pm/verify/domain/DomainVerificationUtils.java
new file mode 100644
index 0000000..93005fa
--- /dev/null
+++ b/android/content/pm/verify/domain/DomainVerificationUtils.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class DomainVerificationUtils {
+
+ private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2;
+
+ /**
+ * Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])}
+ * if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the
+ * written map is the only data structure in the caller that varies based on the host data set.
+ * Other data that will be written to the parcel after this method will not be considered in the
+ * calculation.
+ */
+ public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) {
+ boolean targetSizeExceeded = false;
+ int totalSize = dest.dataSize();
+ for (String host : map.keySet()) {
+ totalSize += estimatedByteSizeOf(host);
+ if (totalSize > STRINGS_TARGET_BYTE_SIZE) {
+ targetSizeExceeded = true;
+ break;
+ }
+ }
+
+ dest.writeBoolean(targetSizeExceeded);
+
+ if (!targetSizeExceeded) {
+ dest.writeMap(map);
+ return;
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeMap(map);
+ dest.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ /**
+ * Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}.
+ */
+ @NonNull
+ @SuppressWarnings("rawtypes")
+ public static <T extends Map> T readHostMap(@NonNull Parcel in, @NonNull T map,
+ @NonNull ClassLoader classLoader) {
+ boolean targetSizeExceeded = in.readBoolean();
+
+ if (!targetSizeExceeded) {
+ in.readMap(map, classLoader);
+ return map;
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ byte[] blob = in.readBlob();
+ data.unmarshall(blob, 0, blob.length);
+ data.setDataPosition(0);
+ data.readMap(map, classLoader);
+ } finally {
+ data.recycle();
+ }
+
+ return map;
+ }
+
+ /**
+ * {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}.
+ */
+ public static void writeHostSet(@NonNull Parcel dest, @NonNull Set<String> set) {
+ boolean targetSizeExceeded = false;
+ int totalSize = dest.dataSize();
+ for (String host : set) {
+ totalSize += estimatedByteSizeOf(host);
+ if (totalSize > STRINGS_TARGET_BYTE_SIZE) {
+ targetSizeExceeded = true;
+ break;
+ }
+ }
+
+ dest.writeBoolean(targetSizeExceeded);
+
+ if (!targetSizeExceeded) {
+ writeSet(dest, set);
+ return;
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ writeSet(data, set);
+ dest.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ /**
+ * {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}.
+ */
+ @NonNull
+ public static Set<String> readHostSet(@NonNull Parcel in) {
+ boolean targetSizeExceeded = in.readBoolean();
+
+ if (!targetSizeExceeded) {
+ return readSet(in);
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ byte[] blob = in.readBlob();
+ data.unmarshall(blob, 0, blob.length);
+ data.setDataPosition(0);
+ return readSet(data);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ private static void writeSet(@NonNull Parcel dest, @Nullable Set<String> set) {
+ if (set == null) {
+ dest.writeInt(-1);
+ return;
+ }
+ dest.writeInt(set.size());
+ for (String string : set) {
+ dest.writeString(string);
+ }
+ }
+
+ @NonNull
+ private static Set<String> readSet(@NonNull Parcel in) {
+ int size = in.readInt();
+ if (size == -1) {
+ return Collections.emptySet();
+ }
+
+ ArraySet<String> set = new ArraySet<>(size);
+ for (int count = 0; count < size; count++) {
+ set.add(in.readString());
+ }
+ return set;
+ }
+
+ /**
+ * Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains
+ * across the client-server API.
+ */
+ public static int estimatedByteSizeOf(String string) {
+ return string.length() * 2 + 12;
+ }
+}
diff --git a/android/content/res/ApkAssets.java b/android/content/res/ApkAssets.java
new file mode 100644
index 0000000..6fd2d05
--- /dev/null
+++ b/android/content/res/ApkAssets.java
@@ -0,0 +1,460 @@
+/*
+ * 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.om.OverlayableInfo;
+import android.content.res.loader.AssetsProvider;
+import android.content.res.loader.ResourcesProvider;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * 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 {
+
+ /**
+ * The apk assets contains framework resource values specified by the system.
+ * This allows some functions to filter out this package when computing what
+ * configurations/resources are available.
+ */
+ public static final int PROPERTY_SYSTEM = 1 << 0;
+
+ /**
+ * The apk assets is a shared library or was loaded as a shared library by force.
+ * The package ids of dynamic apk assets are assigned at runtime instead of compile time.
+ */
+ public static final int PROPERTY_DYNAMIC = 1 << 1;
+
+ /**
+ * The apk assets has been loaded dynamically using a {@link ResourcesProvider}.
+ * Loader apk assets overlay resources like RROs except they are not backed by an idmap.
+ */
+ public static final int PROPERTY_LOADER = 1 << 2;
+
+ /**
+ * The apk assets is a RRO.
+ * An RRO overlays resource values of its target package.
+ */
+ private static final int PROPERTY_OVERLAY = 1 << 3;
+
+ /**
+ * The apk assets is owned by the application running in this process and incremental crash
+ * protections for this APK must be disabled.
+ */
+ public static final int PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 << 4;
+
+ /** Flags that change the behavior of loaded apk assets. */
+ @IntDef(prefix = { "PROPERTY_" }, value = {
+ PROPERTY_SYSTEM,
+ PROPERTY_DYNAMIC,
+ PROPERTY_LOADER,
+ PROPERTY_OVERLAY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PropertyFlags {}
+
+ /** The path used to load the apk assets represents an APK file. */
+ private static final int FORMAT_APK = 0;
+
+ /** The path used to load the apk assets represents an idmap file. */
+ private static final int FORMAT_IDMAP = 1;
+
+ /** The path used to load the apk assets represents an resources.arsc file. */
+ private static final int FORMAT_ARSC = 2;
+
+ /** the path used to load the apk assets represents a directory. */
+ private static final int FORMAT_DIR = 3;
+
+ // Format types that change how the apk assets are loaded.
+ @IntDef(prefix = { "FORMAT_" }, value = {
+ FORMAT_APK,
+ FORMAT_IDMAP,
+ FORMAT_ARSC,
+ FORMAT_DIR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FormatType {}
+
+ @GuardedBy("this")
+ private long mNativePtr; // final, except cleared in finalizer.
+
+ @Nullable
+ @GuardedBy("this")
+ private final StringBlock mStringBlock; // null or closed if mNativePtr = 0.
+
+ @PropertyFlags
+ private final int mFlags;
+
+ @Nullable
+ private final AssetsProvider mAssets;
+
+ /**
+ * 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 loadFromPath(path, 0 /* flags */);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @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, @PropertyFlags int flags)
+ throws IOException {
+ return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @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, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_APK, path, flags, assets);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor.
+ *
+ * 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 flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @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, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor.
+ *
+ * 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 offset The location within the file that the apk starts. This must be 0 if length is
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
+ * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
+ * if it extends to the end of the file.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @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, long offset, long length, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets)
+ throws IOException {
+ return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets);
+ }
+
+ /**
+ * 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 flags flags that change the behavior of loaded apk assets
+ * @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,
+ @PropertyFlags int flags) throws IOException {
+ return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
+ * for use with a {@link ResourcesProvider}.
+ *
+ * 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 resources.arsc.
+ * @param friendlyName The friendly name used to identify this ApkAssets when logging.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
+ * for use with a {@link ResourcesProvider}.
+ *
+ * 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 resources.arsc.
+ * @param friendlyName The friendly name used to identify this ApkAssets when logging.
+ * @param offset The location within the file that the table starts. This must be 0 if length is
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
+ * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
+ * if it extends to the end of the file.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given directory path. The directory should have the
+ * file structure of an APK.
+ *
+ * @param path The path to a directory on disk.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromDir(@NonNull String path,
+ @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_DIR, path, flags, assets);
+ }
+
+ /**
+ * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
+ * is required for a lot of APIs, and it's easier to have a non-null reference rather than
+ * tracking a separate identifier.
+ *
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ */
+ @NonNull
+ public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags,
+ @Nullable AssetsProvider assets) {
+ return new ApkAssets(flags, assets);
+ }
+
+ private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ Objects.requireNonNull(path, "path");
+ mFlags = flags;
+ mNativePtr = nativeLoad(format, path, flags, assets);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
+ }
+
+ private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
+ @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
+ throws IOException {
+ Objects.requireNonNull(fd, "fd");
+ Objects.requireNonNull(friendlyName, "friendlyName");
+ mFlags = flags;
+ mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
+ }
+
+ private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
+ @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ Objects.requireNonNull(fd, "fd");
+ Objects.requireNonNull(friendlyName, "friendlyName");
+ mFlags = flags;
+ mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
+ }
+
+ private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
+ mFlags = flags;
+ mNativePtr = nativeLoadEmpty(flags, assets);
+ mStringBlock = null;
+ mAssets = assets;
+ }
+
+ @UnsupportedAppUsage
+ public @NonNull String getAssetPath() {
+ synchronized (this) {
+ return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr));
+ }
+ }
+
+ /** @hide */
+ public @NonNull String getDebugName() {
+ synchronized (this) {
+ return nativeGetDebugName(mNativePtr);
+ }
+ }
+
+ @Nullable
+ CharSequence getStringFromPool(int idx) {
+ if (mStringBlock == null) {
+ return null;
+ }
+
+ synchronized (this) {
+ return mStringBlock.getSequence(idx);
+ }
+ }
+
+ /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */
+ public boolean isForLoader() {
+ return (mFlags & PROPERTY_LOADER) != 0;
+ }
+
+ /**
+ * Returns the assets provider that overrides the loading of assets present in this apk assets.
+ */
+ @Nullable
+ public AssetsProvider getAssetsProvider() {
+ return mAssets;
+ }
+
+ /**
+ * 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 {
+ Objects.requireNonNull(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 careful.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
+ }
+ }
+
+ /** @hide */
+ @Nullable
+ public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
+ synchronized (this) {
+ return nativeGetOverlayableInfo(mNativePtr, overlayableName);
+ }
+ }
+
+ /** @hide */
+ public boolean definesOverlayable() throws IOException {
+ synchronized (this) {
+ return nativeDefinesOverlayable(mNativePtr);
+ }
+ }
+
+ /**
+ * 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=" + getDebugName() + "}";
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ }
+
+ /**
+ * Closes this class and the contained {@link #mStringBlock}.
+ */
+ public void close() {
+ synchronized (this) {
+ if (mNativePtr != 0) {
+ if (mStringBlock != null) {
+ mStringBlock.close();
+ }
+ nativeDestroy(mNativePtr);
+ mNativePtr = 0;
+ }
+ }
+ }
+
+ private static native long nativeLoad(@FormatType int format, @NonNull String path,
+ @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
+ private static native long nativeLoadEmpty(@PropertyFlags int flags,
+ @Nullable AssetsProvider asset);
+ private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd,
+ @NonNull String friendlyName, @PropertyFlags int flags,
+ @Nullable AssetsProvider asset) throws IOException;
+ private static native long nativeLoadFdOffsets(@FormatType int format,
+ @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length,
+ @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
+ private static native void nativeDestroy(long ptr);
+ private static native @NonNull String nativeGetAssetPath(long ptr);
+ private static native @NonNull String nativeGetDebugName(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;
+ private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
+ String overlayableName) throws IOException;
+ private static native boolean nativeDefinesOverlayable(long ptr) throws IOException;
+}
diff --git a/android/content/res/AssetFileDescriptor.java b/android/content/res/AssetFileDescriptor.java
new file mode 100644
index 0000000..e93ec00
--- /dev/null
+++ b/android/content/res/AssetFileDescriptor.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2006 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * File descriptor of an entry in the AssetManager. This provides your own
+ * opened FileDescriptor that can be used to read the data, as well as the
+ * offset and length of that entry's data in the file.
+ */
+public class AssetFileDescriptor implements Parcelable, Closeable {
+ /**
+ * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
+ * and {@link #getDeclaredLength} when a length has not been declared. This means
+ * the data extends to the end of the file.
+ */
+ public static final long UNKNOWN_LENGTH = -1;
+
+ @UnsupportedAppUsage
+ private final ParcelFileDescriptor mFd;
+ @UnsupportedAppUsage
+ private final long mStartOffset;
+ @UnsupportedAppUsage
+ private final long mLength;
+ private final Bundle mExtras;
+
+ /**
+ * Create a new AssetFileDescriptor from the given values.
+ *
+ * @param fd The underlying file descriptor.
+ * @param startOffset The location within the file that the asset starts.
+ * This must be 0 if length is UNKNOWN_LENGTH.
+ * @param length The number of bytes of the asset, or
+ * {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
+ */
+ public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
+ long length) {
+ this(fd, startOffset, length, null);
+ }
+
+ /**
+ * Create a new AssetFileDescriptor from the given values.
+ *
+ * @param fd The underlying file descriptor.
+ * @param startOffset The location within the file that the asset starts.
+ * This must be 0 if length is UNKNOWN_LENGTH.
+ * @param length The number of bytes of the asset, or
+ * {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
+ * @param extras additional details that can be used to interpret the
+ * underlying file descriptor. May be null.
+ */
+ public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
+ long length, Bundle extras) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (length < 0 && startOffset != 0) {
+ throw new IllegalArgumentException(
+ "startOffset must be 0 when using UNKNOWN_LENGTH");
+ }
+ mFd = fd;
+ mStartOffset = startOffset;
+ mLength = length;
+ mExtras = extras;
+ }
+
+ /**
+ * The AssetFileDescriptor contains its own ParcelFileDescriptor, which
+ * in addition to the normal FileDescriptor object also allows you to close
+ * the descriptor when you are done with it.
+ */
+ public ParcelFileDescriptor getParcelFileDescriptor() {
+ return mFd;
+ }
+
+ /**
+ * Returns the FileDescriptor that can be used to read the data in the
+ * file.
+ */
+ public FileDescriptor getFileDescriptor() {
+ return mFd.getFileDescriptor();
+ }
+
+ /**
+ * Returns the byte offset where this asset entry's data starts.
+ */
+ public long getStartOffset() {
+ return mStartOffset;
+ }
+
+ /**
+ * Returns any additional details that can be used to interpret the
+ * underlying file descriptor. May be null.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Returns the total number of bytes of this asset entry's data. May be
+ * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
+ * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
+ * this will use {@link ParcelFileDescriptor#getStatSize()
+ * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
+ * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
+ * not be determined.
+ *
+ * @see #getDeclaredLength()
+ */
+ public long getLength() {
+ if (mLength >= 0) {
+ return mLength;
+ }
+ long len = mFd.getStatSize();
+ return len >= 0 ? len : UNKNOWN_LENGTH;
+ }
+
+ /**
+ * Return the actual number of bytes that were declared when the
+ * AssetFileDescriptor was constructed. Will be
+ * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
+ * should be read to the end of the file.
+ *
+ * @see #getDeclaredLength()
+ */
+ public long getDeclaredLength() {
+ return mLength;
+ }
+
+ /**
+ * Convenience for calling <code>getParcelFileDescriptor().close()</code>.
+ */
+ @Override
+ public void close() throws IOException {
+ mFd.close();
+ }
+
+ /**
+ * Create and return a new auto-close input stream for this asset. This
+ * will either return a full asset {@link AutoCloseInputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
+ * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileInputStream createInputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
+ }
+ return new AutoCloseInputStream(this);
+ }
+
+ /**
+ * Create and return a new auto-close output stream for this asset. This
+ * will either return a full asset {@link AutoCloseOutputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
+ * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileOutputStream createOutputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
+ }
+ return new AutoCloseOutputStream(this);
+ }
+
+ @Override
+ public String toString() {
+ return "{AssetFileDescriptor: " + mFd
+ + " start=" + mStartOffset + " len=" + mLength + "}";
+ }
+
+ /**
+ * An InputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescriptor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseInputStream
+ extends ParcelFileDescriptor.AutoCloseInputStream {
+ private long mRemaining;
+
+ public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ super.skip(fd.getStartOffset());
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRemaining >= 0
+ ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
+ : super.available();
+ }
+
+ @Override
+ public int read() throws IOException {
+ byte[] buffer = new byte[1];
+ int result = read(buffer, 0, 1);
+ return result == -1 ? -1 : buffer[0] & 0xff;
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, offset, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return read(buffer, 0, buffer.length);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = mRemaining;
+ long res = super.skip(count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.skip(count);
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ if (mRemaining >= 0) {
+ return false;
+ }
+ return super.markSupported();
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.reset();
+ }
+ }
+
+ /**
+ * An OutputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescriptor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseOutputStream
+ extends ParcelFileDescriptor.AutoCloseOutputStream {
+ private long mRemaining;
+
+ public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
+ throw new IOException("Unable to seek");
+ }
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer, offset, count);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer, offset, count);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ super.write(oneByte);
+ mRemaining--;
+ return;
+ }
+
+ super.write(oneByte);
+ }
+ }
+
+ /* Parcelable interface */
+ @Override
+ public int describeContents() {
+ return mFd.describeContents();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ mFd.writeToParcel(out, flags);
+ out.writeLong(mStartOffset);
+ out.writeLong(mLength);
+ if (mExtras != null) {
+ out.writeInt(1);
+ out.writeBundle(mExtras);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ AssetFileDescriptor(Parcel src) {
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
+ mStartOffset = src.readLong();
+ mLength = src.readLong();
+ if (src.readInt() != 0) {
+ mExtras = src.readBundle();
+ } else {
+ mExtras = null;
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<AssetFileDescriptor> CREATOR
+ = new Parcelable.Creator<AssetFileDescriptor>() {
+ public AssetFileDescriptor createFromParcel(Parcel in) {
+ return new AssetFileDescriptor(in);
+ }
+ public AssetFileDescriptor[] newArray(int size) {
+ return new AssetFileDescriptor[size];
+ }
+ };
+
+}
diff --git a/android/content/res/AssetManager.java b/android/content/res/AssetManager.java
new file mode 100644
index 0000000..24c6a5a
--- /dev/null
+++ b/android/content/res/AssetManager.java
@@ -0,0 +1,1634 @@
+/*
+ * Copyright (C) 2006 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.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.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration.NativeConfig;
+import android.content.res.loader.ResourcesLoader;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayConfig;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.Reference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Provides access to an application's raw asset files; see {@link Resources}
+ * for the way most applications will want to retrieve their resource data.
+ * This class presents a lower-level API that allows you to open and read raw
+ * files that have been bundled with the application as a simple stream of
+ * bytes.
+ */
+public final class AssetManager implements AutoCloseable {
+ 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.
+ @UnsupportedAppUsage
+ @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
+ * data will be accessed.
+ */
+ public static final int ACCESS_UNKNOWN = 0;
+ /**
+ * Mode for {@link #open(String, int)}: Read chunks, and seek forward and
+ * backward.
+ */
+ public static final int ACCESS_RANDOM = 1;
+ /**
+ * Mode for {@link #open(String, int)}: Read sequentially, with an
+ * occasional forward seek.
+ */
+ public static final int ACCESS_STREAMING = 2;
+ /**
+ * Mode for {@link #open(String, int)}: Attempt to load contents into
+ * memory, for fast small reads.
+ */
+ public static final int ACCESS_BUFFER = 3;
+
+ @GuardedBy("this") private final TypedValue mValue = new TypedValue();
+ @GuardedBy("this") private final long[] mOffsets = new long[2];
+
+ // Pointer to native implementation, stuffed inside a long.
+ @UnsupportedAppUsage
+ @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;
+
+ private ResourcesLoader[] mLoaders;
+
+ /**
+ * 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<>();
+ private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
+
+ public Builder addApkAssets(ApkAssets apkAssets) {
+ mUserApkAssets.add(apkAssets);
+ return this;
+ }
+
+ public Builder addLoader(ResourcesLoader loader) {
+ mLoaders.add(loader);
+ return this;
+ }
+
+ public AssetManager build() {
+ // Retrieving the system ApkAssets forces their creation as well.
+ final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
+
+ // Filter ApkAssets so that assets provided by multiple loaders are only included once
+ // in the AssetManager assets. The last appearance of the ApkAssets dictates its load
+ // order.
+ final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
+ final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
+ for (int i = mLoaders.size() - 1; i >= 0; i--) {
+ final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
+ for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
+ final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
+ if (uniqueLoaderApkAssets.add(apkAssets)) {
+ loaderApkAssets.add(0, apkAssets);
+ }
+ }
+ }
+
+ final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
+ + loaderApkAssets.size();
+ final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
+
+ System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);
+
+ // Append user ApkAssets after system ApkAssets.
+ for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
+ apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
+ }
+
+ // Append ApkAssets provided by loaders to the end.
+ for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
+ apkAssets[i + systemApkAssets.length + mUserApkAssets.size()] =
+ loaderApkAssets.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*/);
+ assetManager.mLoaders = mLoaders.isEmpty() ? null
+ : mLoaders.toArray(new ResourcesLoader[0]);
+
+ return assetManager;
+ }
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage
+ public AssetManager() {
+ final ApkAssets[] assets;
+ synchronized (sSync) {
+ createSystemAssetsInZygoteLocked(false, FRAMEWORK_APK_PATH);
+ assets = sSystemApkAssets;
+ }
+
+ mObject = nativeCreate();
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(hashCode());
+ }
+
+ // Always set the framework resources.
+ setApkAssets(assets, false /*invalidateCaches*/);
+ }
+
+ /**
+ * 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) {
+ mNumRefs = 0;
+ incRefsLocked(hashCode());
+ }
+ }
+
+ /**
+ * This must be called from Zygote so that system assets are shared by all applications.
+ * @hide
+ */
+ @GuardedBy("sSync")
+ @VisibleForTesting
+ public static void createSystemAssetsInZygoteLocked(boolean reinitialize,
+ String frameworkPath) {
+ if (sSystem != null && !reinitialize) {
+ return;
+ }
+
+ try {
+ final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
+ apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM));
+
+ final String[] systemIdmapPaths =
+ OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote();
+ for (String idmapPath : systemIdmapPaths) {
+ apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM));
+ }
+
+ sSystemApkAssetsSet = new ArraySet<>(apkAssets);
+ sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
+ if (sSystem == null) {
+ sSystem = new AssetManager(true /*sentinel*/);
+ }
+ sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create system AssetManager", e);
+ }
+ }
+
+ /**
+ * Return a global shared asset manager that provides access to only
+ * system assets (no application assets).
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static AssetManager getSystem() {
+ synchronized (sSync) {
+ createSystemAssetsInZygoteLocked(false, FRAMEWORK_APK_PATH);
+ return sSystem;
+ }
+ }
+
+ /**
+ * Close this asset manager.
+ */
+ @Override
+ public void close() {
+ 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) {
+ Objects.requireNonNull(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);
+ }
+ }
+ }
+
+ /**
+ * Changes the {@link ResourcesLoader ResourcesLoaders} used in this AssetManager.
+ * @hide
+ */
+ void setLoaders(@NonNull List<ResourcesLoader> newLoaders) {
+ Objects.requireNonNull(newLoaders, "newLoaders");
+
+ final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
+ for (int i = 0; i < mApkAssets.length; i++) {
+ // Filter out the previous loader apk assets.
+ if (!mApkAssets[i].isForLoader()) {
+ apkAssets.add(mApkAssets[i]);
+ }
+ }
+
+ if (!newLoaders.isEmpty()) {
+ // Filter so that assets provided by multiple loaders are only included once
+ // in the final assets list. The last appearance of the ApkAssets dictates its load
+ // order.
+ final int loaderStartIndex = apkAssets.size();
+ final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
+ for (int i = newLoaders.size() - 1; i >= 0; i--) {
+ final List<ApkAssets> currentLoaderApkAssets = newLoaders.get(i).getApkAssets();
+ for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
+ final ApkAssets loaderApkAssets = currentLoaderApkAssets.get(j);
+ if (uniqueLoaderApkAssets.add(loaderApkAssets)) {
+ apkAssets.add(loaderStartIndex, loaderApkAssets);
+ }
+ }
+ }
+ }
+
+ mLoaders = newLoaders.toArray(new ResourcesLoader[0]);
+ setApkAssets(apkAssets.toArray(new ApkAssets[0]), true /* invalidate_caches */);
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage
+ public @NonNull ApkAssets[] getApkAssets() {
+ synchronized (this) {
+ if (mOpen) {
+ return mApkAssets;
+ }
+ }
+ return sEmptyApkAssets;
+ }
+
+ /** @hide */
+ @TestApi
+ public @NonNull String[] getApkPaths() {
+ synchronized (this) {
+ if (mOpen) {
+ String[] paths = new String[mApkAssets.length];
+ final int count = mApkAssets.length;
+ for (int i = 0; i < count; i++) {
+ paths[i] = mApkAssets[i].getAssetPath();
+ }
+ return paths;
+ }
+ }
+ return new String[0];
+ }
+
+ /**
+ * 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) {
+ Objects.requireNonNull(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
+ @UnsupportedAppUsage
+ public int addAssetPath(String path) {
+ return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public int addAssetPathAsSharedLibrary(String path) {
+ return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/);
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public int addOverlayPath(String path) {
+ return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
+ }
+
+ private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
+ Objects.requireNonNull(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, 0 /* flags */);
+ } else {
+ assets = ApkAssets.loadFromPath(path,
+ appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
+ }
+ } catch (IOException e) {
+ return 0;
+ }
+
+ mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
+ mApkAssets[count] = assets;
+ nativeSetApkAssets(mObject, mApkAssets, true);
+ invalidateCachesLocked(-1);
+ return count + 1;
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ public List<ResourcesLoader> getLoaders() {
+ return mLoaders == null ? Collections.emptyList() : Arrays.asList(mLoaders);
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage
+ boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
+ boolean resolveRefs) {
+ Objects.requireNonNull(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) {
+ if ((outValue.string = getPooledStringForCookie(cookie, outValue.data)) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Retrieves the string value associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier to load
+ * @return the string value, or {@code null}
+ */
+ @UnsupportedAppUsage
+ @Nullable CharSequence getResourceText(@StringRes int resId) {
+ synchronized (this) {
+ final TypedValue outValue = mValue;
+ if (getResourceValue(resId, 0, outValue, true)) {
+ return outValue.coerceToString();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves the string value associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier to load
+ * @param bagEntryId the index into the bag to load
+ * @return the string value, or {@code null}
+ */
+ @UnsupportedAppUsage
+ @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+ synchronized (this) {
+ ensureValidLocked();
+ final TypedValue outValue = mValue;
+ final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue);
+ if (cookie <= 0) {
+ return null;
+ }
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ return getPooledStringForCookie(cookie, outValue.data);
+ }
+ return outValue.coerceToString();
+ }
+ }
+
+ int getResourceArraySize(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceArraySize(mObject, resId);
+ }
+ }
+
+ /**
+ * Populates `outData` with array elements of `resId`. `outData` is normally
+ * used with
+ * {@link TypedArray}.
+ *
+ * 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
+ */
+ int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) {
+ Objects.requireNonNull(outData, "outData");
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceArray(mObject, resId, outData);
+ }
+ }
+
+ /**
+ * Retrieves the string array associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier of the string array
+ * @return the string array, or {@code null}
+ */
+ @Nullable String[] getResourceStringArray(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceStringArray(mObject, resId);
+ }
+ }
+
+ /**
+ * Retrieve the text array associated with a particular resource
+ * identifier.
+ *
+ * @param resId the resource id of the string array
+ */
+ @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId);
+ if (rawInfoArray == null) {
+ return null;
+ }
+
+ final int rawInfoArrayLen = rawInfoArray.length;
+ final int infoArrayLen = rawInfoArrayLen / 2;
+ final CharSequence[] retArray = new CharSequence[infoArrayLen];
+ for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
+ int cookie = rawInfoArray[i];
+ int index = rawInfoArray[i + 1];
+ retArray[j] = (index >= 0 && cookie > 0)
+ ? getPooledStringForCookie(cookie, 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
+ * attributes against the specified theme.
+ *
+ * @param theme the native pointer of the theme
+ * @param resId the resource identifier to load
+ * @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 getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
+ boolean resolveRefs) {
+ Objects.requireNonNull(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) {
+ if ((outValue.string = getPooledStringForCookie(cookie, outValue.data)) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ void dumpTheme(long theme, int priority, String tag, String prefix) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeThemeDump(mObject, theme, priority, tag, prefix);
+ }
+ }
+
+ @UnsupportedAppUsage
+ @Nullable String getResourceName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceName(mObject, resId);
+ }
+ }
+
+ @UnsupportedAppUsage
+ @Nullable String getResourcePackageName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourcePackageName(mObject, resId);
+ }
+ }
+
+ @UnsupportedAppUsage
+ @Nullable String getResourceTypeName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceTypeName(mObject, resId);
+ }
+ }
+
+ @UnsupportedAppUsage
+ @Nullable String getResourceEntryName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceEntryName(mObject, resId);
+ }
+ }
+
+ @UnsupportedAppUsage
+ @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);
+ }
+ }
+
+ /**
+ * Enable resource resolution logging to track the steps taken to resolve the last resource
+ * entry retrieved. Stores the configuration and package names for each step.
+ *
+ * Default disabled.
+ *
+ * @param enabled Boolean indicating whether to enable or disable logging.
+ *
+ * @hide
+ */
+ @TestApi
+ public void setResourceResolutionLoggingEnabled(boolean enabled) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetResourceResolutionLoggingEnabled(mObject, enabled);
+ }
+ }
+
+ /**
+ * Retrieve the last resource resolution path logged.
+ *
+ * @return Formatted string containing last resource ID/name and steps taken to resolve final
+ * entry, including configuration and package names.
+ *
+ * @hide
+ */
+ @TestApi
+ public @Nullable String getLastResourceResolution() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLastResourceResolution(mObject);
+ }
+ }
+
+ /**
+ * Returns whether the {@code resources.arsc} of any loaded apk assets is allocated in RAM
+ * (not mmapped).
+ *
+ * @hide
+ */
+ public boolean containsAllocatedTable() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeContainsAllocatedTable(mObject);
+ }
+ }
+
+ @Nullable
+ 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.
+ *
+ * @see #open(String, int)
+ * @see #list
+ */
+ public @NonNull InputStream open(@NonNull String fileName) throws IOException {
+ return open(fileName, ACCESS_STREAMING);
+ }
+
+ /**
+ * Open an asset using an explicit access mode, returning an InputStream to
+ * read its contents. 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 accessMode Desired access mode for retrieving the data.
+ *
+ * @see #ACCESS_UNKNOWN
+ * @see #ACCESS_STREAMING
+ * @see #ACCESS_RANDOM
+ * @see #ACCESS_BUFFER
+ * @see #open(String)
+ * @see #list
+ */
+ public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
+ Objects.requireNonNull(fileName, "fileName");
+ synchronized (this) {
+ 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;
+ }
+ }
+
+ /**
+ * 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 {
+ Objects.requireNonNull(fileName, "fileName");
+ synchronized (this) {
+ 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]);
+ }
+ }
+
+ /**
+ * Return a String array of all the assets at the given path.
+ *
+ * @param path A relative path within the assets, i.e., "docs/home.html".
+ *
+ * @return String[] Array of strings, one for each asset. These file
+ * names are relative to 'path'. You can open the file by
+ * concatenating 'path' and a name in the returned string (via
+ * File) and passing that to open().
+ *
+ * @see #open
+ */
+ public @Nullable String[] list(@NonNull String path) throws IOException {
+ Objects.requireNonNull(path, "path");
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeList(mObject, path);
+ }
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage
+ public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException {
+ return openNonAsset(0, fileName, ACCESS_STREAMING);
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode)
+ throws IOException {
+ return openNonAsset(0, fileName, accessMode);
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName)
+ throws IOException {
+ return openNonAsset(cookie, fileName, ACCESS_STREAMING);
+ }
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage
+ public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ Objects.requireNonNull(fileName, "fileName");
+ synchronized (this) {
+ 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 {
+ Objects.requireNonNull(fileName, "fileName");
+ synchronized (this) {
+ 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]);
+ }
+ }
+
+ /**
+ * Retrieve a parser for a compiled XML file.
+ *
+ * @param fileName The name of the file to retrieve.
+ */
+ public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
+ throws IOException {
+ return openXmlResourceParser(0, fileName);
+ }
+
+ /**
+ * Retrieve a parser for a compiled XML file.
+ *
+ * @param cookie Identifier of the package to be opened.
+ * @param fileName The name of the file to retrieve.
+ */
+ 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 careful.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
+ }
+
+ /**
+ * Retrieve a non-asset as a compiled XML file. Not for use by applications.
+ *
+ * @param fileName The name of the file to retrieve.
+ * @hide
+ */
+ @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
+ return openXmlBlockAsset(0, fileName);
+ }
+
+ /**
+ * 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
+ */
+ @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
+ Objects.requireNonNull(fileName, "fileName");
+ synchronized (this) {
+ 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;
+ }
+ }
+
+ void xmlBlockGone(int id) {
+ synchronized (this) {
+ decRefsLocked(id);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+ @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
+ long outIndicesAddress) {
+ Objects.requireNonNull(inAttrs, "inAttrs");
+ synchronized (this) {
+ // 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);
+ }
+ }
+
+ int[] getAttributeResolutionStack(long themePtr, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, @StyleRes int xmlStyle) {
+ synchronized (this) {
+ return nativeAttributeResolutionStack(
+ mObject, themePtr, xmlStyle, defStyleAttr, defStyleRes);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+ @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
+ @NonNull int[] outIndices) {
+ Objects.requireNonNull(inAttrs, "inAttrs");
+ Objects.requireNonNull(outValues, "outValues");
+ Objects.requireNonNull(outIndices, "outIndices");
+ synchronized (this) {
+ // 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);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs,
+ @NonNull int[] outValues, @NonNull int[] outIndices) {
+ Objects.requireNonNull(parser, "parser");
+ Objects.requireNonNull(inAttrs, "inAttrs");
+ Objects.requireNonNull(outValues, "outValues");
+ Objects.requireNonNull(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);
+ }
+ }
+
+ @UnsupportedAppUsage
+ long createTheme() {
+ synchronized (this) {
+ ensureValidLocked();
+ long themePtr = nativeThemeCreate(mObject);
+ incRefsLocked(themePtr);
+ return themePtr;
+ }
+ }
+
+ void releaseTheme(long themePtr) {
+ synchronized (this) {
+ decRefsLocked(themePtr);
+ }
+ }
+
+ static long getThemeFreeFunction() {
+ return nativeGetThemeFreeFunction();
+ }
+
+ 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);
+ }
+ }
+
+ AssetManager rebaseTheme(long themePtr, @NonNull AssetManager newAssetManager,
+ @StyleRes int[] styleIds, @StyleRes boolean[] force, int count) {
+ // Exchange ownership of the theme with the new asset manager.
+ if (this != newAssetManager) {
+ synchronized (this) {
+ ensureValidLocked();
+ decRefsLocked(themePtr);
+ }
+ synchronized (newAssetManager) {
+ newAssetManager.ensureValidLocked();
+ newAssetManager.incRefsLocked(themePtr);
+ }
+ }
+
+ try {
+ synchronized (newAssetManager) {
+ newAssetManager.ensureValidLocked();
+ nativeThemeRebase(newAssetManager.mObject, themePtr, styleIds, force, count);
+ }
+ } finally {
+ Reference.reachabilityFence(newAssetManager);
+ }
+ return newAssetManager;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ void setThemeTo(long dstThemePtr, @NonNull AssetManager srcAssetManager, long srcThemePtr) {
+ synchronized (this) {
+ ensureValidLocked();
+ synchronized (srcAssetManager) {
+ srcAssetManager.ensureValidLocked();
+ nativeThemeCopy(mObject, dstThemePtr, srcAssetManager.mObject, srcThemePtr);
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ 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);
+ }
+ }
+ }
+
+ synchronized (this) {
+ if (mObject != 0) {
+ nativeDestroy(mObject);
+ mObject = 0;
+ }
+ }
+ }
+
+ /* 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
+ */
+ @UnsupportedAppUsage
+ public final int getAssetInt() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final long getNativeAsset() {
+ return mAssetNativePtr;
+ }
+
+ private AssetInputStream(long assetNativePtr) {
+ mAssetNativePtr = assetNativePtr;
+ mLength = nativeAssetGetLength(assetNativePtr);
+ }
+
+ @Override
+ public final int read() throws IOException {
+ ensureOpen();
+ return nativeAssetReadChar(mAssetNativePtr);
+ }
+
+ @Override
+ public final int read(@NonNull byte[] b) throws IOException {
+ ensureOpen();
+ Objects.requireNonNull(b, "b");
+ return nativeAssetRead(mAssetNativePtr, b, 0, b.length);
+ }
+
+ @Override
+ public final int read(@NonNull byte[] b, int off, int len) throws IOException {
+ ensureOpen();
+ Objects.requireNonNull(b, "b");
+ return nativeAssetRead(mAssetNativePtr, b, off, len);
+ }
+
+ @Override
+ public final long skip(long n) throws IOException {
+ ensureOpen();
+ long pos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+ if ((pos + n) > mLength) {
+ n = mLength - pos;
+ }
+ if (n > 0) {
+ nativeAssetSeek(mAssetNativePtr, n, 0);
+ }
+ return n;
+ }
+
+ @Override
+ public final int available() throws IOException {
+ ensureOpen();
+ final long len = nativeAssetGetRemainingLength(mAssetNativePtr);
+ return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len;
+ }
+
+ @Override
+ public final boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public final void mark(int readlimit) {
+ ensureOpen();
+ mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+ }
+
+ @Override
+ public final void reset() throws IOException {
+ ensureOpen();
+ nativeAssetSeek(mAssetNativePtr, mMarkPos, -1);
+ }
+
+ @Override
+ public final void close() throws IOException {
+ if (mAssetNativePtr != 0) {
+ nativeAssetDestroy(mAssetNativePtr);
+ mAssetNativePtr = 0;
+
+ synchronized (AssetManager.this) {
+ decRefsLocked(hashCode());
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ }
+
+ 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
+ */
+ @UnsupportedAppUsage
+ public boolean isUpToDate() {
+ synchronized (this) {
+ if (!mOpen) {
+ return false;
+ }
+
+ for (ApkAssets apkAssets : mApkAssets) {
+ if (!apkAssets.isUpToDate()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Get the locales that this asset manager contains data for.
+ *
+ * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
+ * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
+ * parsed using {@link Locale#forLanguageTag(String)}.
+ *
+ * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
+ * 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 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
+ * present in framework-res.apk or its overlays) will not be listed.
+ *
+ * For example, if the "system" assets support English, French, and German, and the additional
+ * assets support Cherokee and French, getLocales() would return
+ * [Cherokee, English, French, German], while getNonSystemLocales() would return
+ * [Cherokee, French].
+ * @hide
+ */
+ public String[] getNonSystemLocales() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLocales(mObject, true /*excludeSystem*/);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ Configuration[] getSizeConfigurations() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetSizeConfigurations(mObject);
+ }
+ }
+
+ /**
+ * Change the configuration used when retrieving resources. Not for use by
+ * applications.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ 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);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public SparseArray<String> getAssignedPackageIdentifiers() {
+ return getAssignedPackageIdentifiers(true, true);
+ }
+
+ /**
+ * @hide
+ */
+ public SparseArray<String> getAssignedPackageIdentifiers(boolean includeOverlays,
+ boolean includeLoaders) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetAssignedPackageIdentifiers(mObject, includeOverlays, includeLoaders);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @GuardedBy("this")
+ public @Nullable Map<String, String> getOverlayableMap(String packageName) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetOverlayableMap(mObject, packageName);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @GuardedBy("this")
+ public @Nullable String getOverlayablesToString(String packageName) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetOverlayablesToString(mObject, packageName);
+ }
+ }
+
+ @GuardedBy("this")
+ private void incRefsLocked(long id) {
+ if (DEBUG_REFS) {
+ if (mRefStacks == null) {
+ mRefStacks = new HashMap<>();
+ }
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ mRefStacks.put(id, ex);
+ }
+ mNumRefs++;
+ }
+
+ @GuardedBy("this")
+ private void decRefsLocked(long id) {
+ if (DEBUG_REFS && mRefStacks != null) {
+ mRefStacks.remove(id);
+ }
+ mNumRefs--;
+ 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, boolean includeOverlays, boolean includeLoaders);
+
+ // File native methods.
+ private static native boolean nativeContainsAllocatedTable(long ptr);
+ 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);
+ private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
+ @NonNull FileDescriptor fileDescriptor);
+
+ // 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);
+ private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled);
+ private static native @Nullable String nativeGetLastResourceResolution(long ptr);
+
+ // Style attribute retrieval native methods.
+ private static native int[] nativeAttributeResolutionStack(long ptr, long themePtr,
+ @StyleRes int xmlStyleRes, @AttrRes int defStyleAttr, @StyleRes int defStyleRes);
+ 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 long nativeGetThemeFreeFunction();
+ private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
+ boolean force);
+ private static native void nativeThemeRebase(long ptr, long themePtr, @NonNull int[] styleIds,
+ @NonNull boolean[] force, int styleSize);
+ private static native void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr,
+ long srcAssetManagerPtr, long srcThemePtr);
+ 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 @Nullable Map nativeGetOverlayableMap(long ptr,
+ @NonNull String packageName);
+ private static native @Nullable String nativeGetOverlayablesToString(long ptr,
+ @NonNull String packageName);
+
+ // Global debug native methods.
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static native int getGlobalAssetCount();
+
+ /**
+ * @hide
+ */
+ public static native String getAssetAllocations();
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static native int getGlobalAssetManagerCount();
+}
diff --git a/android/content/res/AssetManager_Delegate.java b/android/content/res/AssetManager_Delegate.java
new file mode 100644
index 0000000..c27df09
--- /dev/null
+++ b/android/content/res/AssetManager_Delegate.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.SparseArray;
+
+/**
+ * Delegate used to provide implementation of a select few native methods of {@link AssetManager}
+ * <p/>
+ * Through the layoutlib_create tool, the original native methods of AssetManager have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class AssetManager_Delegate {
+
+ // ---- delegate manager ----
+
+ private static final DelegateManager<AssetManager_Delegate> sManager =
+ new DelegateManager<>(AssetManager_Delegate.class);
+
+ // ---- delegate methods. ----
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeCreate() {
+ AssetManager_Delegate delegate = new AssetManager_Delegate();
+ return sManager.addNewDelegate(delegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestroy(long ptr) {
+ sManager.removeJavaReferenceFor(ptr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeThemeCreate(long ptr) {
+ return Resources_Theme_Delegate.getDelegateManager()
+ .addNewDelegate(new Resources_Theme_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeThemeDestroy(long theme) {
+ Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager) {
+ return new SparseArray<>();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager,
+ boolean includeOverlays, boolean includeLoaders) {
+ return new SparseArray<>();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() {
+ // AssetManager requires this not to be null
+ return new String[0];
+ }
+}
diff --git a/android/content/res/BridgeAssetManager.java b/android/content/res/BridgeAssetManager.java
new file mode 100644
index 0000000..c9b7095
--- /dev/null
+++ b/android/content/res/BridgeAssetManager.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 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 android.annotation.Nullable;
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.layoutlib.bridge.Bridge;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class BridgeAssetManager extends AssetManager {
+ @Nullable private AssetRepository mAssetRepository;
+
+ /**
+ * This initializes the static field {@link AssetManager#sSystem} which is used
+ * by methods who get a global asset manager using {@link AssetManager#getSystem()}.
+ * <p/>
+ * They will end up using our bridge asset manager.
+ * <p/>
+ * {@link Bridge} calls this method after setting up a new bridge.
+ */
+ public static AssetManager initSystem() {
+ if (!(AssetManager.sSystem instanceof BridgeAssetManager)) {
+ // Note that AssetManager() creates a system AssetManager and we override it
+ // with our BridgeAssetManager.
+ AssetManager.sSystem = new BridgeAssetManager();
+ }
+ return AssetManager.sSystem;
+ }
+
+ /**
+ * Clears the static {@link AssetManager#sSystem} to make sure we don't leave objects
+ * around that would prevent us from unloading the library.
+ */
+ public static void clearSystem() {
+ AssetManager.sSystem = null;
+ }
+
+ public void setAssetRepository(@NonNull AssetRepository assetRepository) {
+ mAssetRepository = assetRepository;
+ }
+
+ /**
+ * Clears the AssetRepository reference.
+ */
+ public void releaseAssetRepository() {
+ mAssetRepository = null;
+ }
+
+ @NonNull
+ public AssetRepository getAssetRepository() {
+ if (mAssetRepository == null) {
+ throw new IllegalStateException("Asset repository is not set");
+ }
+ return mAssetRepository;
+ }
+
+ @Override
+ public InputStream open(String fileName, int accessMode) throws IOException {
+ return getAssetRepository().openAsset(fileName, accessMode);
+ }
+
+ @Override
+ public InputStream openNonAsset(int cookie, String fileName, int accessMode)
+ throws IOException {
+ return getAssetRepository().openNonAsset(cookie, fileName, accessMode);
+ }
+
+ public BridgeAssetManager() {
+ }
+}
diff --git a/android/content/res/BridgeTypedArray.java b/android/content/res/BridgeTypedArray.java
new file mode 100644
index 0000000..eb603f0
--- /dev/null
+++ b/android/content/res/BridgeTypedArray.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2008 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 com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.AttrResourceValue;
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.rendering.api.TextResourceValue;
+import com.android.ide.common.resources.ValueXmlHelper;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+import com.android.resources.ResourceUrl;
+
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
+import android.graphics.Typeface_Accessor;
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.LayoutInflater_Delegate;
+import android.view.ViewGroup.LayoutParams;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+import static android.text.Html.FROM_HTML_MODE_COMPACT;
+import static android.util.TypedValue.TYPE_ATTRIBUTE;
+import static android.util.TypedValue.TYPE_DIMENSION;
+import static android.util.TypedValue.TYPE_FLOAT;
+import static android.util.TypedValue.TYPE_INT_BOOLEAN;
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB4;
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
+import static android.util.TypedValue.TYPE_INT_COLOR_RGB4;
+import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
+import static android.util.TypedValue.TYPE_INT_DEC;
+import static android.util.TypedValue.TYPE_INT_HEX;
+import static android.util.TypedValue.TYPE_NULL;
+import static android.util.TypedValue.TYPE_REFERENCE;
+import static android.util.TypedValue.TYPE_STRING;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED;
+
+/**
+ * Custom implementation of TypedArray to handle non compiled resources.
+ */
+public final class BridgeTypedArray extends TypedArray {
+ private static final String MATCH_PARENT_INT_STRING = String.valueOf(LayoutParams.MATCH_PARENT);
+ private static final String WRAP_CONTENT_INT_STRING = String.valueOf(LayoutParams.WRAP_CONTENT);
+
+ private final Resources mBridgeResources;
+ private final BridgeContext mContext;
+
+ private final int[] mResourceId;
+ private final ResourceValue[] mResourceData;
+ private final String[] mNames;
+ private final ResourceNamespace[] mNamespaces;
+
+ // Contains ids that are @empty. We still store null in mResourceData for that index, since we
+ // want to save on the check against empty, each time a resource value is requested.
+ @Nullable
+ private int[] mEmptyIds;
+
+ public BridgeTypedArray(Resources resources, BridgeContext context, int len) {
+ super(resources);
+ mBridgeResources = resources;
+ mContext = context;
+ mResourceId = new int[len];
+ mResourceData = new ResourceValue[len];
+ mNames = new String[len];
+ mNamespaces = new ResourceNamespace[len];
+ }
+
+ /**
+ * A bridge-specific method that sets a value in the type array
+ * @param index the index of the value in the TypedArray
+ * @param name the name of the attribute
+ * @param namespace namespace of the attribute
+ * @param resourceId the reference id of this resource
+ * @param value the value of the attribute
+ */
+ public void bridgeSetValue(int index, String name, ResourceNamespace namespace, int resourceId,
+ ResourceValue value) {
+ mResourceId[index] = resourceId;
+ mResourceData[index] = value;
+ mNames[index] = name;
+ mNamespaces[index] = namespace;
+ }
+
+ /**
+ * Seals the array after all calls to
+ * {@link #bridgeSetValue(int, String, ResourceNamespace, int, ResourceValue)} have been done.
+ * <p/>This allows to compute the list of non default values, permitting
+ * {@link #getIndexCount()} to return the proper value.
+ */
+ public void sealArray() {
+ // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt
+ // first count the array size
+ int count = 0;
+ ArrayList<Integer> emptyIds = null;
+ for (int i = 0; i < mResourceData.length; i++) {
+ ResourceValue data = mResourceData[i];
+ if (data != null) {
+ String dataValue = data.getValue();
+ if (REFERENCE_NULL.equals(dataValue) || REFERENCE_UNDEFINED.equals(dataValue)) {
+ mResourceData[i] = null;
+ } else if (REFERENCE_EMPTY.equals(dataValue)) {
+ mResourceData[i] = null;
+ if (emptyIds == null) {
+ emptyIds = new ArrayList<>(4);
+ }
+ emptyIds.add(i);
+ } else {
+ count++;
+ }
+ }
+ }
+
+ if (emptyIds != null) {
+ mEmptyIds = new int[emptyIds.size()];
+ for (int i = 0; i < emptyIds.size(); i++) {
+ mEmptyIds[i] = emptyIds.get(i);
+ }
+ }
+
+ // allocate the table with an extra to store the size
+ mIndices = new int[count+1];
+ mIndices[0] = count;
+
+ // fill the array with the indices.
+ int index = 1;
+ for (int i = 0 ; i < mResourceData.length ; i++) {
+ if (mResourceData[i] != null) {
+ mIndices[index++] = i;
+ }
+ }
+ }
+
+ /**
+ * Set the theme to be used for inflating drawables.
+ */
+ public void setTheme(Theme theme) {
+ mTheme = theme;
+ }
+
+ /**
+ * Return the number of values in this array.
+ */
+ @Override
+ public int length() {
+ return mResourceData.length;
+ }
+
+ /**
+ * Return the Resources object this array was loaded from.
+ */
+ @Override
+ public Resources getResources() {
+ return mBridgeResources;
+ }
+
+ /**
+ * Retrieve the styled string value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence holding string data. May be styled. Returns
+ * null if the attribute is not defined.
+ */
+ @Override
+ public CharSequence getText(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+ // As unfortunate as it is, it's possible to use enums with all attribute formats,
+ // not just integers/enums. So, we need to search the enums always. In case
+ // enums are used, the returned value is an integer.
+ Integer v = resolveEnumAttribute(index);
+ if (v != null) {
+ return String.valueOf((int) v);
+ }
+ ResourceValue resourceValue = mResourceData[index];
+ String value = resourceValue.getValue();
+ if (resourceValue instanceof TextResourceValue) {
+ String rawValue =
+ ValueXmlHelper.unescapeResourceString(resourceValue.getRawXmlValue(),
+ true, false);
+ if (rawValue != null && !rawValue.equals(value)) {
+ return Html.fromHtml(rawValue, FROM_HTML_MODE_COMPACT);
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Retrieve the string value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return String holding string data. Any styling information is
+ * removed. Returns null if the attribute is not defined.
+ */
+ @Override
+ public String getString(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+ // As unfortunate as it is, it's possible to use enums with all attribute formats,
+ // not just integers/enums. So, we need to search the enums always. In case
+ // enums are used, the returned value is an integer.
+ Integer v = resolveEnumAttribute(index);
+ return v == null ? mResourceData[index].getValue() : String.valueOf((int) v);
+ }
+
+ /**
+ * Retrieve the boolean value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined.
+ *
+ * @return Attribute boolean value, or defValue if not defined.
+ */
+ @Override
+ public boolean getBoolean(int index, boolean defValue) {
+ String s = getString(index);
+ return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue);
+
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined.
+ *
+ * @return Attribute int value, or defValue if not defined.
+ */
+ @Override
+ public int getInt(int index, int defValue) {
+ String s = getString(index);
+ try {
+ return convertValueToInt(s, defValue);
+ } catch (NumberFormatException e) {
+ Bridge.getLog().warning(ILayoutLog.TAG_RESOURCES_FORMAT,
+ String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer",
+ s, mNames[index]),
+ null, null);
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieve the float value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Attribute float value, or defValue if not defined..
+ */
+ @Override
+ public float getFloat(int index, float defValue) {
+ String s = getString(index);
+ try {
+ if (s != null) {
+ return Float.parseFloat(s);
+ }
+ } catch (NumberFormatException e) {
+ Bridge.getLog().warning(ILayoutLog.TAG_RESOURCES_FORMAT,
+ String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.",
+ s, mNames[index]),
+ null, null);
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieve the color value for the attribute at <var>index</var>. If
+ * the attribute references a color resource holding a complex
+ * {@link android.content.res.ColorStateList}, then the default color from
+ * the set is returned.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute color value, or defValue if not defined.
+ */
+ @Override
+ public int getColor(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ ColorStateList colorStateList = ResourceHelper.getColorStateList(
+ mResourceData[index], mContext, mTheme);
+ if (colorStateList != null) {
+ return colorStateList.getDefaultColor();
+ }
+
+ return defValue;
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+
+ return ResourceHelper.getColorStateList(mResourceData[index], mContext, mTheme);
+ }
+
+ @Override
+ public ComplexColor getComplexColor(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+
+ return ResourceHelper.getComplexColor(mResourceData[index], mContext, mTheme);
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute integer value, or defValue if not defined.
+ */
+ @Override
+ public int getInteger(int index, int defValue) {
+ return getInt(index, defValue);
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * conversions are based on the current {@link DisplayMetrics}
+ * associated with the resources this {@link TypedArray} object
+ * came from.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric, or defValue if not defined.
+ *
+ * @see #getDimensionPixelOffset
+ * @see #getDimensionPixelSize
+ */
+ @Override
+ public float getDimension(int index, float defValue) {
+ String s = getString(index);
+ if (s == null) {
+ return defValue;
+ }
+ // Check if the value is a magic constant that doesn't require a unit.
+ if (MATCH_PARENT_INT_STRING.equals(s)) {
+ return LayoutParams.MATCH_PARENT;
+ }
+ if (WRAP_CONTENT_INT_STRING.equals(s)) {
+ return LayoutParams.WRAP_CONTENT;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
+ return mValue.getDimension(mBridgeResources.getDisplayMetrics());
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as an offset in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for you. An offset conversion involves simply
+ * truncating the base value to an integer.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelSize
+ */
+ @Override
+ public int getDimensionPixelOffset(int index, int defValue) {
+ return (int) getDimension(index, defValue);
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as a size in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for use as a size. A size conversion involves
+ * rounding the base value, and ensuring that a non-zero base value
+ * is at least one pixel in size.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelOffset
+ */
+ @Override
+ public int getDimensionPixelSize(int index, int defValue) {
+ String s = getString(index);
+ if (s == null) {
+ return defValue;
+ }
+
+ if (MATCH_PARENT_INT_STRING.equals(s)) {
+ return LayoutParams.MATCH_PARENT;
+ }
+ if (WRAP_CONTENT_INT_STRING.equals(s)) {
+ return LayoutParams.WRAP_CONTENT;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
+ float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
+
+ final int res = (int) (f + 0.5f);
+ if (res != 0) return res;
+ if (f == 0) return 0;
+ if (f > 0) return 1;
+ }
+
+ // looks like we were unable to resolve the dimension value
+ Bridge.getLog().warning(ILayoutLog.TAG_RESOURCES_FORMAT,
+ String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.", s, mNames[index]),
+ null, null);
+
+ return defValue;
+ }
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param name Textual name of attribute for error reporting.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ */
+ @Override
+ public int getLayoutDimension(int index, String name) {
+ String s = getString(index);
+ if (s != null) {
+ // Check if the value is a magic constant that doesn't require a unit.
+ if (MATCH_PARENT_INT_STRING.equals(s)) {
+ return LayoutParams.MATCH_PARENT;
+ }
+ if (WRAP_CONTENT_INT_STRING.equals(s)) {
+ return LayoutParams.WRAP_CONTENT;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
+ float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
+
+ final int res = (int) (f + 0.5f);
+ if (res != 0) return res;
+ if (f == 0) return 0;
+ if (f > 0) return 1;
+ }
+ }
+
+ if (LayoutInflater_Delegate.sIsInInclude) {
+ throw new RuntimeException("Layout Dimension '" + name + "' not found.");
+ }
+
+ Bridge.getLog().warning(ILayoutLog.TAG_RESOURCES_FORMAT,
+ "You must supply a " + name + " attribute.", null, null);
+
+ return 0;
+ }
+
+ @Override
+ public int getLayoutDimension(int index, int defValue) {
+ return getDimensionPixelSize(index, defValue);
+ }
+
+ /**
+ * Retrieve a fractional unit attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value, or defValue if not defined.
+ */
+ @Override
+ public float getFraction(int index, int base, int pbase, float defValue) {
+ String value = getString(index);
+ if (value == null) {
+ return defValue;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) {
+ return mValue.getFraction(base, pbase);
+ }
+
+ // looks like we were unable to resolve the fraction value
+ Bridge.getLog().warning(ILayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.",
+ value, mNames[index]),
+ null, null);
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the resource identifier for the attribute at
+ * <var>index</var>. Note that attribute resource as resolved when
+ * the overall {@link TypedArray} object is retrieved. As a
+ * result, this function will return the resource identifier of the
+ * final resource value that was found, <em>not</em> necessarily the
+ * original resource that was specified by the attribute.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute resource identifier, or defValue if not defined.
+ */
+ @Override
+ public int getResourceId(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ // get the Resource for this index
+ ResourceValue resValue = mResourceData[index];
+
+ // no data, return the default value.
+ if (resValue == null) {
+ return defValue;
+ }
+
+ // check if this is a style resource
+ if (resValue instanceof StyleResourceValue) {
+ // get the id that will represent this style.
+ return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
+ }
+
+ // If the attribute was a reference to a resource, and not a declaration of an id (@+id),
+ // then the xml attribute value was "resolved" which leads us to a ResourceValue with a
+ // valid type, name, namespace and a potentially null value.
+ if (!(resValue instanceof UnresolvedResourceValue)) {
+ return mContext.getResourceId(resValue.asReference(), defValue);
+ }
+
+ // else, try to get the value, and resolve it somehow.
+ String value = resValue.getValue();
+ if (value == null) {
+ return defValue;
+ }
+ value = value.trim();
+
+
+ // `resValue` failed to be resolved. We extract the interesting bits and get rid of this
+ // broken object. The namespace and resolver come from where the XML attribute was defined.
+ ResourceNamespace contextNamespace = resValue.getNamespace();
+ Resolver namespaceResolver = resValue.getNamespaceResolver();
+
+ if (value.startsWith("#")) {
+ // this looks like a color, do not try to parse it
+ return defValue;
+ }
+
+ if (Typeface_Accessor.isSystemFont(value)) {
+ // A system font family value, do not try to parse
+ return defValue;
+ }
+
+ // Handle the @id/<name>, @+id/<name> and @android:id/<name>
+ // We need to return the exact value that was compiled (from the various R classes),
+ // as these values can be reused internally with calls to findViewById().
+ // There's a trick with platform layouts that not use "android:" but their IDs are in
+ // fact in the android.R and com.android.internal.R classes.
+ // The field mPlatformFile will indicate that all IDs are to be looked up in the android R
+ // classes exclusively.
+
+ // if this is a reference to an id, find it.
+ ResourceUrl resourceUrl = ResourceUrl.parse(value);
+ if (resourceUrl != null) {
+ if (resourceUrl.type == ResourceType.ID) {
+ ResourceReference referencedId =
+ resourceUrl.resolve(contextNamespace, namespaceResolver);
+
+ // Look for the idName in project or android R class depending on isPlatform.
+ if (resourceUrl.isCreate()) {
+ int idValue;
+ if (referencedId.getNamespace() == ResourceNamespace.ANDROID) {
+ idValue = Bridge.getResourceId(ResourceType.ID, resourceUrl.name);
+ } else {
+ idValue = mContext.getLayoutlibCallback().getOrGenerateResourceId(referencedId);
+ }
+ return idValue;
+ }
+ // This calls the same method as in if(create), but doesn't create a dynamic id, if
+ // one is not found.
+ return mContext.getResourceId(referencedId, defValue);
+ }
+ else if (resourceUrl.type == ResourceType.AAPT) {
+ ResourceReference referencedId =
+ resourceUrl.resolve(contextNamespace, namespaceResolver);
+ return mContext.getLayoutlibCallback().getOrGenerateResourceId(referencedId);
+ }
+ }
+ // not a direct id valid reference. First check if it's an enum (this is a corner case
+ // for attributes that have a reference|enum type), then fallback to resolve
+ // as an ID without prefix.
+ Integer enumValue = resolveEnumAttribute(index);
+ if (enumValue != null) {
+ return enumValue;
+ }
+
+ return defValue;
+ }
+
+ @Override
+ public int getThemeAttributeId(int index, int defValue) {
+ // TODO: Get the right Theme Attribute ID to enable caching of the drawables.
+ return defValue;
+ }
+
+ /**
+ * Retrieve the Drawable for the attribute at <var>index</var>. This
+ * gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getDrawable Resources.getDrawable} of the owning
+ * Resources object to retrieve its Drawable.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Drawable for the attribute, or null if not defined.
+ */
+ @Override
+ @Nullable
+ public Drawable getDrawable(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+
+ ResourceValue value = mResourceData[index];
+ return ResourceHelper.getDrawable(value, mContext, mTheme);
+ }
+
+ /**
+ * Version of {@link #getDrawable(int)} that accepts an override density.
+ * @hide
+ */
+ @Override
+ @Nullable
+ public Drawable getDrawableForDensity(int index, int density) {
+ return getDrawable(index);
+ }
+
+ /**
+ * Retrieve the Typeface for the attribute at <var>index</var>.
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Typeface for the attribute, or null if not defined.
+ */
+ @Override
+ public Typeface getFont(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+
+ ResourceValue value = mResourceData[index];
+ return ResourceHelper.getFont(value, mContext, mTheme);
+ }
+
+ /**
+ * Retrieve the CharSequence[] for the attribute at <var>index</var>.
+ * This gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getTextArray Resources.getTextArray} of the owning
+ * Resources object to retrieve its String[].
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence[] for the attribute, or null if not defined.
+ */
+ @Override
+ public CharSequence[] getTextArray(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+ ResourceValue resVal = mResourceData[index];
+ if (resVal instanceof ArrayResourceValue) {
+ ArrayResourceValue array = (ArrayResourceValue) resVal;
+ int count = array.getElementCount();
+ return count >= 0 ?
+ Resources_Delegate.resolveValues(mBridgeResources, array) :
+ null;
+ }
+ int id = getResourceId(index, 0);
+ String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
+ assert false :
+ String.format("%1$s in %2$s%3$s is not a valid array resource.", resVal.getValue(),
+ mNames[index], resIdMessage);
+
+ return new CharSequence[0];
+ }
+
+ @Override
+ public int[] extractThemeAttrs() {
+ // The drawables are always inflated with a Theme and we don't care about caching. So,
+ // just return.
+ return null;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ // We don't care about caching. Any change in configuration is a fresh render. So,
+ // just return.
+ return 0;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param outValue TypedValue object in which to place the attribute's
+ * data.
+ *
+ * @return Returns true if the value was retrieved, else false.
+ */
+ @Override
+ public boolean getValue(int index, TypedValue outValue) {
+ // TODO: more switch cases for other types.
+ outValue.type = getType(index);
+ switch (outValue.type) {
+ case TYPE_NULL:
+ return false;
+ case TYPE_STRING:
+ outValue.string = getString(index);
+ return true;
+ case TYPE_REFERENCE:
+ outValue.resourceId = mResourceId[index];
+ return true;
+ case TYPE_INT_COLOR_ARGB4:
+ case TYPE_INT_COLOR_ARGB8:
+ case TYPE_INT_COLOR_RGB4:
+ case TYPE_INT_COLOR_RGB8:
+ ColorStateList colorStateList = getColorStateList(index);
+ if (colorStateList == null) {
+ return false;
+ }
+ outValue.data = colorStateList.getDefaultColor();
+ return true;
+ default:
+ // For back-compatibility, parse as float.
+ String s = getString(index);
+ return s != null &&
+ ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false);
+ }
+ }
+
+ @Override
+ public int getType(int index) {
+ String value = getString(index);
+ return getType(value);
+ }
+
+ /**
+ * Determines whether there is an attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return True if the attribute has a value, false otherwise.
+ */
+ @Override
+ public boolean hasValue(int index) {
+ return index >= 0 && index < mResourceData.length && mResourceData[index] != null;
+ }
+
+ @Override
+ public boolean hasValueOrEmpty(int index) {
+ return hasValue(index) || index >= 0 && index < mResourceData.length &&
+ mEmptyIds != null && Arrays.binarySearch(mEmptyIds, index) >= 0;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>
+ * and return a temporary object holding its data. This object is only
+ * valid until the next call on to {@link TypedArray}.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Returns a TypedValue object if the attribute is defined,
+ * containing its data; otherwise returns null. (You will not
+ * receive a TypedValue whose type is TYPE_NULL.)
+ */
+ @Override
+ public TypedValue peekValue(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (getValue(index, mValue)) {
+ return mValue;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a message about the parser state suitable for printing error messages.
+ */
+ @Override
+ public String getPositionDescription() {
+ return "<internal -- stub if needed>";
+ }
+
+ /**
+ * Give back a previously retrieved TypedArray, for later re-use.
+ */
+ @Override
+ public void recycle() {
+ // pass
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(mResourceData);
+ }
+
+ /**
+ * Searches for the string in the attributes (flag or enums) and returns the integer.
+ * If found, it will return an integer matching the value.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Attribute int value, or null if not defined.
+ */
+ private Integer resolveEnumAttribute(int index) {
+ // Get the map of attribute-constant -> IntegerValue
+ Map<String, Integer> map = null;
+ if (mNamespaces[index] == ResourceNamespace.ANDROID) {
+ map = Bridge.getEnumValues(mNames[index]);
+ } else {
+ // get the styleable matching the resolved name
+ RenderResources res = mContext.getRenderResources();
+ ResourceValue attr = res.getResolvedResource(
+ ResourceReference.attr(mNamespaces[index], mNames[index]));
+ if (attr instanceof AttrResourceValue) {
+ map = ((AttrResourceValue) attr).getAttributeValues();
+ }
+ }
+
+ if (map != null && !map.isEmpty()) {
+ // Accumulator to store the value of the 1+ constants.
+ int result = 0;
+ boolean found = false;
+
+ String value = mResourceData[index].getValue();
+ if (!value.isEmpty()) {
+ // Check if the value string is already representing an integer and return it if so.
+ // Resources coming from res.apk in an AAR may have flags and enums in integer form.
+ char c = value.charAt(0);
+ if (Character.isDigit(c) || c == '-' || c == '+') {
+ try {
+ return convertValueToInt(value, 0);
+ } catch (NumberFormatException e) {
+ // Ignore and continue.
+ }
+ }
+ // Split the value in case it is a mix of several flags.
+ String[] keywords = value.split("\\|");
+ for (String keyword : keywords) {
+ Integer i = map.get(keyword.trim());
+ if (i != null) {
+ result |= i;
+ found = true;
+ }
+ // TODO: We should act smartly and log a warning for incorrect keywords. However,
+ // this method is currently called even if the resourceValue is not an enum.
+ }
+ if (found) {
+ return result;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Copied from {@link XmlUtils#convertValueToInt(CharSequence, int)}, but adapted to account
+ * for aapt, and the fact that host Java VM's Integer.parseInt("XXXXXXXX", 16) cannot handle
+ * "XXXXXXXX" > 80000000.
+ */
+ private static int convertValueToInt(@Nullable String charSeq, int defValue) {
+ if (null == charSeq || charSeq.isEmpty())
+ return defValue;
+
+ int sign = 1;
+ int index = 0;
+ int len = charSeq.length();
+ int base = 10;
+
+ if ('-' == charSeq.charAt(0)) {
+ sign = -1;
+ index++;
+ }
+
+ if ('0' == charSeq.charAt(index)) {
+ // Quick check for a zero by itself
+ if (index == (len - 1))
+ return 0;
+
+ char c = charSeq.charAt(index + 1);
+
+ if ('x' == c || 'X' == c) {
+ index += 2;
+ base = 16;
+ } else {
+ index++;
+ // Leave the base as 10. aapt removes the preceding zero, and thus when framework
+ // sees the value, it only gets the decimal value.
+ }
+ } else if ('#' == charSeq.charAt(index)) {
+ return ResourceHelper.getColor(charSeq) * sign;
+ } else if ("true".equals(charSeq) || "TRUE".equals(charSeq)) {
+ return -1;
+ } else if ("false".equals(charSeq) || "FALSE".equals(charSeq)) {
+ return 0;
+ }
+
+ // Use Long, since we want to handle hex ints > 80000000.
+ return ((int)Long.parseLong(charSeq.substring(index), base)) * sign;
+ }
+
+ protected static int getType(@Nullable String value) {
+ if (value == null) {
+ return TYPE_NULL;
+ }
+ if (value.startsWith(PREFIX_RESOURCE_REF)) {
+ return TYPE_REFERENCE;
+ }
+ if (value.startsWith(PREFIX_THEME_REF)) {
+ return TYPE_ATTRIBUTE;
+ }
+ if (value.equals("true") || value.equals("false")) {
+ return TYPE_INT_BOOLEAN;
+ }
+ if (value.startsWith("0x") || value.startsWith("0X")) {
+ try {
+ // Check if it is a hex value.
+ Long.parseLong(value.substring(2), 16);
+ return TYPE_INT_HEX;
+ } catch (NumberFormatException e) {
+ return TYPE_STRING;
+ }
+ }
+ if (value.startsWith("#")) {
+ try {
+ // Check if it is a color.
+ ResourceHelper.getColor(value);
+ int length = value.length() - 1;
+ if (length == 3) { // rgb
+ return TYPE_INT_COLOR_RGB4;
+ }
+ if (length == 4) { // argb
+ return TYPE_INT_COLOR_ARGB4;
+ }
+ if (length == 6) { // rrggbb
+ return TYPE_INT_COLOR_RGB8;
+ }
+ if (length == 8) { // aarrggbb
+ return TYPE_INT_COLOR_ARGB8;
+ }
+ } catch (NumberFormatException e) {
+ return TYPE_STRING;
+ }
+ }
+ if (!Character.isDigit(value.charAt(value.length() - 1))) {
+ // Check if it is a dimension.
+ if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) {
+ return TYPE_DIMENSION;
+ } else {
+ return TYPE_STRING;
+ }
+ }
+ try {
+ // Check if it is an int.
+ convertValueToInt(value, 0);
+ return TYPE_INT_DEC;
+ } catch (NumberFormatException ignored) {
+ try {
+ // Check if it is a float.
+ Float.parseFloat(value);
+ return TYPE_FLOAT;
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ // TODO: handle fractions.
+ return TYPE_STRING;
+ }
+
+ static TypedArray obtain(Resources res, int len) {
+ return new BridgeTypedArray(res, null, len);
+ }
+}
diff --git a/android/content/res/ColorStateList.java b/android/content/res/ColorStateList.java
new file mode 100644
index 0000000..5b727cc
--- /dev/null
+++ b/android/content/res/ColorStateList.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2007 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.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.Resources.Theme;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.SparseArray;
+import android.util.StateSet;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.graphics.cam.Cam;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+
+/**
+ *
+ * Lets you map {@link android.view.View} state sets to colors.
+ * <p>
+ * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the
+ * "color" subdirectory directory of an application's resource directory. The XML file contains
+ * a single "selector" element with a number of "item" elements inside. For example:
+ * <pre>
+ * <selector xmlns:android="http://schemas.android.com/apk/res/android">
+ * <item android:state_focused="true"
+ * android:color="@color/sample_focused" />
+ * <item android:state_pressed="true"
+ * android:state_enabled="false"
+ * android:color="@color/sample_disabled_pressed" />
+ * <item android:state_enabled="false"
+ * android:color="@color/sample_disabled_not_pressed" />
+ * <item android:color="@color/sample_default" />
+ * </selector>
+ * </pre>
+ *
+ * This defines a set of state spec / color pairs where each state spec specifies a set of
+ * states that a view must either be in or not be in and the color specifies the color associated
+ * with that spec.
+ *
+ * <a name="StateSpec"></a>
+ * <h3>State specs</h3>
+ * <p>
+ * Each item defines a set of state spec and color pairs, where the state spec is a series of
+ * attributes set to either {@code true} or {@code false} to represent inclusion or exclusion. If
+ * an attribute is not specified for an item, it may be any value.
+ * <p>
+ * For example, the following item will be matched whenever the focused state is set; any other
+ * states may be set or unset:
+ * <pre>
+ * <item android:state_focused="true"
+ * android:color="@color/sample_focused" />
+ * </pre>
+ * <p>
+ * Typically, a color state list will reference framework-defined state attributes such as
+ * {@link android.R.attr#state_focused android:state_focused} or
+ * {@link android.R.attr#state_enabled android:state_enabled}; however, app-defined attributes may
+ * also be used.
+ * <p>
+ * <strong>Note:</strong> The list of state specs will be matched against in the order that they
+ * appear in the XML file. For this reason, more-specific items should be placed earlier in the
+ * file. An item with no state spec is considered to match any set of states and is generally
+ * useful as a final item to be used as a default.
+ * <p>
+ * If an item with no state spec is placed before other items, those items
+ * will be ignored.
+ *
+ * <a name="ItemAttributes"></a>
+ * <h3>Item attributes</h3>
+ * <p>
+ * Each item must define an {@link android.R.attr#color android:color} attribute, which may be
+ * an HTML-style hex color, a reference to a color resource, or -- in API 23 and above -- a theme
+ * attribute that resolves to a color.
+ * <p>
+ * Starting with API 23, items may optionally define an {@link android.R.attr#alpha android:alpha}
+ * attribute to modify the base color's opacity. This attribute takes a either floating-point value
+ * between 0 and 1 or a theme attribute that resolves as such. The item's overall color is
+ * calculated by multiplying by the base color's alpha channel by the {@code alpha} value. For
+ * example, the following item represents the theme's accent color at 50% opacity:
+ * <pre>
+ * <item android:state_enabled="false"
+ * android:color="?android:attr/colorAccent"
+ * android:alpha="0.5" />
+ * </pre>
+ * <p>
+ * Starting with API 31, items may optionally define an {@link android.R.attr#lStar android:lStar}
+ * attribute to modify the base color's perceptual luminance. This attribute takes either a
+ * floating-point value between 0 and 100 or a theme attribute that resolves as such. The item's
+ * overall color is calculated by converting the base color to an accessibility friendly color space
+ * and setting its L* to the value specified on the {@code lStar} attribute. For
+ * example, the following item represents the theme's accent color at 50% perceptual luminance:
+ * <pre>
+ * <item android:state_enabled="false"
+ * android:color="?android:attr/colorAccent"
+ * android:lStar="50" />
+ * </pre>
+ *
+ * <a name="DeveloperGuide"></a>
+ * <h3>Developer guide</h3>
+ * <p>
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State
+ * List Resource</a>.
+ *
+ * @attr ref android.R.styleable#ColorStateListItem_alpha
+ * @attr ref android.R.styleable#ColorStateListItem_color
+ * @attr ref android.R.styleable#ColorStateListItem_lStar
+ */
+public class ColorStateList extends ComplexColor implements Parcelable {
+ private static final String TAG = "ColorStateList";
+
+ private static final int DEFAULT_COLOR = Color.RED;
+ private static final int[][] EMPTY = new int[][] { new int[0] };
+
+ /** Thread-safe cache of single-color ColorStateLists. */
+ private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>();
+
+ /** Lazily-created factory for this color state list. */
+ @UnsupportedAppUsage
+ private ColorStateListFactory mFactory;
+
+ private int[][] mThemeAttrs;
+ private @Config int mChangingConfigurations;
+
+ @UnsupportedAppUsage
+ private int[][] mStateSpecs;
+ @UnsupportedAppUsage
+ private int[] mColors;
+ @UnsupportedAppUsage
+ private int mDefaultColor;
+ private boolean mIsOpaque;
+
+ @UnsupportedAppUsage
+ private ColorStateList() {
+ // Not publicly instantiable.
+ }
+
+ /**
+ * Creates a ColorStateList that returns the specified mapping from
+ * states to colors.
+ */
+ public ColorStateList(int[][] states, @ColorInt int[] colors) {
+ mStateSpecs = states;
+ mColors = colors;
+
+ onColorsChanged();
+ }
+
+ /**
+ * @return A ColorStateList containing a single color.
+ */
+ @NonNull
+ public static ColorStateList valueOf(@ColorInt int color) {
+ synchronized (sCache) {
+ final int index = sCache.indexOfKey(color);
+ if (index >= 0) {
+ final ColorStateList cached = sCache.valueAt(index).get();
+ if (cached != null) {
+ return cached;
+ }
+
+ // Prune missing entry.
+ sCache.removeAt(index);
+ }
+
+ // Prune the cache before adding new items.
+ final int N = sCache.size();
+ for (int i = N - 1; i >= 0; i--) {
+ if (sCache.valueAt(i).get() == null) {
+ sCache.removeAt(i);
+ }
+ }
+
+ final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color });
+ sCache.put(color, new WeakReference<>(csl));
+ return csl;
+ }
+ }
+
+ /**
+ * Creates a ColorStateList with the same properties as another
+ * ColorStateList.
+ * <p>
+ * The properties of the new ColorStateList can be modified without
+ * affecting the source ColorStateList.
+ *
+ * @param orig the source color state list
+ */
+ private ColorStateList(ColorStateList orig) {
+ if (orig != null) {
+ mChangingConfigurations = orig.mChangingConfigurations;
+ mStateSpecs = orig.mStateSpecs;
+ mDefaultColor = orig.mDefaultColor;
+ mIsOpaque = orig.mIsOpaque;
+
+ // Deep copy, these may change due to applyTheme().
+ mThemeAttrs = orig.mThemeAttrs.clone();
+ mColors = orig.mColors.clone();
+ }
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @return A new color state list.
+ *
+ * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme)
+ */
+ @NonNull
+ @Deprecated
+ public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ return createFromXml(r, parser, null);
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document using given a set of
+ * {@link Resources} and a {@link Theme}.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @param theme Optional theme to apply to the color state list, may be
+ * {@code null}.
+ * @return A new color state list.
+ */
+ @NonNull
+ public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @Nullable Theme theme) throws XmlPullParserException, IOException {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ return createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a ColorStateList from that tag.
+ *
+ * @throws XmlPullParserException if the current tag is not <selector>
+ * @return A new color state list for the current tag.
+ */
+ @NonNull
+ static ColorStateList createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getName();
+ if (!name.equals("selector")) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ": invalid color state list tag " + name);
+ }
+
+ final ColorStateList colorStateList = new ColorStateList();
+ colorStateList.inflate(r, parser, attrs, theme);
+ return colorStateList;
+ }
+
+ /**
+ * Creates a new ColorStateList that has the same states and colors as this
+ * one but where each color has the specified alpha value (0-255).
+ *
+ * @param alpha The new alpha channel value (0-255).
+ * @return A new color state list.
+ */
+ @NonNull
+ public ColorStateList withAlpha(int alpha) {
+ final int[] colors = new int[mColors.length];
+ final int len = colors.length;
+ for (int i = 0; i < len; i++) {
+ colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
+ }
+
+ return new ColorStateList(mStateSpecs, colors);
+ }
+
+ /**
+ * Creates a new ColorStateList that has the same states and colors as this
+ * one but where each color has the specified perceived luminosity value (0-100).
+ *
+ * @param lStar Target perceptual luminance (0-100).
+ * @return A new color state list.
+ */
+ @NonNull
+ public ColorStateList withLStar(float lStar) {
+ final int[] colors = new int[mColors.length];
+ final int len = colors.length;
+ for (int i = 0; i < len; i++) {
+ colors[i] = modulateColor(mColors[i], 1.0f /* alphaMod */, lStar);
+ }
+
+ return new ColorStateList(mStateSpecs, colors);
+ }
+
+ /**
+ * Fill in this object based on the contents of an XML "selector" element.
+ */
+ private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth()+1;
+ int depth;
+ int type;
+
+ @Config int changingConfigurations = 0;
+ int defaultColor = DEFAULT_COLOR;
+
+ boolean hasUnresolvedAttrs = false;
+
+ int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
+ int[][] themeAttrsList = new int[stateSpecList.length][];
+ int[] colorList = new int[stateSpecList.length];
+ int listSize = 0;
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG || depth > innerDepth
+ || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
+ R.styleable.ColorStateListItem);
+ final int[] themeAttrs = a.extractThemeAttrs();
+ final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA);
+ final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
+ final float lStar = a.getFloat(R.styleable.ColorStateListItem_lStar, -1.0f);
+
+ changingConfigurations |= a.getChangingConfigurations();
+
+ a.recycle();
+
+ // Parse all unrecognized attributes as state specifiers.
+ int j = 0;
+ final int numAttrs = attrs.getAttributeCount();
+ int[] stateSpec = new int[numAttrs];
+ for (int i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ if (stateResId == R.attr.lStar) {
+ // Non-finalized resource ids cannot be used in switch statements.
+ continue;
+ }
+ switch (stateResId) {
+ case R.attr.color:
+ case R.attr.alpha:
+ // Recognized attribute, ignore.
+ break;
+ default:
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId : -stateResId;
+ }
+ }
+ stateSpec = StateSet.trimStateSet(stateSpec, j);
+
+ // Apply alpha and luma modulation. If we couldn't resolve the color or
+ // alpha yet, the default values leave us enough information to
+ // modulate again during applyTheme().
+ final int color = modulateColor(baseColor, alphaMod, lStar);
+
+ if (listSize == 0 || stateSpec.length == 0) {
+ defaultColor = color;
+ }
+
+ if (themeAttrs != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
+ stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
+ listSize++;
+ }
+
+ mChangingConfigurations = changingConfigurations;
+ mDefaultColor = defaultColor;
+
+ if (hasUnresolvedAttrs) {
+ mThemeAttrs = new int[listSize][];
+ System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize);
+ } else {
+ mThemeAttrs = null;
+ }
+
+ mColors = new int[listSize];
+ mStateSpecs = new int[listSize][];
+ System.arraycopy(colorList, 0, mColors, 0, listSize);
+ System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
+
+ onColorsChanged();
+ }
+
+ /**
+ * Returns whether a theme can be applied to this color state list, which
+ * usually indicates that the color state list has unresolved theme
+ * attributes.
+ *
+ * @return whether a theme can be applied to this color state list
+ * @hide only for resource preloading
+ */
+ @Override
+ @UnsupportedAppUsage
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null;
+ }
+
+ /**
+ * Applies a theme to this color state list.
+ * <p>
+ * <strong>Note:</strong> Applying a theme may affect the changing
+ * configuration parameters of this color state list. After calling this
+ * method, any dependent configurations must be updated by obtaining the
+ * new configuration mask from {@link #getChangingConfigurations()}.
+ *
+ * @param t the theme to apply
+ */
+ private void applyTheme(Theme t) {
+ if (mThemeAttrs == null) {
+ return;
+ }
+
+ boolean hasUnresolvedAttrs = false;
+
+ final int[][] themeAttrsList = mThemeAttrs;
+ final int N = themeAttrsList.length;
+ for (int i = 0; i < N; i++) {
+ if (themeAttrsList[i] != null) {
+ final TypedArray a = t.resolveAttributes(themeAttrsList[i],
+ R.styleable.ColorStateListItem);
+
+ final float defaultAlphaMod;
+ if (themeAttrsList[i][R.styleable.ColorStateListItem_color] != 0) {
+ // If the base color hasn't been resolved yet, the current
+ // color's alpha channel is either full-opacity (if we
+ // haven't resolved the alpha modulation yet) or
+ // pre-modulated. Either is okay as a default value.
+ defaultAlphaMod = Color.alpha(mColors[i]) / 255.0f;
+ } else {
+ // Otherwise, the only correct default value is 1. Even if
+ // nothing is resolved during this call, we can apply this
+ // multiple times without losing of information.
+ defaultAlphaMod = 1.0f;
+ }
+
+ // Extract the theme attributes, if any, before attempting to
+ // read from the typed array. This prevents a crash if we have
+ // unresolved attrs.
+ themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
+ if (themeAttrsList[i] != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ final int baseColor = a.getColor(
+ R.styleable.ColorStateListItem_color, mColors[i]);
+ final float alphaMod = a.getFloat(
+ R.styleable.ColorStateListItem_alpha, defaultAlphaMod);
+ final float lStar = a.getFloat(
+ R.styleable.ColorStateListItem_lStar, -1.0f);
+ mColors[i] = modulateColor(baseColor, alphaMod, lStar);
+
+ // Account for any configuration changes.
+ mChangingConfigurations |= a.getChangingConfigurations();
+
+ a.recycle();
+ }
+ }
+
+ if (!hasUnresolvedAttrs) {
+ mThemeAttrs = null;
+ }
+
+ onColorsChanged();
+ }
+
+ /**
+ * Returns an appropriately themed color state list.
+ *
+ * @param t the theme to apply
+ * @return a copy of the color state list with the theme applied, or the
+ * color state list itself if there were no unresolved theme
+ * attributes
+ * @hide only for resource preloading
+ */
+ @Override
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ColorStateList obtainForTheme(Theme t) {
+ if (t == null || !canApplyTheme()) {
+ return this;
+ }
+
+ final ColorStateList clone = new ColorStateList(this);
+ clone.applyTheme(t);
+ return clone;
+ }
+
+ /**
+ * Returns a mask of the configuration parameters for which this color
+ * state list may change, requiring that it be re-created.
+ *
+ * @return a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}
+ *
+ * @see android.content.pm.ActivityInfo
+ */
+ public @Config int getChangingConfigurations() {
+ return super.getChangingConfigurations() | mChangingConfigurations;
+ }
+
+ private int modulateColor(int baseColor, float alphaMod, float lStar) {
+ final boolean validLStar = lStar >= 0.0f && lStar <= 100.0f;
+ if (alphaMod == 1.0f && !validLStar) {
+ return baseColor;
+ }
+
+ final int baseAlpha = Color.alpha(baseColor);
+ final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
+
+ if (validLStar) {
+ final Cam baseCam = ColorUtils.colorToCAM(baseColor);
+ baseColor = ColorUtils.CAMToColor(baseCam.getHue(), baseCam.getChroma(), lStar);
+ }
+
+ return (baseColor & 0xFFFFFF) | (alpha << 24);
+ }
+
+ /**
+ * Indicates whether this color state list contains at least one state spec
+ * and the first spec is not empty (e.g. match-all).
+ *
+ * @return True if this color state list changes color based on state, false
+ * otherwise.
+ * @see #getColorForState(int[], int)
+ */
+ @Override
+ public boolean isStateful() {
+ return mStateSpecs.length >= 1 && mStateSpecs[0].length > 0;
+ }
+
+ /**
+ * Return whether the state spec list has at least one item explicitly specifying
+ * {@link android.R.attr#state_focused}.
+ * @hide
+ */
+ public boolean hasFocusStateSpecified() {
+ return StateSet.containsAttribute(mStateSpecs, R.attr.state_focused);
+ }
+
+ /**
+ * Indicates whether this color state list is opaque, which means that every
+ * color returned from {@link #getColorForState(int[], int)} has an alpha
+ * value of 255.
+ *
+ * @return True if this color state list is opaque.
+ */
+ public boolean isOpaque() {
+ return mIsOpaque;
+ }
+
+ /**
+ * Return the color associated with the given set of
+ * {@link android.view.View} states.
+ *
+ * @param stateSet an array of {@link android.view.View} states
+ * @param defaultColor the color to return if there's no matching state
+ * spec in this {@link ColorStateList} that matches the
+ * stateSet.
+ *
+ * @return the color associated with that set of states in this {@link ColorStateList}.
+ */
+ public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
+ final int setLength = mStateSpecs.length;
+ for (int i = 0; i < setLength; i++) {
+ final int[] stateSpec = mStateSpecs[i];
+ if (StateSet.stateSetMatches(stateSpec, stateSet)) {
+ return mColors[i];
+ }
+ }
+ return defaultColor;
+ }
+
+ /**
+ * Return the default color in this {@link ColorStateList}.
+ *
+ * @return the default color in this {@link ColorStateList}.
+ */
+ @ColorInt
+ public int getDefaultColor() {
+ return mDefaultColor;
+ }
+
+ /**
+ * Return the states in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
+ * @return the states in this {@link ColorStateList}
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int[][] getStates() {
+ return mStateSpecs;
+ }
+
+ /**
+ * Return the colors in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
+ * @return the colors in this {@link ColorStateList}
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int[] getColors() {
+ return mColors;
+ }
+
+ /**
+ * Returns whether the specified state is referenced in any of the state
+ * specs contained within this ColorStateList.
+ * <p>
+ * Any reference, either positive or negative {ex. ~R.attr.state_enabled},
+ * will cause this method to return {@code true}. Wildcards are not counted
+ * as references.
+ *
+ * @param state the state to search for
+ * @return {@code true} if the state if referenced, {@code false} otherwise
+ * @hide Use only as directed. For internal use only.
+ */
+ public boolean hasState(int state) {
+ final int[][] stateSpecs = mStateSpecs;
+ final int specCount = stateSpecs.length;
+ for (int specIndex = 0; specIndex < specCount; specIndex++) {
+ final int[] states = stateSpecs[specIndex];
+ final int stateCount = states.length;
+ for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) {
+ if (states[stateIndex] == state || states[stateIndex] == ~state) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "ColorStateList{" +
+ "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
+ "mChangingConfigurations=" + mChangingConfigurations +
+ "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
+ "mColors=" + Arrays.toString(mColors) +
+ "mDefaultColor=" + mDefaultColor + '}';
+ }
+
+ /**
+ * Updates the default color and opacity.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private void onColorsChanged() {
+ int defaultColor = DEFAULT_COLOR;
+ boolean isOpaque = true;
+
+ final int[][] states = mStateSpecs;
+ final int[] colors = mColors;
+ final int N = states.length;
+ if (N > 0) {
+ defaultColor = colors[0];
+
+ for (int i = N - 1; i > 0; i--) {
+ if (states[i].length == 0) {
+ defaultColor = colors[i];
+ break;
+ }
+ }
+
+ for (int i = 0; i < N; i++) {
+ if (Color.alpha(colors[i]) != 0xFF) {
+ isOpaque = false;
+ break;
+ }
+ }
+ }
+
+ mDefaultColor = defaultColor;
+ mIsOpaque = isOpaque;
+ }
+
+ /**
+ * @return a factory that can create new instances of this ColorStateList
+ * @hide only for resource preloading
+ */
+ public ConstantState<ComplexColor> getConstantState() {
+ if (mFactory == null) {
+ mFactory = new ColorStateListFactory(this);
+ }
+ return mFactory;
+ }
+
+ private static class ColorStateListFactory extends ConstantState<ComplexColor> {
+ private final ColorStateList mSrc;
+
+ @UnsupportedAppUsage
+ public ColorStateListFactory(ColorStateList src) {
+ mSrc = src;
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mSrc.mChangingConfigurations;
+ }
+
+ @Override
+ public ColorStateList newInstance() {
+ return mSrc;
+ }
+
+ @Override
+ public ColorStateList newInstance(Resources res, Theme theme) {
+ return (ColorStateList) mSrc.obtainForTheme(theme);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (canApplyTheme()) {
+ Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
+ }
+ final int N = mStateSpecs.length;
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ dest.writeIntArray(mStateSpecs[i]);
+ }
+ dest.writeIntArray(mColors);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ColorStateList> CREATOR =
+ new Parcelable.Creator<ColorStateList>() {
+ @Override
+ public ColorStateList[] newArray(int size) {
+ return new ColorStateList[size];
+ }
+
+ @Override
+ public ColorStateList createFromParcel(Parcel source) {
+ final int N = source.readInt();
+ final int[][] stateSpecs = new int[N][];
+ for (int i = 0; i < N; i++) {
+ stateSpecs[i] = source.createIntArray();
+ }
+ final int[] colors = source.createIntArray();
+ return new ColorStateList(stateSpecs, colors);
+ }
+ };
+}
diff --git a/android/content/res/CompatResources.java b/android/content/res/CompatResources.java
new file mode 100644
index 0000000..829b6b7
--- /dev/null
+++ b/android/content/res/CompatResources.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ColorRes;
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Version of resources generated for apps targeting <26.
+ * @hide
+ */
+public class CompatResources extends Resources {
+
+ private WeakReference<Context> mContext;
+
+ public CompatResources(ClassLoader cls) {
+ super(cls);
+ mContext = new WeakReference<>(null);
+ }
+
+ /**
+ * @hide
+ */
+ public void setContext(Context context) {
+ mContext = new WeakReference<>(context);
+ }
+
+ @Override
+ public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
+ return getDrawable(id, getTheme());
+ }
+
+ @Override
+ public Drawable getDrawableForDensity(@DrawableRes int id, int density)
+ throws NotFoundException {
+ return getDrawableForDensity(id, density, getTheme());
+ }
+
+ @Override
+ public int getColor(@ColorRes int id) throws NotFoundException {
+ return getColor(id, getTheme());
+ }
+
+ @Override
+ public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
+ return getColorStateList(id, getTheme());
+ }
+
+ private Theme getTheme() {
+ Context c = mContext.get();
+ return c != null ? c.getTheme() : null;
+ }
+}
diff --git a/android/content/res/CompatibilityInfo.java b/android/content/res/CompatibilityInfo.java
new file mode 100644
index 0000000..439c639
--- /dev/null
+++ b/android/content/res/CompatibilityInfo.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2006 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.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayMetrics;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+/**
+ * CompatibilityInfo class keeps the information about the screen compatibility mode that the
+ * application is running under.
+ *
+ * {@hide}
+ */
+public class CompatibilityInfo implements Parcelable {
+ /** default compatibility info object for compatible applications */
+ @UnsupportedAppUsage
+ public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
+ };
+
+ /**
+ * This is the number of pixels we would like to have along the
+ * short axis of an app that needs to run on a normal size screen.
+ */
+ public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320;
+
+ /**
+ * This is the maximum aspect ratio we will allow while keeping
+ * applications in a compatible screen size.
+ */
+ public static final float MAXIMUM_ASPECT_RATIO = (854f/480f);
+
+ /**
+ * A compatibility flags
+ */
+ private final int mCompatibilityFlags;
+
+ /**
+ * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
+ * {@see compatibilityFlag}
+ */
+ private static final int SCALING_REQUIRED = 1;
+
+ /**
+ * Application must always run in compatibility mode?
+ */
+ private static final int ALWAYS_NEEDS_COMPAT = 2;
+
+ /**
+ * Application never should run in compatibility mode?
+ */
+ private static final int NEVER_NEEDS_COMPAT = 4;
+
+ /**
+ * Set if the application needs to run in screen size compatibility mode.
+ */
+ private static final int NEEDS_SCREEN_COMPAT = 8;
+
+ /**
+ * Set if the application needs to run in with compat resources.
+ */
+ private static final int NEEDS_COMPAT_RES = 16;
+
+ /**
+ * Set if the application needs to be forcibly downscaled
+ */
+ private static final int HAS_OVERRIDE_SCALING = 32;
+
+ /**
+ * The effective screen density we have selected for this application.
+ */
+ public final int applicationDensity;
+
+ /**
+ * Application's scale.
+ */
+ @UnsupportedAppUsage
+ public final float applicationScale;
+
+ /**
+ * Application's inverted scale.
+ */
+ public final float applicationInvertedScale;
+
+ @UnsupportedAppUsage
+ @Deprecated
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat) {
+ this(appInfo, screenLayout, sw, forceCompat, 1f);
+ }
+
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat, float overrideScale) {
+ int compatFlags = 0;
+
+ if (appInfo.targetSdkVersion < VERSION_CODES.O) {
+ compatFlags |= NEEDS_COMPAT_RES;
+ }
+ if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
+ || appInfo.largestWidthLimitDp != 0) {
+ // New style screen requirements spec.
+ int required = appInfo.requiresSmallestWidthDp != 0
+ ? appInfo.requiresSmallestWidthDp
+ : appInfo.compatibleWidthLimitDp;
+ if (required == 0) {
+ required = appInfo.largestWidthLimitDp;
+ }
+ int compat = appInfo.compatibleWidthLimitDp != 0
+ ? appInfo.compatibleWidthLimitDp : required;
+ if (compat < required) {
+ compat = required;
+ }
+ int largest = appInfo.largestWidthLimitDp;
+
+ if (required > DEFAULT_NORMAL_SHORT_DIMENSION) {
+ // For now -- if they require a size larger than the only
+ // size we can do in compatibility mode, then don't ever
+ // allow the app to go in to compat mode. Trying to run
+ // it at a smaller size it can handle will make it far more
+ // broken than running at a larger size than it wants or
+ // thinks it can handle.
+ compatFlags |= NEVER_NEEDS_COMPAT;
+ } else if (largest != 0 && sw > largest) {
+ // If the screen size is larger than the largest size the
+ // app thinks it can work with, then always force it in to
+ // compatibility mode.
+ compatFlags |= NEEDS_SCREEN_COMPAT | ALWAYS_NEEDS_COMPAT;
+ } else if (compat >= sw) {
+ // The screen size is something the app says it was designed
+ // for, so never do compatibility mode.
+ compatFlags |= NEVER_NEEDS_COMPAT;
+ } else if (forceCompat) {
+ // The app may work better with or without compatibility mode.
+ // Let the user decide.
+ compatFlags |= NEEDS_SCREEN_COMPAT;
+ }
+
+ // Modern apps always support densities.
+ applicationDensity = DisplayMetrics.DENSITY_DEVICE;
+ applicationScale = 1.0f;
+ applicationInvertedScale = 1.0f;
+
+ } else {
+ /**
+ * Has the application said that its UI is expandable? Based on the
+ * <supports-screen> android:expandible in the manifest.
+ */
+ final int EXPANDABLE = 2;
+
+ /**
+ * Has the application said that its UI supports large screens? Based on the
+ * <supports-screen> android:largeScreens in the manifest.
+ */
+ final int LARGE_SCREENS = 8;
+
+ /**
+ * Has the application said that its UI supports xlarge screens? Based on the
+ * <supports-screen> android:xlargeScreens in the manifest.
+ */
+ final int XLARGE_SCREENS = 32;
+
+ int sizeInfo = 0;
+
+ // We can't rely on the application always setting
+ // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input.
+ boolean anyResizeable = false;
+
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ sizeInfo |= LARGE_SCREENS;
+ anyResizeable = true;
+ if (!forceCompat) {
+ // If we aren't forcing the app into compatibility mode, then
+ // assume if it supports large screens that we should allow it
+ // to use the full space of an xlarge screen as well.
+ sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
+ }
+ }
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+ anyResizeable = true;
+ if (!forceCompat) {
+ sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
+ }
+ }
+ if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+ anyResizeable = true;
+ sizeInfo |= EXPANDABLE;
+ }
+
+ if (forceCompat) {
+ // If we are forcing compatibility mode, then ignore an app that
+ // just says it is resizable for screens. We'll only have it fill
+ // the screen if it explicitly says it supports the screen size we
+ // are running in.
+ sizeInfo &= ~EXPANDABLE;
+ }
+
+ compatFlags |= NEEDS_SCREEN_COMPAT;
+ switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) {
+ case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+ if ((sizeInfo&XLARGE_SCREENS) != 0) {
+ compatFlags &= ~NEEDS_SCREEN_COMPAT;
+ }
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+ compatFlags |= NEVER_NEEDS_COMPAT;
+ }
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_LARGE:
+ if ((sizeInfo&LARGE_SCREENS) != 0) {
+ compatFlags &= ~NEEDS_SCREEN_COMPAT;
+ }
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ compatFlags |= NEVER_NEEDS_COMPAT;
+ }
+ break;
+ }
+
+ if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) {
+ if ((sizeInfo&EXPANDABLE) != 0) {
+ compatFlags &= ~NEEDS_SCREEN_COMPAT;
+ } else if (!anyResizeable) {
+ compatFlags |= ALWAYS_NEEDS_COMPAT;
+ }
+ } else {
+ compatFlags &= ~NEEDS_SCREEN_COMPAT;
+ compatFlags |= NEVER_NEEDS_COMPAT;
+ }
+
+ if (overrideScale != 1.0f) {
+ applicationScale = overrideScale;
+ applicationInvertedScale = 1.0f / overrideScale;
+ applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
+ * applicationInvertedScale) + .5f);
+ compatFlags |= HAS_OVERRIDE_SCALING;
+ } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+ applicationDensity = DisplayMetrics.DENSITY_DEVICE;
+ applicationScale = 1.0f;
+ applicationInvertedScale = 1.0f;
+ } else {
+ applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
+ applicationScale = DisplayMetrics.DENSITY_DEVICE
+ / (float) DisplayMetrics.DENSITY_DEFAULT;
+ applicationInvertedScale = 1.0f / applicationScale;
+ compatFlags |= SCALING_REQUIRED;
+ }
+ }
+
+ mCompatibilityFlags = compatFlags;
+ }
+
+ private CompatibilityInfo(int compFlags,
+ int dens, float scale, float invertedScale) {
+ mCompatibilityFlags = compFlags;
+ applicationDensity = dens;
+ applicationScale = scale;
+ applicationInvertedScale = invertedScale;
+ }
+
+ @UnsupportedAppUsage
+ private CompatibilityInfo() {
+ this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
+ 1.0f,
+ 1.0f);
+ }
+
+ /**
+ * @return true if the scaling is required
+ */
+ @UnsupportedAppUsage
+ public boolean isScalingRequired() {
+ return (mCompatibilityFlags & (SCALING_REQUIRED | HAS_OVERRIDE_SCALING)) != 0;
+ }
+
+ @UnsupportedAppUsage
+ public boolean supportsScreen() {
+ return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
+ }
+
+ public boolean neverSupportsScreen() {
+ return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
+ }
+
+ public boolean alwaysSupportsScreen() {
+ return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0;
+ }
+
+ public boolean needsCompatResources() {
+ return (mCompatibilityFlags&NEEDS_COMPAT_RES) != 0;
+ }
+
+ /**
+ * Returns the translator which translates the coordinates in compatibility mode.
+ * @param params the window's parameter
+ */
+ @UnsupportedAppUsage
+ public Translator getTranslator() {
+ return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null;
+ }
+
+ /**
+ * A helper object to translate the screen and window coordinates back and forth.
+ * @hide
+ */
+ public class Translator {
+ @UnsupportedAppUsage
+ final public float applicationScale;
+ @UnsupportedAppUsage
+ final public float applicationInvertedScale;
+
+ private Rect mContentInsetsBuffer = null;
+ private Rect mVisibleInsetsBuffer = null;
+ private Region mTouchableAreaBuffer = null;
+
+ Translator(float applicationScale, float applicationInvertedScale) {
+ this.applicationScale = applicationScale;
+ this.applicationInvertedScale = applicationInvertedScale;
+ }
+
+ Translator() {
+ this(CompatibilityInfo.this.applicationScale,
+ CompatibilityInfo.this.applicationInvertedScale);
+ }
+
+ /**
+ * Translate the region in window to screen.
+ */
+ @UnsupportedAppUsage
+ public void translateRegionInWindowToScreen(Region transparentRegion) {
+ transparentRegion.scale(applicationScale);
+ }
+
+ /**
+ * Apply translation to the canvas that is necessary to draw the content.
+ */
+ @UnsupportedAppUsage
+ public void translateCanvas(Canvas canvas) {
+ if (applicationScale == 1.5f) {
+ /* When we scale for compatibility, we can put our stretched
+ bitmaps and ninepatches on exacty 1/2 pixel boundaries,
+ which can give us inconsistent drawing due to imperfect
+ float precision in the graphics engine's inverse matrix.
+
+ As a work-around, we translate by a tiny amount to avoid
+ landing on exact pixel centers and boundaries, giving us
+ the slop we need to draw consistently.
+
+ This constant is meant to resolve to 1/255 after it is
+ scaled by 1.5 (applicationScale). Note, this is just a guess
+ as to what is small enough not to create its own artifacts,
+ and big enough to avoid the precision problems. Feel free
+ to experiment with smaller values as you choose.
+ */
+ final float tinyOffset = 2.0f / (3 * 255);
+ canvas.translate(tinyOffset, tinyOffset);
+ }
+ canvas.scale(applicationScale, applicationScale);
+ }
+
+ /**
+ * Translate the motion event captured on screen to the application's window.
+ */
+ @UnsupportedAppUsage
+ public void translateEventInScreenToAppWindow(MotionEvent event) {
+ event.scale(applicationInvertedScale);
+ }
+
+ /**
+ * Translate the window's layout parameter, from application's view to
+ * Screen's view.
+ */
+ @UnsupportedAppUsage
+ public void translateWindowLayout(WindowManager.LayoutParams params) {
+ params.scale(applicationScale);
+ }
+
+ /**
+ * Translate a length in application's window to screen.
+ */
+ public float translateLengthInAppWindowToScreen(float length) {
+ return length * applicationScale;
+ }
+
+ /**
+ * Translate a Rect in application's window to screen.
+ */
+ @UnsupportedAppUsage
+ public void translateRectInAppWindowToScreen(Rect rect) {
+ rect.scale(applicationScale);
+ }
+
+ /**
+ * Translate a Rect in screen coordinates into the app window's coordinates.
+ */
+ @UnsupportedAppUsage
+ public void translateRectInScreenToAppWindow(Rect rect) {
+ rect.scale(applicationInvertedScale);
+ }
+
+ /**
+ * Translate an {@link InsetsState} in screen coordinates into the app window's coordinates.
+ */
+ public void translateInsetsStateInScreenToAppWindow(InsetsState state) {
+ state.scale(applicationInvertedScale);
+ }
+
+ /**
+ * Translate {@link InsetsSourceControl}s in screen coordinates into the app window's
+ * coordinates.
+ */
+ public void translateSourceControlsInScreenToAppWindow(InsetsSourceControl[] controls) {
+ if (controls == null) {
+ return;
+ }
+ final float scale = applicationInvertedScale;
+ if (scale == 1f) {
+ return;
+ }
+ for (InsetsSourceControl control : controls) {
+ if (control == null) {
+ continue;
+ }
+ final Insets hint = control.getInsetsHint();
+ control.setInsetsHint(
+ (int) (scale * hint.left),
+ (int) (scale * hint.top),
+ (int) (scale * hint.right),
+ (int) (scale * hint.bottom));
+ }
+ }
+
+ /**
+ * Translate a Point in screen coordinates into the app window's coordinates.
+ */
+ public void translatePointInScreenToAppWindow(PointF point) {
+ final float scale = applicationInvertedScale;
+ if (scale != 1.0f) {
+ point.x *= scale;
+ point.y *= scale;
+ }
+ }
+
+ /**
+ * Translate the location of the sub window.
+ * @param params
+ */
+ public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
+ params.scale(applicationScale);
+ }
+
+ /**
+ * Translate the content insets in application window to Screen. This uses
+ * the internal buffer for content insets to avoid extra object allocation.
+ */
+ @UnsupportedAppUsage
+ public Rect getTranslatedContentInsets(Rect contentInsets) {
+ if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
+ mContentInsetsBuffer.set(contentInsets);
+ translateRectInAppWindowToScreen(mContentInsetsBuffer);
+ return mContentInsetsBuffer;
+ }
+
+ /**
+ * Translate the visible insets in application window to Screen. This uses
+ * the internal buffer for visible insets to avoid extra object allocation.
+ */
+ public Rect getTranslatedVisibleInsets(Rect visibleInsets) {
+ if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
+ mVisibleInsetsBuffer.set(visibleInsets);
+ translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
+ return mVisibleInsetsBuffer;
+ }
+
+ /**
+ * Translate the touchable area in application window to Screen. This uses
+ * the internal buffer for touchable area to avoid extra object allocation.
+ */
+ public Region getTranslatedTouchableArea(Region touchableArea) {
+ if (mTouchableAreaBuffer == null) mTouchableAreaBuffer = new Region();
+ mTouchableAreaBuffer.set(touchableArea);
+ mTouchableAreaBuffer.scale(applicationScale);
+ return mTouchableAreaBuffer;
+ }
+ }
+
+ public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
+ if (!supportsScreen()) {
+ // This is a larger screen device and the app is not
+ // compatible with large screens, so diddle it.
+ CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm);
+ } else {
+ inoutDm.widthPixels = inoutDm.noncompatWidthPixels;
+ inoutDm.heightPixels = inoutDm.noncompatHeightPixels;
+ }
+
+ if (isScalingRequired()) {
+ float invertedRatio = applicationInvertedScale;
+ inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
+ inoutDm.densityDpi = (int)((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
+ inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
+ inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
+ inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
+ inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
+ inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+ }
+ }
+
+ public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
+ if (!supportsScreen()) {
+ // This is a larger screen device and the app is not
+ // compatible with large screens, so we are forcing it to
+ // run as if the screen is normal size.
+ inoutConfig.screenLayout =
+ (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
+ | Configuration.SCREENLAYOUT_SIZE_NORMAL;
+ inoutConfig.screenWidthDp = inoutConfig.compatScreenWidthDp;
+ inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp;
+ inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp;
+ }
+ inoutConfig.densityDpi = displayDensity;
+ if (isScalingRequired()) {
+ float invertedRatio = applicationInvertedScale;
+ inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
+ inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
+ inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
+ final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
+ if (appBounds != null) {
+ appBounds.scale(invertedRatio);
+ }
+ }
+ }
+
+ /**
+ * Compute the frame Rect for applications runs under compatibility mode.
+ *
+ * @param dm the display metrics used to compute the frame size.
+ * @param outDm If non-null the width and height will be set to their scaled values.
+ * @return Returns the scaling factor for the window.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
+ final int width = dm.noncompatWidthPixels;
+ final int height = dm.noncompatHeightPixels;
+ int shortSize, longSize;
+ if (width < height) {
+ shortSize = width;
+ longSize = height;
+ } else {
+ shortSize = height;
+ longSize = width;
+ }
+ int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
+ float aspect = ((float)longSize) / shortSize;
+ if (aspect > MAXIMUM_ASPECT_RATIO) {
+ aspect = MAXIMUM_ASPECT_RATIO;
+ }
+ int newLongSize = (int)(newShortSize * aspect + 0.5f);
+ int newWidth, newHeight;
+ if (width < height) {
+ newWidth = newShortSize;
+ newHeight = newLongSize;
+ } else {
+ newWidth = newLongSize;
+ newHeight = newShortSize;
+ }
+
+ float sw = width/(float)newWidth;
+ float sh = height/(float)newHeight;
+ float scale = sw < sh ? sw : sh;
+ if (scale < 1) {
+ scale = 1;
+ }
+
+ if (outDm != null) {
+ outDm.widthPixels = newWidth;
+ outDm.heightPixels = newHeight;
+ }
+
+ return scale;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ try {
+ CompatibilityInfo oc = (CompatibilityInfo)o;
+ if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
+ if (applicationDensity != oc.applicationDensity) return false;
+ if (applicationScale != oc.applicationScale) return false;
+ if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ return true;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("{");
+ sb.append(applicationDensity);
+ sb.append("dpi");
+ if (isScalingRequired()) {
+ sb.append(" ");
+ sb.append(applicationScale);
+ sb.append("x");
+ }
+ if (!supportsScreen()) {
+ sb.append(" resizing");
+ }
+ if (neverSupportsScreen()) {
+ sb.append(" never-compat");
+ }
+ if (alwaysSupportsScreen()) {
+ sb.append(" always-compat");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mCompatibilityFlags;
+ result = 31 * result + applicationDensity;
+ result = 31 * result + Float.floatToIntBits(applicationScale);
+ result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ return result;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCompatibilityFlags);
+ dest.writeInt(applicationDensity);
+ dest.writeFloat(applicationScale);
+ dest.writeFloat(applicationInvertedScale);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static final @android.annotation.NonNull Parcelable.Creator<CompatibilityInfo> CREATOR
+ = new Parcelable.Creator<CompatibilityInfo>() {
+ @Override
+ public CompatibilityInfo createFromParcel(Parcel source) {
+ return new CompatibilityInfo(source);
+ }
+
+ @Override
+ public CompatibilityInfo[] newArray(int size) {
+ return new CompatibilityInfo[size];
+ }
+ };
+
+ private CompatibilityInfo(Parcel source) {
+ mCompatibilityFlags = source.readInt();
+ applicationDensity = source.readInt();
+ applicationScale = source.readFloat();
+ applicationInvertedScale = source.readFloat();
+ }
+}
diff --git a/android/content/res/ComplexColor.java b/android/content/res/ComplexColor.java
new file mode 100644
index 0000000..58c6fc5
--- /dev/null
+++ b/android/content/res/ComplexColor.java
@@ -0,0 +1,78 @@
+/*
+ * 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.res;
+
+import android.annotation.ColorInt;
+import android.content.res.Resources.Theme;
+import android.graphics.Color;
+
+/**
+ * Defines an abstract class for the complex color information, like
+ * {@link android.content.res.ColorStateList} or {@link android.content.res.GradientColor}
+ * @hide
+ */
+public abstract class ComplexColor {
+ private int mChangingConfigurations;
+
+ /**
+ * @return {@code true} if this ComplexColor changes color based on state, {@code false}
+ * otherwise.
+ */
+ public boolean isStateful() { return false; }
+
+ /**
+ * @return the default color.
+ */
+ @ColorInt
+ public abstract int getDefaultColor();
+
+ /**
+ * @hide only for resource preloading
+ *
+ */
+ public abstract ConstantState<ComplexColor> getConstantState();
+
+ /**
+ * @hide only for resource preloading
+ */
+ public abstract boolean canApplyTheme();
+
+ /**
+ * @hide only for resource preloading
+ */
+ public abstract ComplexColor obtainForTheme(Theme t);
+
+ /**
+ * @hide only for resource preloading
+ */
+ final void setBaseChangingConfigurations(int changingConfigurations) {
+ mChangingConfigurations = changingConfigurations;
+ }
+
+ /**
+ * Returns a mask of the configuration parameters for which this color
+ * may change, requiring that it be re-created.
+ *
+ * @return a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}
+ *
+ * @see android.content.pm.ActivityInfo
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+}
diff --git a/android/content/res/ComplexColor_Accessor.java b/android/content/res/ComplexColor_Accessor.java
new file mode 100644
index 0000000..09c0260
--- /dev/null
+++ b/android/content/res/ComplexColor_Accessor.java
@@ -0,0 +1,46 @@
+/*
+ * 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.res;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * Class that provides access to the {@link GradientColor#createFromXmlInner(Resources,
+ * XmlPullParser, AttributeSet, Theme)} and {@link ColorStateList#createFromXmlInner(Resources,
+ * XmlPullParser, AttributeSet, Theme)} methods
+ */
+public class ComplexColor_Accessor {
+ public static GradientColor createGradientColorFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws IOException, XmlPullParserException {
+ return GradientColor.createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ public static ColorStateList createColorStateListFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws IOException, XmlPullParserException {
+ return ColorStateList.createFromXmlInner(r, parser, attrs, theme);
+ }
+}
diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java
new file mode 100644
index 0000000..b66f048
--- /dev/null
+++ b/android/content/res/Configuration.java
@@ -0,0 +1,2815 @@
+/*
+ * Copyright (C) 2008 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 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.FONT_WEIGHT_ADJUSTMENT;
+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.LOCALE_LIST;
+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;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.LocaleProto;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.Config;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+import android.view.View;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.IllformedLocaleException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class describes all device configuration information that can
+ * impact the resources the application retrieves. This includes both
+ * user-specified configuration options (locale list and scaling) as well
+ * as device configurations (such as input modes, screen size and screen orientation).
+ * <p>You can acquire this object from {@link Resources}, using {@link
+ * Resources#getConfiguration}. Thus, from an activity, you can get it by chaining the request
+ * with {@link android.app.Activity#getResources}:</p>
+ * <pre>Configuration config = getResources().getConfiguration();</pre>
+ */
+public final class Configuration implements Parcelable, Comparable<Configuration> {
+ /** @hide */
+ public static final Configuration EMPTY = new Configuration();
+
+ private static final String TAG = "Configuration";
+
+ /**
+ * Current user preference for the scaling factor for fonts, relative
+ * to the base density scaling.
+ */
+ public float fontScale;
+
+ /**
+ * IMSI MCC (Mobile Country Code), corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#MccQualifier">mcc</a>
+ * resource qualifier. 0 if undefined.
+ */
+ public int mcc;
+
+ /**
+ * IMSI MNC (Mobile Network Code), corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#MccQualifier">mnc</a>
+ * resource qualifier. 0 if undefined. Note that the actual MNC may be 0; in order to check
+ * for this use the {@link #MNC_ZERO} symbol.
+ */
+ public int mnc;
+
+ /**
+ * Constant used to to represent MNC (Mobile Network Code) zero.
+ * 0 cannot be used, since it is used to represent an undefined MNC.
+ */
+ public static final int MNC_ZERO = 0xffff;
+
+ /**
+ * Current user preference for the locale, corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#LocaleQualifier">locale</a>
+ * resource qualifier.
+ *
+ * @deprecated Do not set or read this directly. Use {@link #getLocales()} and
+ * {@link #setLocales(LocaleList)}. If only the primary locale is needed,
+ * <code>getLocales().get(0)</code> is now the preferred accessor.
+ */
+ @Deprecated public Locale locale;
+
+ private LocaleList mLocaleList;
+
+ /**
+ * Locale should persist on setting. This is hidden because it is really
+ * questionable whether this is the right way to expose the functionality.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean userSetLocale;
+
+
+ /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
+ /**
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
+ * indicating that it is unknown whether or not the screen is wide gamut.
+ */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;
+ /**
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
+ * indicating that the screen is not wide gamut.
+ * <p>Corresponds to the <code>-nowidecg</code> resource qualifier.</p>
+ */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x1;
+ /**
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
+ * indicating that the screen is wide gamut.
+ * <p>Corresponds to the <code>-widecg</code> resource qualifier.</p>
+ */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x2;
+
+ /** Constant for {@link #colorMode}: bits that encode the dynamic range of the screen. */
+ public static final int COLOR_MODE_HDR_MASK = 0xc;
+ /** Constant for {@link #colorMode}: bits shift to get the screen dynamic range. */
+ public static final int COLOR_MODE_HDR_SHIFT = 2;
+ /**
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
+ * indicating that it is unknown whether or not the screen is HDR.
+ */
+ public static final int COLOR_MODE_HDR_UNDEFINED = 0x0;
+ /**
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
+ * indicating that the screen is not HDR (low/standard dynamic range).
+ * <p>Corresponds to the <code>-lowdr</code> resource qualifier.</p>
+ */
+ public static final int COLOR_MODE_HDR_NO = 0x1 << COLOR_MODE_HDR_SHIFT;
+ /**
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
+ * indicating that the screen is HDR (dynamic range).
+ * <p>Corresponds to the <code>-highdr</code> resource qualifier.</p>
+ */
+ public static final int COLOR_MODE_HDR_YES = 0x2 << COLOR_MODE_HDR_SHIFT;
+
+ /** Constant for {@link #colorMode}: a value indicating that the color mode is undefined */
+ @SuppressWarnings("PointlessBitwiseExpression")
+ public static final int COLOR_MODE_UNDEFINED = COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED |
+ COLOR_MODE_HDR_UNDEFINED;
+
+ /**
+ * Bit mask of color capabilities of the screen. Currently there are two fields:
+ * <p>The {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of
+ * the screen. They may be one of
+ * {@link #COLOR_MODE_WIDE_COLOR_GAMUT_NO} or {@link #COLOR_MODE_WIDE_COLOR_GAMUT_YES}.</p>
+ *
+ * <p>The {@link #COLOR_MODE_HDR_MASK} defines the dynamic range of the screen. They may be
+ * one of {@link #COLOR_MODE_HDR_NO} or {@link #COLOR_MODE_HDR_YES}.</p>
+ *
+ * <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
+ * Multiple Screens</a> for more information.</p>
+ */
+ public int colorMode;
+
+ /** Constant for {@link #screenLayout}: bits that encode the size. */
+ public static final int SCREENLAYOUT_SIZE_MASK = 0x0f;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
+ * value indicating that no size has been set. */
+ public static final int SCREENLAYOUT_SIZE_UNDEFINED = 0x00;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
+ * value indicating the screen is at least approximately 320x426 dp units,
+ * corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">small</a>
+ * resource qualifier.
+ * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
+ * Multiple Screens</a> for more information. */
+ public static final int SCREENLAYOUT_SIZE_SMALL = 0x01;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
+ * value indicating the screen is at least approximately 320x470 dp units,
+ * corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">normal</a>
+ * resource qualifier.
+ * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
+ * Multiple Screens</a> for more information. */
+ public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
+ * value indicating the screen is at least approximately 480x640 dp units,
+ * corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">large</a>
+ * resource qualifier.
+ * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
+ * Multiple Screens</a> for more information. */
+ public static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
+ * value indicating the screen is at least approximately 720x960 dp units,
+ * corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">xlarge</a>
+ * resource qualifier.
+ * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
+ * Multiple Screens</a> for more information.*/
+ public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
+
+ /** Constant for {@link #screenLayout}: bits that encode the aspect ratio. */
+ public static final int SCREENLAYOUT_LONG_MASK = 0x30;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LONG_MASK}
+ * value indicating that no size has been set. */
+ public static final int SCREENLAYOUT_LONG_UNDEFINED = 0x00;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LONG_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenAspectQualifier">notlong</a>
+ * resource qualifier. */
+ public static final int SCREENLAYOUT_LONG_NO = 0x10;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LONG_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenAspectQualifier">long</a>
+ * resource qualifier. */
+ public static final int SCREENLAYOUT_LONG_YES = 0x20;
+
+ /** Constant for {@link #screenLayout}: bits that encode the layout direction. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0;
+ /** Constant for {@link #screenLayout}: bits shift to get the layout direction. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_SHIFT = 6;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK}
+ * value indicating that no layout dir has been set. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_UNDEFINED = 0x00;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK}
+ * value indicating that a layout dir has been set to LTR. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x01 << SCREENLAYOUT_LAYOUTDIR_SHIFT;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK}
+ * value indicating that a layout dir has been set to RTL. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x02 << SCREENLAYOUT_LAYOUTDIR_SHIFT;
+
+ /** Constant for {@link #screenLayout}: bits that encode roundness of the screen. */
+ public static final int SCREENLAYOUT_ROUND_MASK = 0x300;
+ /** @hide Constant for {@link #screenLayout}: bit shift to get to screen roundness bits */
+ public static final int SCREENLAYOUT_ROUND_SHIFT = 8;
+ /**
+ * Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_ROUND_MASK} value indicating
+ * that it is unknown whether or not the screen has a round shape.
+ */
+ public static final int SCREENLAYOUT_ROUND_UNDEFINED = 0x00;
+ /**
+ * Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_ROUND_MASK} value indicating
+ * that the screen does not have a rounded shape.
+ */
+ public static final int SCREENLAYOUT_ROUND_NO = 0x1 << SCREENLAYOUT_ROUND_SHIFT;
+ /**
+ * Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_ROUND_MASK} value indicating
+ * that the screen has a rounded shape. Corners may not be visible to the user;
+ * developers should pay special attention to the {@link android.view.WindowInsets} delivered
+ * to views for more information about ensuring content is not obscured.
+ *
+ * <p>Corresponds to the <code>-round</code> resource qualifier.</p>
+ */
+ public static final int SCREENLAYOUT_ROUND_YES = 0x2 << SCREENLAYOUT_ROUND_SHIFT;
+
+ /** Constant for {@link #screenLayout}: a value indicating that screenLayout is undefined */
+ public static final int SCREENLAYOUT_UNDEFINED = SCREENLAYOUT_SIZE_UNDEFINED |
+ SCREENLAYOUT_LONG_UNDEFINED | SCREENLAYOUT_LAYOUTDIR_UNDEFINED |
+ SCREENLAYOUT_ROUND_UNDEFINED;
+
+ /**
+ * Special flag we generate to indicate that the screen layout requires
+ * us to use a compatibility mode for apps that are not modern layout
+ * aware.
+ * @hide
+ */
+ public static final int SCREENLAYOUT_COMPAT_NEEDED = 0x10000000;
+
+ /**
+ * Bit mask of overall layout of the screen. Currently there are four
+ * fields:
+ * <p>The {@link #SCREENLAYOUT_SIZE_MASK} bits define the overall size
+ * of the screen. They may be one of
+ * {@link #SCREENLAYOUT_SIZE_SMALL}, {@link #SCREENLAYOUT_SIZE_NORMAL},
+ * {@link #SCREENLAYOUT_SIZE_LARGE}, or {@link #SCREENLAYOUT_SIZE_XLARGE}.</p>
+ *
+ * <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen
+ * is wider/taller than normal. They may be one of
+ * {@link #SCREENLAYOUT_LONG_NO} or {@link #SCREENLAYOUT_LONG_YES}.</p>
+ *
+ * <p>The {@link #SCREENLAYOUT_LAYOUTDIR_MASK} defines whether the screen layout
+ * is either LTR or RTL. They may be one of
+ * {@link #SCREENLAYOUT_LAYOUTDIR_LTR} or {@link #SCREENLAYOUT_LAYOUTDIR_RTL}.</p>
+ *
+ * <p>The {@link #SCREENLAYOUT_ROUND_MASK} defines whether the screen has a rounded
+ * shape. They may be one of {@link #SCREENLAYOUT_ROUND_NO} or {@link #SCREENLAYOUT_ROUND_YES}.
+ * </p>
+ *
+ * <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
+ * Multiple Screens</a> for more information.</p>
+ */
+ public int screenLayout;
+
+ /**
+ * An undefined fontWeightAdjustment.
+ */
+ public static final int FONT_WEIGHT_ADJUSTMENT_UNDEFINED = Integer.MAX_VALUE;
+
+ /**
+ * Adjustment in text font weight. Used to reflect the current user preference for increasing
+ * font weight.
+ *
+ * <p> If the text font weight is less than the minimum of 1, 1 will be used. If the font weight
+ * exceeds the maximum of 1000, 1000 will be used.
+ *
+ * @see android.graphics.Typeface#create(Typeface, int, boolean)
+ * @see android.graphics.fonts.FontStyle#FONT_WEIGHT_MIN
+ * @see android.graphics.fonts.FontStyle#FONT_WEIGHT_MAX
+ */
+ public int fontWeightAdjustment;
+
+ /**
+ * Configuration relating to the windowing state of the object associated with this
+ * Configuration. Contents of this field are not intended to affect resources, but need to be
+ * communicated and propagated at the same time as the rest of Configuration.
+ * @hide
+ */
+ @TestApi
+ public final WindowConfiguration windowConfiguration = new WindowConfiguration();
+
+ /** @hide */
+ static public int resetScreenLayout(int curLayout) {
+ return (curLayout&~(SCREENLAYOUT_LONG_MASK | SCREENLAYOUT_SIZE_MASK
+ | SCREENLAYOUT_COMPAT_NEEDED))
+ | (SCREENLAYOUT_LONG_YES | SCREENLAYOUT_SIZE_XLARGE);
+ }
+
+ /** @hide */
+ static public int reduceScreenLayout(int curLayout, int longSizeDp, int shortSizeDp) {
+ int screenLayoutSize;
+ boolean screenLayoutLong;
+ boolean screenLayoutCompatNeeded;
+
+ // These semi-magic numbers define our compatibility modes for
+ // applications with different screens. These are guarantees to
+ // app developers about the space they can expect for a particular
+ // configuration. DO NOT CHANGE!
+ if (longSizeDp < 470) {
+ // This is shorter than an HVGA normal density screen (which
+ // is 480 pixels on its long side).
+ screenLayoutSize = SCREENLAYOUT_SIZE_SMALL;
+ screenLayoutLong = false;
+ screenLayoutCompatNeeded = false;
+ } else {
+ // What size is this screen screen?
+ if (longSizeDp >= 960 && shortSizeDp >= 720) {
+ // 1.5xVGA or larger screens at medium density are the point
+ // at which we consider it to be an extra large screen.
+ screenLayoutSize = SCREENLAYOUT_SIZE_XLARGE;
+ } else if (longSizeDp >= 640 && shortSizeDp >= 480) {
+ // VGA or larger screens at medium density are the point
+ // at which we consider it to be a large screen.
+ screenLayoutSize = SCREENLAYOUT_SIZE_LARGE;
+ } else {
+ screenLayoutSize = SCREENLAYOUT_SIZE_NORMAL;
+ }
+
+ // If this screen is wider than normal HVGA, or taller
+ // than FWVGA, then for old apps we want to run in size
+ // compatibility mode.
+ if (shortSizeDp > 321 || longSizeDp > 570) {
+ screenLayoutCompatNeeded = true;
+ } else {
+ screenLayoutCompatNeeded = false;
+ }
+
+ // Is this a long screen?
+ if (((longSizeDp*3)/5) >= (shortSizeDp-1)) {
+ // Anything wider than WVGA (5:3) is considering to be long.
+ screenLayoutLong = true;
+ } else {
+ screenLayoutLong = false;
+ }
+ }
+
+ // Now reduce the last screenLayout to not be better than what we
+ // have found.
+ if (!screenLayoutLong) {
+ curLayout = (curLayout&~SCREENLAYOUT_LONG_MASK) | SCREENLAYOUT_LONG_NO;
+ }
+ if (screenLayoutCompatNeeded) {
+ curLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED;
+ }
+ int curSize = curLayout&SCREENLAYOUT_SIZE_MASK;
+ if (screenLayoutSize < curSize) {
+ curLayout = (curLayout&~SCREENLAYOUT_SIZE_MASK) | screenLayoutSize;
+ }
+ return curLayout;
+ }
+
+ /** @hide */
+ public static String configurationDiffToString(int diff) {
+ ArrayList<String> list = new ArrayList<>();
+ if ((diff & ActivityInfo.CONFIG_MCC) != 0) {
+ list.add("CONFIG_MCC");
+ }
+ if ((diff & ActivityInfo.CONFIG_MNC) != 0) {
+ list.add("CONFIG_MNC");
+ }
+ if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ list.add("CONFIG_LOCALE");
+ }
+ if ((diff & ActivityInfo.CONFIG_TOUCHSCREEN) != 0) {
+ list.add("CONFIG_TOUCHSCREEN");
+ }
+ if ((diff & ActivityInfo.CONFIG_KEYBOARD) != 0) {
+ list.add("CONFIG_KEYBOARD");
+ }
+ if ((diff & ActivityInfo.CONFIG_KEYBOARD_HIDDEN) != 0) {
+ list.add("CONFIG_KEYBOARD_HIDDEN");
+ }
+ if ((diff & ActivityInfo.CONFIG_NAVIGATION) != 0) {
+ list.add("CONFIG_NAVIGATION");
+ }
+ if ((diff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ list.add("CONFIG_ORIENTATION");
+ }
+ if ((diff & ActivityInfo.CONFIG_SCREEN_LAYOUT) != 0) {
+ list.add("CONFIG_SCREEN_LAYOUT");
+ }
+ if ((diff & ActivityInfo.CONFIG_COLOR_MODE) != 0) {
+ list.add("CONFIG_COLOR_MODE");
+ }
+ if ((diff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ list.add("CONFIG_UI_MODE");
+ }
+ if ((diff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+ list.add("CONFIG_SCREEN_SIZE");
+ }
+ if ((diff & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ list.add("CONFIG_SMALLEST_SCREEN_SIZE");
+ }
+ if ((diff & ActivityInfo.CONFIG_DENSITY) != 0) {
+ list.add("CONFIG_DENSITY");
+ }
+ if ((diff & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0) {
+ list.add("CONFIG_LAYOUT_DIRECTION");
+ }
+ if ((diff & ActivityInfo.CONFIG_FONT_SCALE) != 0) {
+ list.add("CONFIG_FONT_SCALE");
+ }
+ if ((diff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
+ list.add("CONFIG_ASSETS_PATHS");
+ }
+ if ((diff & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) {
+ list.add("CONFIG_WINDOW_CONFIGURATION");
+ }
+ if ((diff & ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT) != 0) {
+ list.add("CONFIG_AUTO_BOLD_TEXT");
+ }
+ StringBuilder builder = new StringBuilder("{");
+ for (int i = 0, n = list.size(); i < n; i++) {
+ builder.append(list.get(i));
+ if (i != n - 1) {
+ builder.append(", ");
+ }
+ }
+ builder.append("}");
+ return builder.toString();
+ }
+
+ /**
+ * Check if the Configuration's current {@link #screenLayout} is at
+ * least the given size.
+ *
+ * @param size The desired size, either {@link #SCREENLAYOUT_SIZE_SMALL},
+ * {@link #SCREENLAYOUT_SIZE_NORMAL}, {@link #SCREENLAYOUT_SIZE_LARGE}, or
+ * {@link #SCREENLAYOUT_SIZE_XLARGE}.
+ * @return Returns true if the current screen layout size is at least
+ * the given size.
+ */
+ public boolean isLayoutSizeAtLeast(int size) {
+ int cur = screenLayout&SCREENLAYOUT_SIZE_MASK;
+ if (cur == SCREENLAYOUT_SIZE_UNDEFINED) return false;
+ return cur >= size;
+ }
+
+ /** Constant for {@link #touchscreen}: a value indicating that no value has been set. */
+ public static final int TOUCHSCREEN_UNDEFINED = 0;
+ /** Constant for {@link #touchscreen}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#TouchscreenQualifier">notouch</a>
+ * resource qualifier. */
+ public static final int TOUCHSCREEN_NOTOUCH = 1;
+ /** @deprecated Not currently supported or used. */
+ @Deprecated public static final int TOUCHSCREEN_STYLUS = 2;
+ /** Constant for {@link #touchscreen}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#TouchscreenQualifier">finger</a>
+ * resource qualifier. */
+ public static final int TOUCHSCREEN_FINGER = 3;
+
+ /**
+ * The kind of touch screen attached to the device.
+ * One of: {@link #TOUCHSCREEN_NOTOUCH}, {@link #TOUCHSCREEN_FINGER}.
+ */
+ public int touchscreen;
+
+ /** Constant for {@link #keyboard}: a value indicating that no value has been set. */
+ public static final int KEYBOARD_UNDEFINED = 0;
+ /** Constant for {@link #keyboard}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ImeQualifier">nokeys</a>
+ * resource qualifier. */
+ public static final int KEYBOARD_NOKEYS = 1;
+ /** Constant for {@link #keyboard}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ImeQualifier">qwerty</a>
+ * resource qualifier. */
+ public static final int KEYBOARD_QWERTY = 2;
+ /** Constant for {@link #keyboard}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ImeQualifier">12key</a>
+ * resource qualifier. */
+ public static final int KEYBOARD_12KEY = 3;
+
+ /**
+ * The kind of keyboard attached to the device.
+ * One of: {@link #KEYBOARD_NOKEYS}, {@link #KEYBOARD_QWERTY},
+ * {@link #KEYBOARD_12KEY}.
+ */
+ public int keyboard;
+
+ /** Constant for {@link #keyboardHidden}: a value indicating that no value has been set. */
+ public static final int KEYBOARDHIDDEN_UNDEFINED = 0;
+ /** Constant for {@link #keyboardHidden}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keysexposed</a>
+ * resource qualifier. */
+ public static final int KEYBOARDHIDDEN_NO = 1;
+ /** Constant for {@link #keyboardHidden}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyshidden</a>
+ * resource qualifier. */
+ public static final int KEYBOARDHIDDEN_YES = 2;
+ /** Constant matching actual resource implementation. {@hide} */
+ public static final int KEYBOARDHIDDEN_SOFT = 3;
+
+ /**
+ * A flag indicating whether any keyboard is available. Unlike
+ * {@link #hardKeyboardHidden}, this also takes into account a soft
+ * keyboard, so if the hard keyboard is hidden but there is soft
+ * keyboard available, it will be set to NO. Value is one of:
+ * {@link #KEYBOARDHIDDEN_NO}, {@link #KEYBOARDHIDDEN_YES}.
+ */
+ public int keyboardHidden;
+
+ /** Constant for {@link #hardKeyboardHidden}: a value indicating that no value has been set. */
+ public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0;
+ /** Constant for {@link #hardKeyboardHidden}, value corresponding to the
+ * physical keyboard being exposed. */
+ public static final int HARDKEYBOARDHIDDEN_NO = 1;
+ /** Constant for {@link #hardKeyboardHidden}, value corresponding to the
+ * physical keyboard being hidden. */
+ public static final int HARDKEYBOARDHIDDEN_YES = 2;
+
+ /**
+ * A flag indicating whether the hard keyboard has been hidden. This will
+ * be set on a device with a mechanism to hide the keyboard from the
+ * user, when that mechanism is closed. One of:
+ * {@link #HARDKEYBOARDHIDDEN_NO}, {@link #HARDKEYBOARDHIDDEN_YES}.
+ */
+ public int hardKeyboardHidden;
+
+ /** Constant for {@link #navigation}: a value indicating that no value has been set. */
+ public static final int NAVIGATION_UNDEFINED = 0;
+ /** Constant for {@link #navigation}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">nonav</a>
+ * resource qualifier. */
+ public static final int NAVIGATION_NONAV = 1;
+ /** Constant for {@link #navigation}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">dpad</a>
+ * resource qualifier. */
+ public static final int NAVIGATION_DPAD = 2;
+ /** Constant for {@link #navigation}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">trackball</a>
+ * resource qualifier. */
+ public static final int NAVIGATION_TRACKBALL = 3;
+ /** Constant for {@link #navigation}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">wheel</a>
+ * resource qualifier. */
+ public static final int NAVIGATION_WHEEL = 4;
+
+ /**
+ * The kind of navigation method available on the device.
+ * One of: {@link #NAVIGATION_NONAV}, {@link #NAVIGATION_DPAD},
+ * {@link #NAVIGATION_TRACKBALL}, {@link #NAVIGATION_WHEEL}.
+ */
+ public int navigation;
+
+ /** Constant for {@link #navigationHidden}: a value indicating that no value has been set. */
+ public static final int NAVIGATIONHIDDEN_UNDEFINED = 0;
+ /** Constant for {@link #navigationHidden}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavAvailQualifier">navexposed</a>
+ * resource qualifier. */
+ public static final int NAVIGATIONHIDDEN_NO = 1;
+ /** Constant for {@link #navigationHidden}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavAvailQualifier">navhidden</a>
+ * resource qualifier. */
+ public static final int NAVIGATIONHIDDEN_YES = 2;
+
+ /**
+ * A flag indicating whether any 5-way or DPAD navigation available.
+ * This will be set on a device with a mechanism to hide the navigation
+ * controls from the user, when that mechanism is closed. One of:
+ * {@link #NAVIGATIONHIDDEN_NO}, {@link #NAVIGATIONHIDDEN_YES}.
+ */
+ public int navigationHidden;
+
+ /** @hide **/
+ @IntDef(prefix = {"ORIENTATION_"}, value = {
+ ORIENTATION_UNDEFINED,
+ ORIENTATION_PORTRAIT,
+ ORIENTATION_LANDSCAPE,
+ ORIENTATION_SQUARE
+ })
+ public @interface Orientation {
+ }
+
+ /** Constant for {@link #orientation}: a value indicating that no value has been set. */
+ public static final int ORIENTATION_UNDEFINED = 0;
+ /** Constant for {@link #orientation}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#OrientationQualifier">port</a>
+ * resource qualifier. */
+ public static final int ORIENTATION_PORTRAIT = 1;
+ /** Constant for {@link #orientation}, value corresponding to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#OrientationQualifier">land</a>
+ * resource qualifier. */
+ public static final int ORIENTATION_LANDSCAPE = 2;
+ /** @deprecated Not currently supported or used. */
+ @Deprecated public static final int ORIENTATION_SQUARE = 3;
+
+ /**
+ * Overall orientation of the screen. May be one of
+ * {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}.
+ */
+ @Orientation
+ public int orientation;
+
+ /** Constant for {@link #uiMode}: bits that encode the mode type. */
+ public static final int UI_MODE_TYPE_MASK = 0x0f;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value indicating that no mode type has been set. */
+ public static final int UI_MODE_TYPE_UNDEFINED = 0x00;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">no
+ * UI mode</a> resource qualifier specified. */
+ public static final int UI_MODE_TYPE_NORMAL = 0x01;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">desk</a>
+ * resource qualifier. */
+ public static final int UI_MODE_TYPE_DESK = 0x02;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">car</a>
+ * resource qualifier. */
+ public static final int UI_MODE_TYPE_CAR = 0x03;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">television</a>
+ * resource qualifier. */
+ public static final int UI_MODE_TYPE_TELEVISION = 0x04;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">appliance</a>
+ * resource qualifier. */
+ public static final int UI_MODE_TYPE_APPLIANCE = 0x05;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">watch</a>
+ * resource qualifier. */
+ public static final int UI_MODE_TYPE_WATCH = 0x06;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">vrheadset</a>
+ * resource qualifier. */
+ public static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
+
+ /** Constant for {@link #uiMode}: bits that encode the night mode. */
+ public static final int UI_MODE_NIGHT_MASK = 0x30;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_NIGHT_MASK}
+ * value indicating that no mode type has been set. */
+ public static final int UI_MODE_NIGHT_UNDEFINED = 0x00;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_NIGHT_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NightQualifier">notnight</a>
+ * resource qualifier. */
+ public static final int UI_MODE_NIGHT_NO = 0x10;
+ /** Constant for {@link #uiMode}: a {@link #UI_MODE_NIGHT_MASK}
+ * value that corresponds to the
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NightQualifier">night</a>
+ * resource qualifier. */
+ public static final int UI_MODE_NIGHT_YES = 0x20;
+
+ /**
+ * Bit mask of the ui mode. Currently there are two fields:
+ * <p>The {@link #UI_MODE_TYPE_MASK} bits define the overall ui mode of the
+ * device. They may be one of {@link #UI_MODE_TYPE_UNDEFINED},
+ * {@link #UI_MODE_TYPE_NORMAL}, {@link #UI_MODE_TYPE_DESK},
+ * {@link #UI_MODE_TYPE_CAR}, {@link #UI_MODE_TYPE_TELEVISION},
+ * {@link #UI_MODE_TYPE_APPLIANCE}, {@link #UI_MODE_TYPE_WATCH},
+ * or {@link #UI_MODE_TYPE_VR_HEADSET}.
+ *
+ * <p>The {@link #UI_MODE_NIGHT_MASK} defines whether the screen
+ * is in a special mode. They may be one of {@link #UI_MODE_NIGHT_UNDEFINED},
+ * {@link #UI_MODE_NIGHT_NO} or {@link #UI_MODE_NIGHT_YES}.
+ */
+ public int uiMode;
+
+ /**
+ * Default value for {@link #screenWidthDp} indicating that no width
+ * has been specified.
+ */
+ public static final int SCREEN_WIDTH_DP_UNDEFINED = 0;
+
+ /**
+ * The current width of the available screen space, in dp units,
+ * corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenWidthQualifier">screen
+ * width</a> resource qualifier. Set to
+ * {@link #SCREEN_WIDTH_DP_UNDEFINED} if no width is specified.
+ */
+ public int screenWidthDp;
+
+ /**
+ * Default value for {@link #screenHeightDp} indicating that no width
+ * has been specified.
+ */
+ public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0;
+
+ /**
+ * The current height of the available screen space, in dp units,
+ * corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenHeightQualifier">screen
+ * height</a> resource qualifier. Set to
+ * {@link #SCREEN_HEIGHT_DP_UNDEFINED} if no height is specified.
+ */
+ public int screenHeightDp;
+
+ /**
+ * Default value for {@link #smallestScreenWidthDp} indicating that no width
+ * has been specified.
+ */
+ public static final int SMALLEST_SCREEN_WIDTH_DP_UNDEFINED = 0;
+
+ /**
+ * The smallest screen size an application will see in normal operation,
+ * corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest
+ * screen width</a> resource qualifier.
+ * This is the smallest value of both screenWidthDp and screenHeightDp
+ * in both portrait and landscape. Set to
+ * {@link #SMALLEST_SCREEN_WIDTH_DP_UNDEFINED} if no width is specified.
+ */
+ public int smallestScreenWidthDp;
+
+ /**
+ * Default value for {@link #densityDpi} indicating that no width
+ * has been specified.
+ */
+ public static final int DENSITY_DPI_UNDEFINED = 0;
+
+ /**
+ * Value for {@link #densityDpi} for resources that scale to any density (vector drawables).
+ * {@hide}
+ */
+ public static final int DENSITY_DPI_ANY = 0xfffe;
+
+ /**
+ * Value for {@link #densityDpi} for resources that are not meant to be scaled.
+ * {@hide}
+ */
+ public static final int DENSITY_DPI_NONE = 0xffff;
+
+ /**
+ * The target screen density being rendered to,
+ * corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#DensityQualifier">density</a>
+ * resource qualifier. Set to
+ * {@link #DENSITY_DPI_UNDEFINED} if no density is specified.
+ */
+ public int densityDpi;
+
+ /** @hide Hack to get this information from WM to app running in compat mode. */
+ public int compatScreenWidthDp;
+ /** @hide Hack to get this information from WM to app running in compat mode. */
+ public int compatScreenHeightDp;
+ /** @hide Hack to get this information from WM to app running in compat mode. */
+ public int compatSmallestScreenWidthDp;
+
+ /**
+ * An undefined assetsSeq. This will not override an existing assetsSeq.
+ * @hide
+ */
+ public static final int ASSETS_SEQ_UNDEFINED = 0;
+
+ /**
+ * Internal counter that allows us to piggyback off the configuration change mechanism to
+ * signal to apps that the the assets for an Application have changed. A difference in these
+ * between two Configurations will yield a diff flag of
+ * {@link ActivityInfo#CONFIG_ASSETS_PATHS}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public int assetsSeq;
+
+ /**
+ * @hide Internal book-keeping.
+ */
+ @UnsupportedAppUsage
+ public int seq;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "NATIVE_CONFIG_" }, value = {
+ NATIVE_CONFIG_MCC,
+ NATIVE_CONFIG_MNC,
+ NATIVE_CONFIG_LOCALE,
+ NATIVE_CONFIG_TOUCHSCREEN,
+ NATIVE_CONFIG_KEYBOARD,
+ NATIVE_CONFIG_KEYBOARD_HIDDEN,
+ NATIVE_CONFIG_NAVIGATION,
+ NATIVE_CONFIG_ORIENTATION,
+ NATIVE_CONFIG_DENSITY,
+ NATIVE_CONFIG_SCREEN_SIZE,
+ NATIVE_CONFIG_VERSION,
+ NATIVE_CONFIG_SCREEN_LAYOUT,
+ NATIVE_CONFIG_UI_MODE,
+ NATIVE_CONFIG_SMALLEST_SCREEN_SIZE,
+ NATIVE_CONFIG_LAYOUTDIR,
+ NATIVE_CONFIG_COLOR_MODE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NativeConfig {}
+
+ /** @hide Native-specific bit mask for MCC config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_MCC = 0x0001;
+ /** @hide Native-specific bit mask for MNC config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_MNC = 0x0002;
+ /** @hide Native-specific bit mask for LOCALE config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_LOCALE = 0x0004;
+ /** @hide Native-specific bit mask for TOUCHSCREEN config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_TOUCHSCREEN = 0x0008;
+ /** @hide Native-specific bit mask for KEYBOARD config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_KEYBOARD = 0x0010;
+ /** @hide Native-specific bit mask for KEYBOARD_HIDDEN config; DO NOT USE UNLESS YOU
+ * ARE SURE. */
+ public static final int NATIVE_CONFIG_KEYBOARD_HIDDEN = 0x0020;
+ /** @hide Native-specific bit mask for NAVIGATION config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_NAVIGATION = 0x0040;
+ /** @hide Native-specific bit mask for ORIENTATION config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_ORIENTATION = 0x0080;
+ /** @hide Native-specific bit mask for DENSITY config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_DENSITY = 0x0100;
+ /** @hide Native-specific bit mask for SCREEN_SIZE config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_SCREEN_SIZE = 0x0200;
+ /** @hide Native-specific bit mask for VERSION config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_VERSION = 0x0400;
+ /** @hide Native-specific bit mask for SCREEN_LAYOUT config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_SCREEN_LAYOUT = 0x0800;
+ /** @hide Native-specific bit mask for UI_MODE config; DO NOT USE UNLESS YOU ARE SURE. */
+ public static final int NATIVE_CONFIG_UI_MODE = 0x1000;
+ /** @hide Native-specific bit mask for SMALLEST_SCREEN_SIZE config; DO NOT USE UNLESS YOU
+ * ARE SURE. */
+ public static final int NATIVE_CONFIG_SMALLEST_SCREEN_SIZE = 0x2000;
+ /** @hide Native-specific bit mask for LAYOUTDIR config ; DO NOT USE UNLESS YOU ARE SURE.*/
+ public static final int NATIVE_CONFIG_LAYOUTDIR = 0x4000;
+ /** @hide Native-specific bit mask for COLOR_MODE config ; DO NOT USE UNLESS YOU ARE SURE.*/
+ public static final int NATIVE_CONFIG_COLOR_MODE = 0x10000;
+
+ /**
+ * <p>Construct an invalid Configuration. This state is only suitable for constructing a
+ * Configuration delta that will be applied to some valid Configuration object. In order to
+ * create a valid standalone Configuration, you must call {@link #setToDefaults}. </p>
+ *
+ * <p>Example:</p>
+ * <pre class="prettyprint">
+ * Configuration validConfig = new Configuration();
+ * validConfig.setToDefaults();
+ *
+ * Configuration deltaOnlyConfig = new Configuration();
+ * deltaOnlyConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ *
+ * validConfig.updateFrom(deltaOnlyConfig);
+ * </pre>
+ */
+ public Configuration() {
+ unset();
+ }
+
+ /**
+ * Makes a deep copy suitable for modification.
+ */
+ public Configuration(Configuration o) {
+ setTo(o);
+ }
+
+ /* This brings mLocaleList in sync with locale in case a user of the older API who doesn't know
+ * about setLocales() has changed locale directly. */
+ private void fixUpLocaleList() {
+ if ((locale == null && !mLocaleList.isEmpty()) ||
+ (locale != null && !locale.equals(mLocaleList.get(0)))) {
+ mLocaleList = locale == null ? LocaleList.getEmptyLocaleList() : new LocaleList(locale);
+ }
+ }
+
+ /**
+ * Sets the fields in this object to those in the given Configuration.
+ *
+ * @param o The Configuration object used to set the values of this Configuration's fields.
+ */
+ public void setTo(Configuration o) {
+ fontScale = o.fontScale;
+ mcc = o.mcc;
+ mnc = o.mnc;
+ if (o.locale == null) {
+ locale = null;
+ } else if (!o.locale.equals(locale)) {
+ // Only clone a new Locale instance if we need to: the clone() is
+ // both CPU and GC intensive.
+ locale = (Locale) o.locale.clone();
+ }
+ o.fixUpLocaleList();
+ mLocaleList = o.mLocaleList;
+ userSetLocale = o.userSetLocale;
+ touchscreen = o.touchscreen;
+ keyboard = o.keyboard;
+ keyboardHidden = o.keyboardHidden;
+ hardKeyboardHidden = o.hardKeyboardHidden;
+ navigation = o.navigation;
+ navigationHidden = o.navigationHidden;
+ orientation = o.orientation;
+ screenLayout = o.screenLayout;
+ colorMode = o.colorMode;
+ uiMode = o.uiMode;
+ screenWidthDp = o.screenWidthDp;
+ screenHeightDp = o.screenHeightDp;
+ smallestScreenWidthDp = o.smallestScreenWidthDp;
+ densityDpi = o.densityDpi;
+ compatScreenWidthDp = o.compatScreenWidthDp;
+ compatScreenHeightDp = o.compatScreenHeightDp;
+ compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
+ assetsSeq = o.assetsSeq;
+ seq = o.seq;
+ windowConfiguration.setTo(o.windowConfiguration);
+ fontWeightAdjustment = o.fontWeightAdjustment;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("{");
+ sb.append(fontScale);
+ sb.append(" ");
+ if (mcc != 0) {
+ sb.append(mcc);
+ sb.append("mcc");
+ } else {
+ sb.append("?mcc");
+ }
+ if (mnc != 0) {
+ sb.append(mnc);
+ sb.append("mnc");
+ } else {
+ sb.append("?mnc");
+ }
+ fixUpLocaleList();
+ if (!mLocaleList.isEmpty()) {
+ sb.append(" ");
+ sb.append(mLocaleList);
+ } else {
+ sb.append(" ?localeList");
+ }
+ int layoutDir = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK);
+ switch (layoutDir) {
+ case SCREENLAYOUT_LAYOUTDIR_UNDEFINED: sb.append(" ?layoutDir"); break;
+ case SCREENLAYOUT_LAYOUTDIR_LTR: sb.append(" ldltr"); break;
+ case SCREENLAYOUT_LAYOUTDIR_RTL: sb.append(" ldrtl"); break;
+ default: sb.append(" layoutDir=");
+ sb.append(layoutDir >> SCREENLAYOUT_LAYOUTDIR_SHIFT); break;
+ }
+ if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ sb.append(" sw"); sb.append(smallestScreenWidthDp); sb.append("dp");
+ } else {
+ sb.append(" ?swdp");
+ }
+ if (screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) {
+ sb.append(" w"); sb.append(screenWidthDp); sb.append("dp");
+ } else {
+ sb.append(" ?wdp");
+ }
+ if (screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+ sb.append(" h"); sb.append(screenHeightDp); sb.append("dp");
+ } else {
+ sb.append(" ?hdp");
+ }
+ if (densityDpi != DENSITY_DPI_UNDEFINED) {
+ sb.append(" "); sb.append(densityDpi); sb.append("dpi");
+ } else {
+ sb.append(" ?density");
+ }
+ switch ((screenLayout&SCREENLAYOUT_SIZE_MASK)) {
+ case SCREENLAYOUT_SIZE_UNDEFINED: sb.append(" ?lsize"); break;
+ case SCREENLAYOUT_SIZE_SMALL: sb.append(" smll"); break;
+ case SCREENLAYOUT_SIZE_NORMAL: sb.append(" nrml"); break;
+ case SCREENLAYOUT_SIZE_LARGE: sb.append(" lrg"); break;
+ case SCREENLAYOUT_SIZE_XLARGE: sb.append(" xlrg"); break;
+ default: sb.append(" layoutSize=");
+ sb.append(screenLayout&SCREENLAYOUT_SIZE_MASK); break;
+ }
+ switch ((screenLayout&SCREENLAYOUT_LONG_MASK)) {
+ case SCREENLAYOUT_LONG_UNDEFINED: sb.append(" ?long"); break;
+ case SCREENLAYOUT_LONG_NO: /* not-long is not interesting to print */ break;
+ case SCREENLAYOUT_LONG_YES: sb.append(" long"); break;
+ default: sb.append(" layoutLong=");
+ sb.append(screenLayout&SCREENLAYOUT_LONG_MASK); break;
+ }
+ switch ((colorMode &COLOR_MODE_HDR_MASK)) {
+ case COLOR_MODE_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR
+ case COLOR_MODE_HDR_NO: /* ldr is not interesting to print */ break;
+ case COLOR_MODE_HDR_YES: sb.append(" hdr"); break;
+ default: sb.append(" dynamicRange=");
+ sb.append(colorMode &COLOR_MODE_HDR_MASK); break;
+ }
+ switch ((colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ case COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break;
+ case COLOR_MODE_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break;
+ case COLOR_MODE_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break;
+ default: sb.append(" wideColorGamut=");
+ sb.append(colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK); break;
+ }
+ switch (orientation) {
+ case ORIENTATION_UNDEFINED: sb.append(" ?orien"); break;
+ case ORIENTATION_LANDSCAPE: sb.append(" land"); break;
+ case ORIENTATION_PORTRAIT: sb.append(" port"); break;
+ default: sb.append(" orien="); sb.append(orientation); break;
+ }
+ switch ((uiMode&UI_MODE_TYPE_MASK)) {
+ case UI_MODE_TYPE_UNDEFINED: sb.append(" ?uimode"); break;
+ case UI_MODE_TYPE_NORMAL: /* normal is not interesting to print */ break;
+ case UI_MODE_TYPE_DESK: sb.append(" desk"); break;
+ case UI_MODE_TYPE_CAR: sb.append(" car"); break;
+ case UI_MODE_TYPE_TELEVISION: sb.append(" television"); break;
+ case UI_MODE_TYPE_APPLIANCE: sb.append(" appliance"); break;
+ case UI_MODE_TYPE_WATCH: sb.append(" watch"); break;
+ case UI_MODE_TYPE_VR_HEADSET: sb.append(" vrheadset"); break;
+ default: sb.append(" uimode="); sb.append(uiMode&UI_MODE_TYPE_MASK); break;
+ }
+ switch ((uiMode&UI_MODE_NIGHT_MASK)) {
+ case UI_MODE_NIGHT_UNDEFINED: sb.append(" ?night"); break;
+ case UI_MODE_NIGHT_NO: /* not-night is not interesting to print */ break;
+ case UI_MODE_NIGHT_YES: sb.append(" night"); break;
+ default: sb.append(" night="); sb.append(uiMode&UI_MODE_NIGHT_MASK); break;
+ }
+ switch (touchscreen) {
+ case TOUCHSCREEN_UNDEFINED: sb.append(" ?touch"); break;
+ case TOUCHSCREEN_NOTOUCH: sb.append(" -touch"); break;
+ case TOUCHSCREEN_STYLUS: sb.append(" stylus"); break;
+ case TOUCHSCREEN_FINGER: sb.append(" finger"); break;
+ default: sb.append(" touch="); sb.append(touchscreen); break;
+ }
+ switch (keyboard) {
+ case KEYBOARD_UNDEFINED: sb.append(" ?keyb"); break;
+ case KEYBOARD_NOKEYS: sb.append(" -keyb"); break;
+ case KEYBOARD_QWERTY: sb.append(" qwerty"); break;
+ case KEYBOARD_12KEY: sb.append(" 12key"); break;
+ default: sb.append(" keys="); sb.append(keyboard); break;
+ }
+ switch (keyboardHidden) {
+ case KEYBOARDHIDDEN_UNDEFINED: sb.append("/?"); break;
+ case KEYBOARDHIDDEN_NO: sb.append("/v"); break;
+ case KEYBOARDHIDDEN_YES: sb.append("/h"); break;
+ case KEYBOARDHIDDEN_SOFT: sb.append("/s"); break;
+ default: sb.append("/"); sb.append(keyboardHidden); break;
+ }
+ switch (hardKeyboardHidden) {
+ case HARDKEYBOARDHIDDEN_UNDEFINED: sb.append("/?"); break;
+ case HARDKEYBOARDHIDDEN_NO: sb.append("/v"); break;
+ case HARDKEYBOARDHIDDEN_YES: sb.append("/h"); break;
+ default: sb.append("/"); sb.append(hardKeyboardHidden); break;
+ }
+ switch (navigation) {
+ case NAVIGATION_UNDEFINED: sb.append(" ?nav"); break;
+ case NAVIGATION_NONAV: sb.append(" -nav"); break;
+ case NAVIGATION_DPAD: sb.append(" dpad"); break;
+ case NAVIGATION_TRACKBALL: sb.append(" tball"); break;
+ case NAVIGATION_WHEEL: sb.append(" wheel"); break;
+ default: sb.append(" nav="); sb.append(navigation); break;
+ }
+ switch (navigationHidden) {
+ case NAVIGATIONHIDDEN_UNDEFINED: sb.append("/?"); break;
+ case NAVIGATIONHIDDEN_NO: sb.append("/v"); break;
+ case NAVIGATIONHIDDEN_YES: sb.append("/h"); break;
+ default: sb.append("/"); sb.append(navigationHidden); break;
+ }
+ sb.append(" winConfig="); sb.append(windowConfiguration);
+ if (assetsSeq != 0) {
+ sb.append(" as.").append(assetsSeq);
+ }
+ if (seq != 0) {
+ sb.append(" s.").append(seq);
+ }
+ if (fontWeightAdjustment != FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+ sb.append(" fontWeightAdjustment=");
+ sb.append(fontWeightAdjustment);
+ } else {
+ sb.append(" ?fontWeightAdjustment");
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.content.ConfigurationProto}
+ * Has the option to ignore fields that don't need to be persisted to disk.
+ *
+ * @param protoOutputStream Stream to write the Configuration object to.
+ * @param fieldId Field Id of the Configuration as defined in the parent message
+ * @param persisted Note if this proto will be persisted to disk
+ * @param critical If true, reduce amount of data written.
+ * @hide
+ */
+ public void dumpDebug(ProtoOutputStream protoOutputStream, long fieldId, boolean persisted,
+ boolean critical) {
+ final long token = protoOutputStream.start(fieldId);
+ if (!critical) {
+ protoOutputStream.write(FONT_SCALE, fontScale);
+ protoOutputStream.write(MCC, mcc);
+ protoOutputStream.write(MNC, mnc);
+ if (mLocaleList != null) {
+ protoOutputStream.write(LOCALE_LIST, mLocaleList.toLanguageTags());
+ }
+ 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(UI_MODE, uiMode);
+ protoOutputStream.write(SMALLEST_SCREEN_WIDTH_DP, smallestScreenWidthDp);
+ protoOutputStream.write(DENSITY_DPI, densityDpi);
+ // For persistence, we do not care about window configuration
+ if (!persisted && windowConfiguration != null) {
+ windowConfiguration.dumpDebug(protoOutputStream, WINDOW_CONFIGURATION);
+ }
+ protoOutputStream.write(FONT_WEIGHT_ADJUSTMENT, fontWeightAdjustment);
+ }
+ protoOutputStream.write(ORIENTATION, orientation);
+ protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp);
+ protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp);
+ protoOutputStream.end(token);
+ }
+
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.content.ConfigurationProto}
+ *
+ * @param protoOutputStream Stream to write the Configuration object to.
+ * @param fieldId Field Id of the Configuration as defined in the parent message
+ * @hide
+ */
+ public void dumpDebug(ProtoOutputStream protoOutputStream, long fieldId) {
+ dumpDebug(protoOutputStream, fieldId, false /* persisted */, false /* critical */);
+ }
+
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.content.ConfigurationProto}
+ *
+ * @param protoOutputStream Stream to write the Configuration object to.
+ * @param fieldId Field Id of the Configuration as defined in the parent message
+ * @param critical If true, reduce amount of data written.
+ * @hide
+ */
+ public void dumpDebug(ProtoOutputStream protoOutputStream, long fieldId, boolean critical) {
+ dumpDebug(protoOutputStream, fieldId, false /* persisted */, critical);
+ }
+
+ /**
+ * Read from a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.content.ConfigurationProto}
+ *
+ * @param protoInputStream Stream to read the Configuration object from.
+ * @param fieldId Field Id of the Configuration as defined in the parent message
+ * @hide
+ */
+ public void readFromProto(ProtoInputStream protoInputStream, long fieldId) throws IOException {
+ final long token = protoInputStream.start(fieldId);
+ final List<Locale> list = new ArrayList();
+ try {
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) FONT_SCALE:
+ fontScale = protoInputStream.readFloat(FONT_SCALE);
+ break;
+ case (int) MCC:
+ mcc = protoInputStream.readInt(MCC);
+ break;
+ case (int) MNC:
+ mnc = protoInputStream.readInt(MNC);
+ break;
+ case (int) LOCALES:
+ // Parse the Locale here to handle all the repeated Locales
+ // The LocaleList will be created when the message is completed
+ final long localeToken = protoInputStream.start(LOCALES);
+ String language = "";
+ String country = "";
+ String variant = "";
+ String script = "";
+ try {
+ while (protoInputStream.nextField()
+ != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) LocaleProto.LANGUAGE:
+ language = protoInputStream.readString(
+ LocaleProto.LANGUAGE);
+ break;
+ case (int) LocaleProto.COUNTRY:
+ country = protoInputStream.readString(LocaleProto.COUNTRY);
+ break;
+ case (int) LocaleProto.VARIANT:
+ variant = protoInputStream.readString(LocaleProto.VARIANT);
+ break;
+ case (int) LocaleProto.SCRIPT:
+ script = protoInputStream.readString(LocaleProto.SCRIPT);
+ break;
+ }
+ }
+ } catch (WireTypeMismatchException wtme) {
+ // rethrow for caller deal with
+ throw wtme;
+ } finally {
+ protoInputStream.end(localeToken);
+ try {
+ final Locale locale = new Locale.Builder()
+ .setLanguage(language)
+ .setRegion(country)
+ .setVariant(variant)
+ .setScript(script)
+ .build();
+ // Log a WTF here if a repeated locale is found to avoid throwing an
+ // exception in system server when LocaleList is created below
+ final int inListIndex = list.indexOf(locale);
+ if (inListIndex != -1) {
+ Slog.wtf(TAG, "Repeated locale (" + list.get(inListIndex) + ")"
+ + " found when trying to add: " + locale.toString());
+ } else {
+ list.add(locale);
+ }
+ } catch (IllformedLocaleException e) {
+ Slog.e(TAG, "readFromProto error building locale with: "
+ + "language-" + language + ";country-" + country
+ + ";variant-" + variant + ";script-" + script);
+ }
+ }
+ break;
+ case (int) SCREEN_LAYOUT:
+ screenLayout = protoInputStream.readInt(SCREEN_LAYOUT);
+ break;
+ case (int) COLOR_MODE:
+ colorMode = protoInputStream.readInt(COLOR_MODE);
+ break;
+ case (int) TOUCHSCREEN:
+ touchscreen = protoInputStream.readInt(TOUCHSCREEN);
+ break;
+ case (int) KEYBOARD:
+ keyboard = protoInputStream.readInt(KEYBOARD);
+ break;
+ case (int) KEYBOARD_HIDDEN:
+ keyboardHidden = protoInputStream.readInt(KEYBOARD_HIDDEN);
+ break;
+ case (int) HARD_KEYBOARD_HIDDEN:
+ hardKeyboardHidden = protoInputStream.readInt(HARD_KEYBOARD_HIDDEN);
+ break;
+ case (int) NAVIGATION:
+ navigation = protoInputStream.readInt(NAVIGATION);
+ break;
+ case (int) NAVIGATION_HIDDEN:
+ navigationHidden = protoInputStream.readInt(NAVIGATION_HIDDEN);
+ break;
+ case (int) ORIENTATION:
+ orientation = protoInputStream.readInt(ORIENTATION);
+ break;
+ case (int) UI_MODE:
+ uiMode = protoInputStream.readInt(UI_MODE);
+ break;
+ case (int) SCREEN_WIDTH_DP:
+ screenWidthDp = protoInputStream.readInt(SCREEN_WIDTH_DP);
+ break;
+ case (int) SCREEN_HEIGHT_DP:
+ screenHeightDp = protoInputStream.readInt(SCREEN_HEIGHT_DP);
+ break;
+ case (int) SMALLEST_SCREEN_WIDTH_DP:
+ smallestScreenWidthDp = protoInputStream.readInt(SMALLEST_SCREEN_WIDTH_DP);
+ break;
+ case (int) DENSITY_DPI:
+ densityDpi = protoInputStream.readInt(DENSITY_DPI);
+ break;
+ case (int) WINDOW_CONFIGURATION:
+ windowConfiguration.readFromProto(protoInputStream, WINDOW_CONFIGURATION);
+ break;
+ case (int) LOCALE_LIST:
+ try {
+ setLocales(LocaleList.forLanguageTags(protoInputStream.readString(
+ LOCALE_LIST)));
+ } catch (Exception e) {
+ Slog.e(TAG, "error parsing locale list in configuration.", e);
+ }
+ break;
+ case (int) FONT_WEIGHT_ADJUSTMENT:
+ fontWeightAdjustment = protoInputStream.readInt(FONT_WEIGHT_ADJUSTMENT);
+ break;
+ }
+ }
+ } finally {
+ // Let caller handle any exceptions
+ if (list.size() > 0) {
+ //Create the LocaleList from the collected Locales
+ setLocales(new LocaleList(list.toArray(new Locale[list.size()])));
+ }
+ protoInputStream.end(token);
+ }
+ }
+
+ /**
+ * 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);
+ dumpDebug(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
+ */
+ public static String uiModeToString(int uiMode) {
+ switch (uiMode) {
+ case UI_MODE_TYPE_UNDEFINED:
+ return "UI_MODE_TYPE_UNDEFINED";
+ case UI_MODE_TYPE_NORMAL:
+ return "UI_MODE_TYPE_NORMAL";
+ case UI_MODE_TYPE_DESK:
+ return "UI_MODE_TYPE_DESK";
+ case UI_MODE_TYPE_CAR:
+ return "UI_MODE_TYPE_CAR";
+ case UI_MODE_TYPE_TELEVISION:
+ return "UI_MODE_TYPE_TELEVISION";
+ case UI_MODE_TYPE_APPLIANCE:
+ return "UI_MODE_TYPE_APPLIANCE";
+ case UI_MODE_TYPE_WATCH:
+ return "UI_MODE_TYPE_WATCH";
+ case UI_MODE_TYPE_VR_HEADSET:
+ return "UI_MODE_TYPE_VR_HEADSET";
+ default:
+ return Integer.toString(uiMode);
+ }
+ }
+
+ /**
+ * Set this object to the system defaults.
+ */
+ public void setToDefaults() {
+ fontScale = 1;
+ mcc = mnc = 0;
+ mLocaleList = LocaleList.getEmptyLocaleList();
+ locale = null;
+ userSetLocale = false;
+ touchscreen = TOUCHSCREEN_UNDEFINED;
+ keyboard = KEYBOARD_UNDEFINED;
+ keyboardHidden = KEYBOARDHIDDEN_UNDEFINED;
+ hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED;
+ navigation = NAVIGATION_UNDEFINED;
+ navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
+ orientation = ORIENTATION_UNDEFINED;
+ screenLayout = SCREENLAYOUT_UNDEFINED;
+ colorMode = COLOR_MODE_UNDEFINED;
+ uiMode = UI_MODE_TYPE_UNDEFINED;
+ screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
+ screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
+ smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ densityDpi = DENSITY_DPI_UNDEFINED;
+ assetsSeq = ASSETS_SEQ_UNDEFINED;
+ seq = 0;
+ windowConfiguration.setToDefaults();
+ fontWeightAdjustment = FONT_WEIGHT_ADJUSTMENT_UNDEFINED;
+ }
+
+ /**
+ * Set this object to completely undefined.
+ * @hide
+ */
+ public void unset() {
+ setToDefaults();
+ fontScale = 0;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ @Deprecated public void makeDefault() {
+ setToDefaults();
+ }
+
+ /**
+ * Copies the fields from delta into this Configuration object, keeping
+ * track of which ones have changed. Any undefined fields in {@code delta}
+ * are ignored and not copied in to the current Configuration.
+ *
+ * @return a bit mask of the changed fields, as per {@link #diff}
+ */
+ public @Config int updateFrom(@NonNull Configuration delta) {
+ int changed = 0;
+ if (delta.fontScale > 0 && fontScale != delta.fontScale) {
+ changed |= ActivityInfo.CONFIG_FONT_SCALE;
+ fontScale = delta.fontScale;
+ }
+ if (delta.mcc != 0 && mcc != delta.mcc) {
+ changed |= ActivityInfo.CONFIG_MCC;
+ mcc = delta.mcc;
+ }
+ if (delta.mnc != 0 && mnc != delta.mnc) {
+ changed |= ActivityInfo.CONFIG_MNC;
+ mnc = delta.mnc;
+ }
+ fixUpLocaleList();
+ delta.fixUpLocaleList();
+ if (!delta.mLocaleList.isEmpty() && !mLocaleList.equals(delta.mLocaleList)) {
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ mLocaleList = delta.mLocaleList;
+ // delta.locale can't be null, since delta.mLocaleList is not empty.
+ if (!delta.locale.equals(locale)) {
+ locale = (Locale) delta.locale.clone();
+ // If locale has changed, then layout direction is also changed ...
+ changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+ // ... and we need to update the layout direction (represented by the first
+ // 2 most significant bits in screenLayout).
+ setLayoutDirection(locale);
+ }
+ }
+ final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
+ if (deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED &&
+ deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
+ screenLayout = (screenLayout & ~SCREENLAYOUT_LAYOUTDIR_MASK) | deltaScreenLayoutDir;
+ changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+ }
+ if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
+ {
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ userSetLocale = true;
+ }
+ if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
+ && touchscreen != delta.touchscreen) {
+ changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
+ touchscreen = delta.touchscreen;
+ }
+ if (delta.keyboard != KEYBOARD_UNDEFINED
+ && keyboard != delta.keyboard) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD;
+ keyboard = delta.keyboard;
+ }
+ if (delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED
+ && keyboardHidden != delta.keyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ keyboardHidden = delta.keyboardHidden;
+ }
+ if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+ && hardKeyboardHidden != delta.hardKeyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ hardKeyboardHidden = delta.hardKeyboardHidden;
+ }
+ if (delta.navigation != NAVIGATION_UNDEFINED
+ && navigation != delta.navigation) {
+ changed |= ActivityInfo.CONFIG_NAVIGATION;
+ navigation = delta.navigation;
+ }
+ if (delta.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED
+ && navigationHidden != delta.navigationHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ navigationHidden = delta.navigationHidden;
+ }
+ if (delta.orientation != ORIENTATION_UNDEFINED
+ && orientation != delta.orientation) {
+ changed |= ActivityInfo.CONFIG_ORIENTATION;
+ orientation = delta.orientation;
+ }
+ if (((delta.screenLayout & SCREENLAYOUT_SIZE_MASK) != SCREENLAYOUT_SIZE_UNDEFINED)
+ && (delta.screenLayout & SCREENLAYOUT_SIZE_MASK)
+ != (screenLayout & SCREENLAYOUT_SIZE_MASK)) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ screenLayout = (screenLayout & ~SCREENLAYOUT_SIZE_MASK)
+ | (delta.screenLayout & SCREENLAYOUT_SIZE_MASK);
+ }
+ if (((delta.screenLayout & SCREENLAYOUT_LONG_MASK) != SCREENLAYOUT_LONG_UNDEFINED)
+ && (delta.screenLayout & SCREENLAYOUT_LONG_MASK)
+ != (screenLayout & SCREENLAYOUT_LONG_MASK)) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ screenLayout = (screenLayout & ~SCREENLAYOUT_LONG_MASK)
+ | (delta.screenLayout & SCREENLAYOUT_LONG_MASK);
+ }
+ if (((delta.screenLayout & SCREENLAYOUT_ROUND_MASK) != SCREENLAYOUT_ROUND_UNDEFINED)
+ && (delta.screenLayout & SCREENLAYOUT_ROUND_MASK)
+ != (screenLayout & SCREENLAYOUT_ROUND_MASK)) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ screenLayout = (screenLayout & ~SCREENLAYOUT_ROUND_MASK)
+ | (delta.screenLayout & SCREENLAYOUT_ROUND_MASK);
+ }
+ if ((delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED)
+ != (screenLayout & SCREENLAYOUT_COMPAT_NEEDED)
+ && delta.screenLayout != 0) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ screenLayout = (screenLayout & ~SCREENLAYOUT_COMPAT_NEEDED)
+ | (delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED);
+ }
+
+ if (((delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED)
+ && (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)
+ != (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
+ colorMode = (colorMode & ~COLOR_MODE_WIDE_COLOR_GAMUT_MASK)
+ | (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK);
+ }
+
+ if (((delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED)
+ && (delta.colorMode & COLOR_MODE_HDR_MASK)
+ != (colorMode & COLOR_MODE_HDR_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
+ colorMode = (colorMode & ~COLOR_MODE_HDR_MASK)
+ | (delta.colorMode & COLOR_MODE_HDR_MASK);
+ }
+
+ if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
+ && uiMode != delta.uiMode) {
+ changed |= ActivityInfo.CONFIG_UI_MODE;
+ if ((delta.uiMode&UI_MODE_TYPE_MASK) != UI_MODE_TYPE_UNDEFINED) {
+ uiMode = (uiMode&~UI_MODE_TYPE_MASK)
+ | (delta.uiMode&UI_MODE_TYPE_MASK);
+ }
+ if ((delta.uiMode&UI_MODE_NIGHT_MASK) != UI_MODE_NIGHT_UNDEFINED) {
+ uiMode = (uiMode&~UI_MODE_NIGHT_MASK)
+ | (delta.uiMode&UI_MODE_NIGHT_MASK);
+ }
+ }
+ if (delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+ && screenWidthDp != delta.screenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ screenWidthDp = delta.screenWidthDp;
+ }
+ if (delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED
+ && screenHeightDp != delta.screenHeightDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ screenHeightDp = delta.screenHeightDp;
+ }
+ if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && smallestScreenWidthDp != delta.smallestScreenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+ smallestScreenWidthDp = delta.smallestScreenWidthDp;
+ }
+ if (delta.densityDpi != DENSITY_DPI_UNDEFINED &&
+ densityDpi != delta.densityDpi) {
+ changed |= ActivityInfo.CONFIG_DENSITY;
+ densityDpi = delta.densityDpi;
+ }
+ if (delta.compatScreenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) {
+ compatScreenWidthDp = delta.compatScreenWidthDp;
+ }
+ if (delta.compatScreenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+ compatScreenHeightDp = delta.compatScreenHeightDp;
+ }
+ if (delta.compatSmallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ compatSmallestScreenWidthDp = delta.compatSmallestScreenWidthDp;
+ }
+ if (delta.assetsSeq != ASSETS_SEQ_UNDEFINED && delta.assetsSeq != assetsSeq) {
+ changed |= ActivityInfo.CONFIG_ASSETS_PATHS;
+ assetsSeq = delta.assetsSeq;
+ }
+ if (delta.seq != 0) {
+ seq = delta.seq;
+ }
+ if (windowConfiguration.updateFrom(delta.windowConfiguration) != 0) {
+ changed |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+ }
+
+ if (delta.fontWeightAdjustment != FONT_WEIGHT_ADJUSTMENT_UNDEFINED
+ && delta.fontWeightAdjustment != fontWeightAdjustment) {
+ changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
+ fontWeightAdjustment = delta.fontWeightAdjustment;
+ }
+
+ return changed;
+ }
+
+ /**
+ * Copies the fields specified by mask from delta into this Configuration object. This will
+ * copy anything allowed by the mask (including undefined values).
+ * @hide
+ */
+ public void setTo(@NonNull Configuration delta, @Config int mask,
+ @WindowConfiguration.WindowConfig int windowMask) {
+ if ((mask & ActivityInfo.CONFIG_FONT_SCALE) != 0) {
+ fontScale = delta.fontScale;
+ }
+ if ((mask & ActivityInfo.CONFIG_MCC) != 0) {
+ mcc = delta.mcc;
+ }
+ if ((mask & ActivityInfo.CONFIG_MNC) != 0) {
+ mnc = delta.mnc;
+ }
+ if ((mask & ActivityInfo.CONFIG_LOCALE) != 0) {
+ mLocaleList = delta.mLocaleList;
+ if (!mLocaleList.isEmpty()) {
+ if (!delta.locale.equals(locale)) {
+ // Don't churn a new Locale clone unless we're actually changing it
+ locale = (Locale) delta.locale.clone();
+ }
+ }
+ }
+ if ((mask & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0) {
+ final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
+ screenLayout = (screenLayout & ~SCREENLAYOUT_LAYOUTDIR_MASK) | deltaScreenLayoutDir;
+ }
+ if ((mask & ActivityInfo.CONFIG_LOCALE) != 0) {
+ userSetLocale = delta.userSetLocale;
+ }
+ if ((mask & ActivityInfo.CONFIG_TOUCHSCREEN) != 0) {
+ touchscreen = delta.touchscreen;
+ }
+ if ((mask & ActivityInfo.CONFIG_KEYBOARD) != 0) {
+ keyboard = delta.keyboard;
+ }
+ if ((mask & ActivityInfo.CONFIG_KEYBOARD_HIDDEN) != 0) {
+ keyboardHidden = delta.keyboardHidden;
+ hardKeyboardHidden = delta.hardKeyboardHidden;
+ navigationHidden = delta.navigationHidden;
+ }
+ if ((mask & ActivityInfo.CONFIG_NAVIGATION) != 0) {
+ navigation = delta.navigation;
+ }
+ if ((mask & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ orientation = delta.orientation;
+ }
+ if ((mask & ActivityInfo.CONFIG_SCREEN_LAYOUT) != 0) {
+ // Not enough granularity for each component unfortunately.
+ screenLayout = screenLayout | (delta.screenLayout & ~SCREENLAYOUT_LAYOUTDIR_MASK);
+ }
+ if ((mask & ActivityInfo.CONFIG_COLOR_MODE) != 0) {
+ colorMode = delta.colorMode;
+ }
+ if ((mask & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ uiMode = delta.uiMode;
+ }
+ if ((mask & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+ screenWidthDp = delta.screenWidthDp;
+ screenHeightDp = delta.screenHeightDp;
+ }
+ if ((mask & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ smallestScreenWidthDp = delta.smallestScreenWidthDp;
+ }
+ if ((mask & ActivityInfo.CONFIG_DENSITY) != 0) {
+ densityDpi = delta.densityDpi;
+ }
+ if ((mask & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
+ assetsSeq = delta.assetsSeq;
+ }
+ if ((mask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) {
+ windowConfiguration.setTo(delta.windowConfiguration, windowMask);
+ }
+ if ((mask & ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT) != 0) {
+ fontWeightAdjustment = delta.fontWeightAdjustment;
+ }
+ }
+
+ /**
+ * Return a bit mask of the differences between this Configuration
+ * object and the given one. Does not change the values of either. Any
+ * undefined fields in <var>delta</var> are ignored.
+ * @return Returns a bit mask indicating which configuration
+ * values has changed, containing any combination of
+ * {@link android.content.pm.ActivityInfo#CONFIG_FONT_SCALE
+ * PackageManager.ActivityInfo.CONFIG_FONT_SCALE},
+ * {@link android.content.pm.ActivityInfo#CONFIG_MCC
+ * PackageManager.ActivityInfo.CONFIG_MCC},
+ * {@link android.content.pm.ActivityInfo#CONFIG_MNC
+ * PackageManager.ActivityInfo.CONFIG_MNC},
+ * {@link android.content.pm.ActivityInfo#CONFIG_LOCALE
+ * PackageManager.ActivityInfo.CONFIG_LOCALE},
+ * {@link android.content.pm.ActivityInfo#CONFIG_TOUCHSCREEN
+ * PackageManager.ActivityInfo.CONFIG_TOUCHSCREEN},
+ * {@link android.content.pm.ActivityInfo#CONFIG_KEYBOARD
+ * PackageManager.ActivityInfo.CONFIG_KEYBOARD},
+ * {@link android.content.pm.ActivityInfo#CONFIG_NAVIGATION
+ * PackageManager.ActivityInfo.CONFIG_NAVIGATION},
+ * {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION
+ * PackageManager.ActivityInfo.CONFIG_ORIENTATION},
+ * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_LAYOUT
+ * PackageManager.ActivityInfo.CONFIG_SCREEN_LAYOUT}, or
+ * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE
+ * PackageManager.ActivityInfo.CONFIG_SCREEN_SIZE}, or
+ * {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE
+ * PackageManager.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE}.
+ * {@link android.content.pm.ActivityInfo#CONFIG_LAYOUT_DIRECTION
+ * PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}.
+ * {@link android.content.pm.ActivityInfo#CONFIG_FONT_WEIGHT_ADJUSTMENT
+ * PackageManager.ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT.
+ */
+ public int diff(Configuration delta) {
+ return diff(delta, false /* compareUndefined */, false /* publicOnly */);
+ }
+
+ /**
+ * Returns the diff against the provided {@link Configuration} excluding values that would
+ * publicly be equivalent, such as appBounds.
+ * @param delta {@link Configuration} to compare to.
+ *
+ * TODO(b/36812336): Remove once appBounds has been moved out of Configuration.
+ * {@hide}
+ */
+ public int diffPublicOnly(Configuration delta) {
+ return diff(delta, false /* compareUndefined */, true /* publicOnly */);
+ }
+
+ /**
+ * Variation of {@link #diff(Configuration)} with an option to skip checks for undefined values.
+ *
+ * @hide
+ */
+ public int diff(Configuration delta, boolean compareUndefined, boolean publicOnly) {
+ int changed = 0;
+ if ((compareUndefined || delta.fontScale > 0) && fontScale != delta.fontScale) {
+ changed |= ActivityInfo.CONFIG_FONT_SCALE;
+ }
+ if ((compareUndefined || delta.mcc != 0) && mcc != delta.mcc) {
+ changed |= ActivityInfo.CONFIG_MCC;
+ }
+ if ((compareUndefined || delta.mnc != 0) && mnc != delta.mnc) {
+ changed |= ActivityInfo.CONFIG_MNC;
+ }
+ fixUpLocaleList();
+ delta.fixUpLocaleList();
+ if ((compareUndefined || !delta.mLocaleList.isEmpty())
+ && !mLocaleList.equals(delta.mLocaleList)) {
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+ }
+ final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
+ if ((compareUndefined || deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED)
+ && deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
+ changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+ }
+ if ((compareUndefined || delta.touchscreen != TOUCHSCREEN_UNDEFINED)
+ && touchscreen != delta.touchscreen) {
+ changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
+ }
+ if ((compareUndefined || delta.keyboard != KEYBOARD_UNDEFINED)
+ && keyboard != delta.keyboard) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD;
+ }
+ if ((compareUndefined || delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED)
+ && keyboardHidden != delta.keyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ }
+ if ((compareUndefined || delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED)
+ && hardKeyboardHidden != delta.hardKeyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ }
+ if ((compareUndefined || delta.navigation != NAVIGATION_UNDEFINED)
+ && navigation != delta.navigation) {
+ changed |= ActivityInfo.CONFIG_NAVIGATION;
+ }
+ if ((compareUndefined || delta.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED)
+ && navigationHidden != delta.navigationHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ }
+ if ((compareUndefined || delta.orientation != ORIENTATION_UNDEFINED)
+ && orientation != delta.orientation) {
+ changed |= ActivityInfo.CONFIG_ORIENTATION;
+ }
+ if ((compareUndefined || getScreenLayoutNoDirection(delta.screenLayout) !=
+ (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED))
+ && getScreenLayoutNoDirection(screenLayout) !=
+ getScreenLayoutNoDirection(delta.screenLayout)) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ }
+ if ((compareUndefined ||
+ (delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED)
+ && (colorMode & COLOR_MODE_HDR_MASK) !=
+ (delta.colorMode & COLOR_MODE_HDR_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
+ }
+ if ((compareUndefined ||
+ (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED)
+ && (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
+ }
+ if ((compareUndefined || delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED))
+ && uiMode != delta.uiMode) {
+ changed |= ActivityInfo.CONFIG_UI_MODE;
+ }
+ if ((compareUndefined || delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED)
+ && screenWidthDp != delta.screenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ }
+ if ((compareUndefined || delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED)
+ && screenHeightDp != delta.screenHeightDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ }
+ if ((compareUndefined || delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED)
+ && smallestScreenWidthDp != delta.smallestScreenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ if ((compareUndefined || delta.densityDpi != DENSITY_DPI_UNDEFINED)
+ && densityDpi != delta.densityDpi) {
+ changed |= ActivityInfo.CONFIG_DENSITY;
+ }
+ if ((compareUndefined || delta.assetsSeq != ASSETS_SEQ_UNDEFINED)
+ && assetsSeq != delta.assetsSeq) {
+ changed |= ActivityInfo.CONFIG_ASSETS_PATHS;
+ }
+
+ // WindowConfiguration differences aren't considered public...
+ if (!publicOnly
+ && windowConfiguration.diff(delta.windowConfiguration, compareUndefined) != 0) {
+ changed |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+ }
+
+ if ((compareUndefined || delta.fontWeightAdjustment != FONT_WEIGHT_ADJUSTMENT_UNDEFINED)
+ && fontWeightAdjustment != delta.fontWeightAdjustment) {
+ changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
+ }
+ return changed;
+ }
+
+ /**
+ * Determines if a new resource needs to be loaded from the bit set of
+ * configuration changes returned by {@link #updateFrom(Configuration)}.
+ *
+ * @param configChanges the mask of changes configurations as returned by
+ * {@link #updateFrom(Configuration)}
+ * @param interestingChanges the configuration changes that the resource
+ * can handle as given in
+ * {@link android.util.TypedValue#changingConfigurations}
+ * @return {@code true} if the resource needs to be loaded, {@code false}
+ * otherwise
+ */
+ public static boolean needNewResources(@Config int configChanges,
+ @Config int interestingChanges) {
+ // CONFIG_ASSETS_PATHS and CONFIG_FONT_SCALE are higher level configuration changes that
+ // all resources are subject to change with.
+ interestingChanges = interestingChanges | ActivityInfo.CONFIG_ASSETS_PATHS
+ | ActivityInfo.CONFIG_FONT_SCALE;
+ return (configChanges & interestingChanges) != 0;
+ }
+
+ /**
+ * @hide Return true if the sequence of 'other' is better than this. Assumes
+ * that 'this' is your current sequence and 'other' is a new one you have
+ * received some how and want to compare with what you have.
+ */
+ public boolean isOtherSeqNewer(Configuration other) {
+ if (other == null) {
+ // Validation check.
+ return false;
+ }
+ if (other.seq == 0) {
+ // If the other sequence is not specified, then we must assume
+ // it is newer since we don't know any better.
+ return true;
+ }
+ if (seq == 0) {
+ // If this sequence is not specified, then we also consider the
+ // other is better. Yes we have a preference for other. Sue us.
+ return true;
+ }
+ int diff = other.seq - seq;
+ if (diff > 0x10000) {
+ // If there has been a sufficiently large jump, assume the
+ // sequence has wrapped around.
+ return false;
+ }
+ return diff > 0;
+ }
+
+ /**
+ * Parcelable methods
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(fontScale);
+ dest.writeInt(mcc);
+ dest.writeInt(mnc);
+
+ fixUpLocaleList();
+ dest.writeTypedObject(mLocaleList, flags);
+
+ if(userSetLocale) {
+ dest.writeInt(1);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(touchscreen);
+ dest.writeInt(keyboard);
+ dest.writeInt(keyboardHidden);
+ dest.writeInt(hardKeyboardHidden);
+ dest.writeInt(navigation);
+ dest.writeInt(navigationHidden);
+ dest.writeInt(orientation);
+ dest.writeInt(screenLayout);
+ dest.writeInt(colorMode);
+ dest.writeInt(uiMode);
+ dest.writeInt(screenWidthDp);
+ dest.writeInt(screenHeightDp);
+ dest.writeInt(smallestScreenWidthDp);
+ dest.writeInt(densityDpi);
+ dest.writeInt(compatScreenWidthDp);
+ dest.writeInt(compatScreenHeightDp);
+ dest.writeInt(compatSmallestScreenWidthDp);
+ windowConfiguration.writeToParcel(dest, flags);
+ dest.writeInt(assetsSeq);
+ dest.writeInt(seq);
+ dest.writeInt(fontWeightAdjustment);
+ }
+
+ public void readFromParcel(Parcel source) {
+ fontScale = source.readFloat();
+ mcc = source.readInt();
+ mnc = source.readInt();
+
+ mLocaleList = source.readTypedObject(LocaleList.CREATOR);
+ locale = mLocaleList.get(0);
+
+ userSetLocale = (source.readInt()==1);
+ touchscreen = source.readInt();
+ keyboard = source.readInt();
+ keyboardHidden = source.readInt();
+ hardKeyboardHidden = source.readInt();
+ navigation = source.readInt();
+ navigationHidden = source.readInt();
+ orientation = source.readInt();
+ screenLayout = source.readInt();
+ colorMode = source.readInt();
+ uiMode = source.readInt();
+ screenWidthDp = source.readInt();
+ screenHeightDp = source.readInt();
+ smallestScreenWidthDp = source.readInt();
+ densityDpi = source.readInt();
+ compatScreenWidthDp = source.readInt();
+ compatScreenHeightDp = source.readInt();
+ compatSmallestScreenWidthDp = source.readInt();
+ windowConfiguration.readFromParcel(source);
+ assetsSeq = source.readInt();
+ seq = source.readInt();
+ fontWeightAdjustment = source.readInt();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<Configuration> CREATOR
+ = new Parcelable.Creator<Configuration>() {
+ public Configuration createFromParcel(Parcel source) {
+ return new Configuration(source);
+ }
+
+ public Configuration[] newArray(int size) {
+ return new Configuration[size];
+ }
+ };
+
+ /**
+ * Construct this Configuration object, reading from the Parcel.
+ */
+ private Configuration(Parcel source) {
+ readFromParcel(source);
+ }
+
+
+ /**
+ * Retuns whether the configuration is in night mode
+ * @return true if night mode is active and false otherwise
+ */
+ public boolean isNightModeActive() {
+ return (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES;
+ }
+
+ public int compareTo(Configuration that) {
+ int n;
+ float a = this.fontScale;
+ float b = that.fontScale;
+ if (a < b) return -1;
+ if (a > b) return 1;
+ n = this.mcc - that.mcc;
+ if (n != 0) return n;
+ n = this.mnc - that.mnc;
+ if (n != 0) return n;
+
+ fixUpLocaleList();
+ that.fixUpLocaleList();
+ // for backward compatibility, we consider an empty locale list to be greater
+ // than any non-empty locale list.
+ if (this.mLocaleList.isEmpty()) {
+ if (!that.mLocaleList.isEmpty()) return 1;
+ } else if (that.mLocaleList.isEmpty()) {
+ return -1;
+ } else {
+ final int minSize = Math.min(this.mLocaleList.size(), that.mLocaleList.size());
+ for (int i = 0; i < minSize; ++i) {
+ final Locale thisLocale = this.mLocaleList.get(i);
+ final Locale thatLocale = that.mLocaleList.get(i);
+ n = thisLocale.getLanguage().compareTo(thatLocale.getLanguage());
+ if (n != 0) return n;
+ n = thisLocale.getCountry().compareTo(thatLocale.getCountry());
+ if (n != 0) return n;
+ n = thisLocale.getVariant().compareTo(thatLocale.getVariant());
+ if (n != 0) return n;
+ n = thisLocale.toLanguageTag().compareTo(thatLocale.toLanguageTag());
+ if (n != 0) return n;
+ }
+ n = this.mLocaleList.size() - that.mLocaleList.size();
+ if (n != 0) return n;
+ }
+
+ n = this.touchscreen - that.touchscreen;
+ if (n != 0) return n;
+ n = this.keyboard - that.keyboard;
+ if (n != 0) return n;
+ n = this.keyboardHidden - that.keyboardHidden;
+ if (n != 0) return n;
+ n = this.hardKeyboardHidden - that.hardKeyboardHidden;
+ if (n != 0) return n;
+ n = this.navigation - that.navigation;
+ if (n != 0) return n;
+ n = this.navigationHidden - that.navigationHidden;
+ if (n != 0) return n;
+ n = this.orientation - that.orientation;
+ if (n != 0) return n;
+ n = this.colorMode - that.colorMode;
+ if (n != 0) return n;
+ n = this.screenLayout - that.screenLayout;
+ if (n != 0) return n;
+ n = this.uiMode - that.uiMode;
+ if (n != 0) return n;
+ n = this.screenWidthDp - that.screenWidthDp;
+ if (n != 0) return n;
+ n = this.screenHeightDp - that.screenHeightDp;
+ if (n != 0) return n;
+ n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
+ if (n != 0) return n;
+ n = this.densityDpi - that.densityDpi;
+ if (n != 0) return n;
+ n = this.assetsSeq - that.assetsSeq;
+ if (n != 0) return n;
+ n = windowConfiguration.compareTo(that.windowConfiguration);
+ if (n != 0) return n;
+ n = this.fontWeightAdjustment - that.fontWeightAdjustment;
+ if (n != 0) return n;
+
+ // if (n != 0) return n;
+ return n;
+ }
+
+ public boolean equals(Configuration that) {
+ if (that == null) return false;
+ if (that == this) return true;
+ return this.compareTo(that) == 0;
+ }
+
+ public boolean equals(@Nullable Object that) {
+ try {
+ return equals((Configuration)that);
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Float.floatToIntBits(fontScale);
+ result = 31 * result + mcc;
+ result = 31 * result + mnc;
+ result = 31 * result + mLocaleList.hashCode();
+ result = 31 * result + touchscreen;
+ result = 31 * result + keyboard;
+ result = 31 * result + keyboardHidden;
+ result = 31 * result + hardKeyboardHidden;
+ result = 31 * result + navigation;
+ result = 31 * result + navigationHidden;
+ result = 31 * result + orientation;
+ result = 31 * result + screenLayout;
+ result = 31 * result + colorMode;
+ result = 31 * result + uiMode;
+ result = 31 * result + screenWidthDp;
+ result = 31 * result + screenHeightDp;
+ result = 31 * result + smallestScreenWidthDp;
+ result = 31 * result + densityDpi;
+ result = 31 * result + assetsSeq;
+ result = 31 * result + fontWeightAdjustment;
+ return result;
+ }
+
+ /**
+ * Get the locale list. This is the preferred way for getting the locales (instead of using
+ * the direct accessor to {@link #locale}, which would only provide the primary locale).
+ *
+ * @return The locale list.
+ */
+ public @NonNull LocaleList getLocales() {
+ fixUpLocaleList();
+ return mLocaleList;
+ }
+
+ /**
+ * Set the locale list. This is the preferred way for setting up the locales (instead of using
+ * the direct accessor or {@link #setLocale(Locale)}). This will also set the layout direction
+ * according to the first locale in the list.
+ *
+ * Note that the layout direction will always come from the first locale in the locale list,
+ * even if the locale is not supported by the resources (the resources may only support
+ * another locale further down the list which has a different direction).
+ *
+ * @param locales The locale list. If null, an empty LocaleList will be assigned.
+ */
+ public void setLocales(@Nullable LocaleList locales) {
+ mLocaleList = locales == null ? LocaleList.getEmptyLocaleList() : locales;
+ locale = mLocaleList.get(0);
+ setLayoutDirection(locale);
+ }
+
+ /**
+ * Set the locale list to a list of just one locale. This will also set the layout direction
+ * according to the locale.
+ *
+ * Note that after this is run, calling <code>.equals()</code> on the input locale and the
+ * {@link #locale} attribute would return <code>true</code> if they are not null, but there is
+ * no guarantee that they would be the same object.
+ *
+ * See also the note about layout direction in {@link #setLocales(LocaleList)}.
+ *
+ * @param loc The locale. Can be null.
+ */
+ public void setLocale(@Nullable Locale loc) {
+ setLocales(loc == null ? LocaleList.getEmptyLocaleList() : new LocaleList(loc));
+ }
+
+ /**
+ * @hide
+ *
+ * Clears the locale without changing layout direction.
+ */
+ public void clearLocales() {
+ mLocaleList = LocaleList.getEmptyLocaleList();
+ locale = null;
+ }
+
+ /**
+ * Return the layout direction. Will be either {@link View#LAYOUT_DIRECTION_LTR} or
+ * {@link View#LAYOUT_DIRECTION_RTL}.
+ *
+ * @return Returns {@link View#LAYOUT_DIRECTION_RTL} if the configuration
+ * is {@link #SCREENLAYOUT_LAYOUTDIR_RTL}, otherwise {@link View#LAYOUT_DIRECTION_LTR}.
+ */
+ public int getLayoutDirection() {
+ return (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) == SCREENLAYOUT_LAYOUTDIR_RTL
+ ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+ }
+
+ /**
+ * Set the layout direction from a Locale.
+ *
+ * @param loc The Locale. If null will set the layout direction to
+ * {@link View#LAYOUT_DIRECTION_LTR}. If not null will set it to the layout direction
+ * corresponding to the Locale.
+ *
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
+ */
+ public void setLayoutDirection(Locale loc) {
+ // There is a "1" difference between the configuration values for
+ // layout direction and View constants for layout direction, just add "1".
+ final int layoutDirection = 1 + TextUtils.getLayoutDirectionFromLocale(loc);
+ screenLayout = (screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK)|
+ (layoutDirection << SCREENLAYOUT_LAYOUTDIR_SHIFT);
+ }
+
+ private static int getScreenLayoutNoDirection(int screenLayout) {
+ return screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK;
+ }
+
+ /**
+ * Return whether the screen has a round shape. Apps may choose to change styling based
+ * on this property, such as the alignment or layout of text or informational icons.
+ *
+ * @return true if the screen is rounded, false otherwise
+ */
+ public boolean isScreenRound() {
+ return (screenLayout & SCREENLAYOUT_ROUND_MASK) == SCREENLAYOUT_ROUND_YES;
+ }
+
+ /**
+ * Return whether the screen has a wide color gamut and wide color gamut rendering
+ * is supported by this device.
+ *
+ * When true, it implies the screen is colorspace aware but not
+ * necessarily color-managed. The final colors may still be changed by the
+ * screen depending on user settings.
+ *
+ * @return true if the screen has a wide color gamut and wide color gamut rendering
+ * is supported, false otherwise
+ */
+ public boolean isScreenWideColorGamut() {
+ return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES;
+ }
+
+ /**
+ * Return whether the screen has a high dynamic range.
+ *
+ * @return true if the screen has a high dynamic range, false otherwise
+ */
+ public boolean isScreenHdr() {
+ return (colorMode & COLOR_MODE_HDR_MASK) == COLOR_MODE_HDR_YES;
+ }
+
+ /**
+ *
+ * @hide
+ */
+ public static String localesToResourceQualifier(LocaleList locs) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < locs.size(); i++) {
+ final Locale loc = locs.get(i);
+ final int l = loc.getLanguage().length();
+ if (l == 0) {
+ continue;
+ }
+ final int s = loc.getScript().length();
+ final int c = loc.getCountry().length();
+ final int v = loc.getVariant().length();
+ // We ignore locale extensions, since they are not supported by AAPT
+
+ if (sb.length() != 0) {
+ sb.append(",");
+ }
+ if (l == 2 && s == 0 && (c == 0 || c == 2) && v == 0) {
+ // Traditional locale format: xx or xx-rYY
+ sb.append(loc.getLanguage());
+ if (c == 2) {
+ sb.append("-r").append(loc.getCountry());
+ }
+ } else {
+ sb.append("b+");
+ sb.append(loc.getLanguage());
+ if (s != 0) {
+ sb.append("+");
+ sb.append(loc.getScript());
+ }
+ if (c != 0) {
+ sb.append("+");
+ sb.append(loc.getCountry());
+ }
+ if (v != 0) {
+ sb.append("+");
+ sb.append(loc.getVariant());
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Returns a string representation of the configuration that can be parsed
+ * by build tools (like AAPT), without display metrics included
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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) {
+ parts.add("mcc" + config.mcc);
+ if (config.mnc != 0) {
+ parts.add("mnc" + config.mnc);
+ }
+ }
+
+ if (!config.mLocaleList.isEmpty()) {
+ final String resourceQualifier = localesToResourceQualifier(config.mLocaleList);
+ if (!resourceQualifier.isEmpty()) {
+ parts.add(resourceQualifier);
+ }
+ }
+
+ switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) {
+ case Configuration.SCREENLAYOUT_LAYOUTDIR_LTR:
+ parts.add("ldltr");
+ break;
+ case Configuration.SCREENLAYOUT_LAYOUTDIR_RTL:
+ parts.add("ldrtl");
+ break;
+ default:
+ break;
+ }
+
+ if (config.smallestScreenWidthDp != 0) {
+ parts.add("sw" + config.smallestScreenWidthDp + "dp");
+ }
+
+ if (config.screenWidthDp != 0) {
+ parts.add("w" + config.screenWidthDp + "dp");
+ }
+
+ if (config.screenHeightDp != 0) {
+ parts.add("h" + config.screenHeightDp + "dp");
+ }
+
+ switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) {
+ case Configuration.SCREENLAYOUT_SIZE_SMALL:
+ parts.add("small");
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_NORMAL:
+ parts.add("normal");
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_LARGE:
+ parts.add("large");
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+ parts.add("xlarge");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) {
+ case Configuration.SCREENLAYOUT_LONG_YES:
+ parts.add("long");
+ break;
+ case Configuration.SCREENLAYOUT_LONG_NO:
+ parts.add("notlong");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.screenLayout & Configuration.SCREENLAYOUT_ROUND_MASK) {
+ case Configuration.SCREENLAYOUT_ROUND_YES:
+ parts.add("round");
+ break;
+ case Configuration.SCREENLAYOUT_ROUND_NO:
+ parts.add("notround");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) {
+ case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES:
+ parts.add("widecg");
+ break;
+ case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO:
+ parts.add("nowidecg");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) {
+ case Configuration.COLOR_MODE_HDR_YES:
+ parts.add("highdr");
+ break;
+ case Configuration.COLOR_MODE_HDR_NO:
+ parts.add("lowdr");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.orientation) {
+ case Configuration.ORIENTATION_LANDSCAPE:
+ parts.add("land");
+ break;
+ case Configuration.ORIENTATION_PORTRAIT:
+ parts.add("port");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) {
+ case Configuration.UI_MODE_TYPE_APPLIANCE:
+ parts.add("appliance");
+ break;
+ case Configuration.UI_MODE_TYPE_DESK:
+ parts.add("desk");
+ break;
+ case Configuration.UI_MODE_TYPE_TELEVISION:
+ parts.add("television");
+ break;
+ case Configuration.UI_MODE_TYPE_CAR:
+ parts.add("car");
+ break;
+ case Configuration.UI_MODE_TYPE_WATCH:
+ parts.add("watch");
+ break;
+ case Configuration.UI_MODE_TYPE_VR_HEADSET:
+ parts.add("vrheadset");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
+ case Configuration.UI_MODE_NIGHT_YES:
+ parts.add("night");
+ break;
+ case Configuration.UI_MODE_NIGHT_NO:
+ parts.add("notnight");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.densityDpi) {
+ case DENSITY_DPI_UNDEFINED:
+ break;
+ case 120:
+ parts.add("ldpi");
+ break;
+ case 160:
+ parts.add("mdpi");
+ break;
+ case 213:
+ parts.add("tvdpi");
+ break;
+ case 240:
+ parts.add("hdpi");
+ break;
+ case 320:
+ parts.add("xhdpi");
+ break;
+ case 480:
+ parts.add("xxhdpi");
+ break;
+ case 640:
+ parts.add("xxxhdpi");
+ break;
+ case DENSITY_DPI_ANY:
+ parts.add("anydpi");
+ break;
+ case DENSITY_DPI_NONE:
+ parts.add("nodpi");
+ break;
+ default:
+ parts.add(config.densityDpi + "dpi");
+ break;
+ }
+
+ switch (config.touchscreen) {
+ case Configuration.TOUCHSCREEN_NOTOUCH:
+ parts.add("notouch");
+ break;
+ case Configuration.TOUCHSCREEN_FINGER:
+ parts.add("finger");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.keyboardHidden) {
+ case Configuration.KEYBOARDHIDDEN_NO:
+ parts.add("keysexposed");
+ break;
+ case Configuration.KEYBOARDHIDDEN_YES:
+ parts.add("keyshidden");
+ break;
+ case Configuration.KEYBOARDHIDDEN_SOFT:
+ parts.add("keyssoft");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.keyboard) {
+ case Configuration.KEYBOARD_NOKEYS:
+ parts.add("nokeys");
+ break;
+ case Configuration.KEYBOARD_QWERTY:
+ parts.add("qwerty");
+ break;
+ case Configuration.KEYBOARD_12KEY:
+ parts.add("12key");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.navigationHidden) {
+ case Configuration.NAVIGATIONHIDDEN_NO:
+ parts.add("navexposed");
+ break;
+ case Configuration.NAVIGATIONHIDDEN_YES:
+ parts.add("navhidden");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.navigation) {
+ case Configuration.NAVIGATION_NONAV:
+ parts.add("nonav");
+ break;
+ case Configuration.NAVIGATION_DPAD:
+ parts.add("dpad");
+ break;
+ case Configuration.NAVIGATION_TRACKBALL:
+ parts.add("trackball");
+ break;
+ case Configuration.NAVIGATION_WHEEL:
+ parts.add("wheel");
+ break;
+ default:
+ 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);
+ }
+
+ /**
+ * Generate a delta Configuration between <code>base</code> and <code>change</code>. The
+ * resulting delta can be used with {@link #updateFrom(Configuration)}.
+ * <p />
+ * Caveat: If the any of the Configuration's members becomes undefined, then
+ * {@link #updateFrom(Configuration)} will treat it as a no-op and not update that member.
+ *
+ * This is fine for device configurations as no member is ever undefined.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public static Configuration generateDelta(Configuration base, Configuration change) {
+ final Configuration delta = new Configuration();
+ if (base.fontScale != change.fontScale) {
+ delta.fontScale = change.fontScale;
+ }
+
+ if (base.mcc != change.mcc) {
+ delta.mcc = change.mcc;
+ }
+
+ if (base.mnc != change.mnc) {
+ delta.mnc = change.mnc;
+ }
+
+ base.fixUpLocaleList();
+ change.fixUpLocaleList();
+ if (!base.mLocaleList.equals(change.mLocaleList)) {
+ delta.mLocaleList = change.mLocaleList;
+ delta.locale = change.locale;
+ }
+
+ if (base.touchscreen != change.touchscreen) {
+ delta.touchscreen = change.touchscreen;
+ }
+
+ if (base.keyboard != change.keyboard) {
+ delta.keyboard = change.keyboard;
+ }
+
+ if (base.keyboardHidden != change.keyboardHidden) {
+ delta.keyboardHidden = change.keyboardHidden;
+ }
+
+ if (base.navigation != change.navigation) {
+ delta.navigation = change.navigation;
+ }
+
+ if (base.navigationHidden != change.navigationHidden) {
+ delta.navigationHidden = change.navigationHidden;
+ }
+
+ if (base.orientation != change.orientation) {
+ delta.orientation = change.orientation;
+ }
+
+ if ((base.screenLayout & SCREENLAYOUT_SIZE_MASK) !=
+ (change.screenLayout & SCREENLAYOUT_SIZE_MASK)) {
+ delta.screenLayout |= change.screenLayout & SCREENLAYOUT_SIZE_MASK;
+ }
+
+ if ((base.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK) !=
+ (change.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
+ delta.screenLayout |= change.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
+ }
+
+ if ((base.screenLayout & SCREENLAYOUT_LONG_MASK) !=
+ (change.screenLayout & SCREENLAYOUT_LONG_MASK)) {
+ delta.screenLayout |= change.screenLayout & SCREENLAYOUT_LONG_MASK;
+ }
+
+ if ((base.screenLayout & SCREENLAYOUT_ROUND_MASK) !=
+ (change.screenLayout & SCREENLAYOUT_ROUND_MASK)) {
+ delta.screenLayout |= change.screenLayout & SCREENLAYOUT_ROUND_MASK;
+ }
+
+ if ((base.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ (change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ delta.colorMode |= change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK;
+ }
+
+ if ((base.colorMode & COLOR_MODE_HDR_MASK) !=
+ (change.colorMode & COLOR_MODE_HDR_MASK)) {
+ delta.colorMode |= change.colorMode & COLOR_MODE_HDR_MASK;
+ }
+
+ if ((base.uiMode & UI_MODE_TYPE_MASK) != (change.uiMode & UI_MODE_TYPE_MASK)) {
+ delta.uiMode |= change.uiMode & UI_MODE_TYPE_MASK;
+ }
+
+ if ((base.uiMode & UI_MODE_NIGHT_MASK) != (change.uiMode & UI_MODE_NIGHT_MASK)) {
+ delta.uiMode |= change.uiMode & UI_MODE_NIGHT_MASK;
+ }
+
+ if (base.screenWidthDp != change.screenWidthDp) {
+ delta.screenWidthDp = change.screenWidthDp;
+ }
+
+ if (base.screenHeightDp != change.screenHeightDp) {
+ delta.screenHeightDp = change.screenHeightDp;
+ }
+
+ if (base.smallestScreenWidthDp != change.smallestScreenWidthDp) {
+ delta.smallestScreenWidthDp = change.smallestScreenWidthDp;
+ }
+
+ if (base.densityDpi != change.densityDpi) {
+ delta.densityDpi = change.densityDpi;
+ }
+
+ if (base.assetsSeq != change.assetsSeq) {
+ delta.assetsSeq = change.assetsSeq;
+ }
+
+ if (!base.windowConfiguration.equals(change.windowConfiguration)) {
+ delta.windowConfiguration.setTo(change.windowConfiguration);
+ }
+
+ if (base.fontWeightAdjustment != change.fontWeightAdjustment) {
+ delta.fontWeightAdjustment = change.fontWeightAdjustment;
+ }
+ return delta;
+ }
+
+ private static final String XML_ATTR_FONT_SCALE = "fs";
+ private static final String XML_ATTR_MCC = "mcc";
+ private static final String XML_ATTR_MNC = "mnc";
+ private static final String XML_ATTR_LOCALES = "locales";
+ private static final String XML_ATTR_TOUCHSCREEN = "touch";
+ private static final String XML_ATTR_KEYBOARD = "key";
+ private static final String XML_ATTR_KEYBOARD_HIDDEN = "keyHid";
+ private static final String XML_ATTR_HARD_KEYBOARD_HIDDEN = "hardKeyHid";
+ private static final String XML_ATTR_NAVIGATION = "nav";
+ private static final String XML_ATTR_NAVIGATION_HIDDEN = "navHid";
+ private static final String XML_ATTR_ORIENTATION = "ori";
+ private static final String XML_ATTR_ROTATION = "rot";
+ private static final String XML_ATTR_SCREEN_LAYOUT = "scrLay";
+ private static final String XML_ATTR_COLOR_MODE = "clrMod";
+ private static final String XML_ATTR_UI_MODE = "ui";
+ private static final String XML_ATTR_SCREEN_WIDTH = "width";
+ private static final String XML_ATTR_SCREEN_HEIGHT = "height";
+ private static final String XML_ATTR_SMALLEST_WIDTH = "sw";
+ private static final String XML_ATTR_DENSITY = "density";
+ private static final String XML_ATTR_APP_BOUNDS = "app_bounds";
+ private static final String XML_ATTR_FONT_WEIGHT_ADJUSTMENT = "fontWeightAdjustment";
+
+ /**
+ * Reads the attributes corresponding to Configuration member fields from the Xml parser.
+ * The parser is expected to be on a tag which has Configuration attributes.
+ *
+ * @param parser The Xml parser from which to read attributes.
+ * @param configOut The Configuration to populate from the Xml attributes.
+ * {@hide}
+ */
+ public static void readXmlAttrs(XmlPullParser parser, Configuration configOut)
+ throws XmlPullParserException, IOException {
+ configOut.fontScale = Float.intBitsToFloat(
+ XmlUtils.readIntAttribute(parser, XML_ATTR_FONT_SCALE, 0));
+ configOut.mcc = XmlUtils.readIntAttribute(parser, XML_ATTR_MCC, 0);
+ configOut.mnc = XmlUtils.readIntAttribute(parser, XML_ATTR_MNC, 0);
+
+ final String localesStr = XmlUtils.readStringAttribute(parser, XML_ATTR_LOCALES);
+ configOut.mLocaleList = LocaleList.forLanguageTags(localesStr);
+ configOut.locale = configOut.mLocaleList.get(0);
+
+ configOut.touchscreen = XmlUtils.readIntAttribute(parser, XML_ATTR_TOUCHSCREEN,
+ TOUCHSCREEN_UNDEFINED);
+ configOut.keyboard = XmlUtils.readIntAttribute(parser, XML_ATTR_KEYBOARD,
+ KEYBOARD_UNDEFINED);
+ configOut.keyboardHidden = XmlUtils.readIntAttribute(parser, XML_ATTR_KEYBOARD_HIDDEN,
+ KEYBOARDHIDDEN_UNDEFINED);
+ configOut.hardKeyboardHidden =
+ XmlUtils.readIntAttribute(parser, XML_ATTR_HARD_KEYBOARD_HIDDEN,
+ HARDKEYBOARDHIDDEN_UNDEFINED);
+ configOut.navigation = XmlUtils.readIntAttribute(parser, XML_ATTR_NAVIGATION,
+ NAVIGATION_UNDEFINED);
+ configOut.navigationHidden = XmlUtils.readIntAttribute(parser, XML_ATTR_NAVIGATION_HIDDEN,
+ NAVIGATIONHIDDEN_UNDEFINED);
+ configOut.orientation = XmlUtils.readIntAttribute(parser, XML_ATTR_ORIENTATION,
+ ORIENTATION_UNDEFINED);
+ configOut.screenLayout = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_LAYOUT,
+ SCREENLAYOUT_UNDEFINED);
+ configOut.colorMode = XmlUtils.readIntAttribute(parser, XML_ATTR_COLOR_MODE,
+ COLOR_MODE_UNDEFINED);
+ configOut.uiMode = XmlUtils.readIntAttribute(parser, XML_ATTR_UI_MODE, 0);
+ configOut.screenWidthDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_WIDTH,
+ SCREEN_WIDTH_DP_UNDEFINED);
+ configOut.screenHeightDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_HEIGHT,
+ SCREEN_HEIGHT_DP_UNDEFINED);
+ configOut.smallestScreenWidthDp =
+ XmlUtils.readIntAttribute(parser, XML_ATTR_SMALLEST_WIDTH,
+ SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ configOut.densityDpi = XmlUtils.readIntAttribute(parser, XML_ATTR_DENSITY,
+ DENSITY_DPI_UNDEFINED);
+ configOut.fontWeightAdjustment = XmlUtils.readIntAttribute(parser,
+ XML_ATTR_FONT_WEIGHT_ADJUSTMENT, FONT_WEIGHT_ADJUSTMENT_UNDEFINED);
+
+ // For persistence, we don't care about assetsSeq and WindowConfiguration, so do not read it
+ // out.
+ }
+}
diff --git a/android/content/res/ConfigurationBoundResourceCache.java b/android/content/res/ConfigurationBoundResourceCache.java
new file mode 100644
index 0000000..5e10a57
--- /dev/null
+++ b/android/content/res/ConfigurationBoundResourceCache.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo.Config;
+
+/**
+ * A Cache class which can be used to cache resource objects that are easy to clone but more
+ * expensive to inflate.
+ *
+ * @hide For internal use only.
+ */
+public class ConfigurationBoundResourceCache<T> extends ThemedResourceCache<ConstantState<T>> {
+
+ @UnsupportedAppUsage
+ public ConfigurationBoundResourceCache() {
+ }
+
+ /**
+ * If the resource is cached, creates and returns a new instance of it.
+ *
+ * @param key a key that uniquely identifies the drawable resource
+ * @param resources a Resources object from which to create new instances.
+ * @param theme the theme where the resource will be used
+ * @return a new instance of the resource, or {@code null} if not in
+ * the cache
+ */
+ public T getInstance(long key, Resources resources, Resources.Theme theme) {
+ final ConstantState<T> entry = get(key, theme);
+ if (entry != null) {
+ return entry.newInstance(resources, theme);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean shouldInvalidateEntry(ConstantState<T> entry, @Config int configChanges) {
+ return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
+ }
+}
diff --git a/android/content/res/ConstantState.java b/android/content/res/ConstantState.java
new file mode 100644
index 0000000..09d4a59
--- /dev/null
+++ b/android/content/res/ConstantState.java
@@ -0,0 +1,63 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package android.content.res;
+
+import android.content.pm.ActivityInfo.Config;
+
+/**
+ * A cache class that can provide new instances of a particular resource which may change
+ * depending on the current {@link Resources.Theme} or {@link Configuration}.
+ * <p>
+ * A constant state should be able to return a bitmask of changing configurations, which
+ * identifies the type of configuration changes that may invalidate this resource. These
+ * configuration changes can be obtained from {@link android.util.TypedValue}. Entities such as
+ * {@link android.animation.Animator} also provide a changing configuration method to include
+ * their dependencies (e.g. An AnimatorSet's changing configuration is the union of the
+ * changing configurations of each Animator in the set)
+ * @hide
+ */
+abstract public class ConstantState<T> {
+
+ /**
+ * Return a bit mask of configuration changes that will impact
+ * this resource (and thus require completely reloading it).
+ */
+ abstract public @Config int getChangingConfigurations();
+
+ /**
+ * Create a new instance without supplying resources the caller
+ * is running in.
+ */
+ public abstract T newInstance();
+
+ /**
+ * Create a new instance from its constant state. This
+ * must be implemented for resources that change based on the target
+ * density of their caller (that is depending on whether it is
+ * in compatibility mode).
+ */
+ public T newInstance(Resources res) {
+ return newInstance();
+ }
+
+ /**
+ * Create a new instance from its constant state. This must be
+ * implemented for resources that can have a theme applied.
+ */
+ public T newInstance(Resources res, Resources.Theme theme) {
+ return newInstance(res);
+ }
+}
diff --git a/android/content/res/DrawableCache.java b/android/content/res/DrawableCache.java
new file mode 100644
index 0000000..d0ebe33
--- /dev/null
+++ b/android/content/res/DrawableCache.java
@@ -0,0 +1,55 @@
+/*
+ * 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.res;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+/**
+ * Class which can be used to cache Drawable resources against a theme.
+ */
+class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
+
+ @UnsupportedAppUsage
+ DrawableCache() {
+ }
+
+ /**
+ * If the resource is cached, creates and returns a new instance of it.
+ *
+ * @param key a key that uniquely identifies the drawable resource
+ * @param resources a Resources object from which to create new instances.
+ * @param theme the theme where the resource will be used
+ * @return a new instance of the resource, or {@code null} if not in
+ * the cache
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
+ final Drawable.ConstantState entry = get(key, theme);
+ if (entry != null) {
+ return entry.newDrawable(resources, theme);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) {
+ return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
+ }
+}
diff --git a/android/content/res/FontResourcesParser.java b/android/content/res/FontResourcesParser.java
new file mode 100644
index 0000000..ecd240d
--- /dev/null
+++ b/android/content/res/FontResourcesParser.java
@@ -0,0 +1,266 @@
+/*
+ * 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 android.annotation.Nullable;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Parser for xml type font resources.
+ * @hide
+ */
+public class FontResourcesParser {
+ private static final String TAG = "FontResourcesParser";
+
+ // A class represents single entry of font-family in xml file.
+ public interface FamilyResourceEntry {}
+
+ // A class represents font provider based font-family element in xml file.
+ public static final class ProviderResourceEntry implements FamilyResourceEntry {
+ private final @NonNull String mProviderAuthority;
+ private final @NonNull String mProviderPackage;
+ private final @NonNull String mQuery;
+ private final @Nullable String mSystemFontFamilyName;
+ private final @Nullable List<List<String>> mCerts;
+
+ public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
+ @NonNull String query, @Nullable List<List<String>> certs,
+ @Nullable String systemFontFamilyName) {
+ mProviderAuthority = authority;
+ mProviderPackage = pkg;
+ mQuery = query;
+ mCerts = certs;
+ mSystemFontFamilyName = systemFontFamilyName;
+ }
+
+ public @NonNull String getAuthority() {
+ return mProviderAuthority;
+ }
+
+ public @NonNull String getPackage() {
+ return mProviderPackage;
+ }
+
+ public @NonNull String getQuery() {
+ return mQuery;
+ }
+
+ public @NonNull String getSystemFontFamilyName() {
+ return mSystemFontFamilyName;
+ }
+
+ public @Nullable List<List<String>> getCerts() {
+ return mCerts;
+ }
+ }
+
+ // A class represents font element in xml file which points a file in resource.
+ public static final class FontFileResourceEntry {
+ public static final int RESOLVE_BY_FONT_TABLE = Typeface.RESOLVE_BY_FONT_TABLE;
+ public static final int UPRIGHT = 0;
+ public static final int ITALIC = 1;
+
+ private final @NonNull String mFileName;
+ private int mWeight;
+ private int mItalic;
+ private int mTtcIndex;
+ private String mVariationSettings;
+ private int mResourceId;
+
+ public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
+ @Nullable String variationSettings, int ttcIndex) {
+ mFileName = fileName;
+ mWeight = weight;
+ mItalic = italic;
+ mVariationSettings = variationSettings;
+ mTtcIndex = ttcIndex;
+ }
+
+ public @NonNull String getFileName() {
+ return mFileName;
+ }
+
+ public int getWeight() {
+ return mWeight;
+ }
+
+ public int getItalic() {
+ return mItalic;
+ }
+
+ public @Nullable String getVariationSettings() {
+ return mVariationSettings;
+ }
+
+ public int getTtcIndex() {
+ return mTtcIndex;
+ }
+ }
+
+ // A class represents file based font-family element in xml file.
+ public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry {
+ private final @NonNull FontFileResourceEntry[] mEntries;
+
+ public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) {
+ mEntries = entries;
+ }
+
+ public @NonNull FontFileResourceEntry[] getEntries() {
+ return mEntries;
+ }
+ }
+
+ public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type=parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Empty loop.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+ return readFamilies(parser, resources);
+ }
+
+ private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser,
+ Resources resources) throws XmlPullParserException, IOException {
+ parser.require(XmlPullParser.START_TAG, null, "font-family");
+ String tag = parser.getName();
+ FamilyResourceEntry result = null;
+ if (tag.equals("font-family")) {
+ return readFamily(parser, resources);
+ } else {
+ skip(parser);
+ Log.e(TAG, "Failed to find font-family tag");
+ return null;
+ }
+ }
+
+ private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser,
+ Resources resources) throws XmlPullParserException, IOException {
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
+ String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
+ String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
+ String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
+ int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
+ String systemFontFamilyName = array.getString(
+ R.styleable.FontFamily_fontProviderSystemFontFamily);
+ array.recycle();
+ if (authority != null && providerPackage != null && query != null) {
+ while (parser.next() != XmlPullParser.END_TAG) {
+ skip(parser);
+ }
+ List<List<String>> certs = null;
+ if (certsId != 0) {
+ TypedArray typedArray = resources.obtainTypedArray(certsId);
+ if (typedArray.length() > 0) {
+ certs = new ArrayList<>();
+ boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
+ if (isArrayOfArrays) {
+ for (int i = 0; i < typedArray.length(); i++) {
+ int certId = typedArray.getResourceId(i, 0);
+ String[] certsArray = resources.getStringArray(certId);
+ List<String> certsList = Arrays.asList(certsArray);
+ certs.add(certsList);
+ }
+ } else {
+ String[] certsArray = resources.getStringArray(certsId);
+ List<String> certsList = Arrays.asList(certsArray);
+ certs.add(certsList);
+ }
+ }
+ }
+ return new ProviderResourceEntry(
+ authority,
+ providerPackage,
+ query,
+ certs,
+ systemFontFamilyName
+ );
+ }
+ List<FontFileResourceEntry> fonts = new ArrayList<>();
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ String tag = parser.getName();
+ if (tag.equals("font")) {
+ final FontFileResourceEntry entry = readFont(parser, resources);
+ if (entry != null) {
+ fonts.add(entry);
+ }
+ } else {
+ skip(parser);
+ }
+ }
+ if (fonts.isEmpty()) {
+ return null;
+ }
+ return new FontFamilyFilesResourceEntry(fonts.toArray(
+ new FontFileResourceEntry[fonts.size()]));
+ }
+
+ private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources)
+ throws XmlPullParserException, IOException {
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
+ int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight,
+ Typeface.RESOLVE_BY_FONT_TABLE);
+ int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
+ FontFileResourceEntry.RESOLVE_BY_FONT_TABLE);
+ String variationSettings = array.getString(
+ R.styleable.FontFamilyFont_fontVariationSettings);
+ int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
+ String filename = array.getString(R.styleable.FontFamilyFont_font);
+ array.recycle();
+ while (parser.next() != XmlPullParser.END_TAG) {
+ skip(parser);
+ }
+ if (filename == null) {
+ return null;
+ }
+ return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
+ }
+
+ private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ int depth = 1;
+ while (depth > 0) {
+ switch (parser.next()) {
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ }
+ }
+ }
+}
diff --git a/android/content/res/GradientColor.java b/android/content/res/GradientColor.java
new file mode 100644
index 0000000..35ad503
--- /dev/null
+++ b/android/content/res/GradientColor.java
@@ -0,0 +1,601 @@
+/*
+ * 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.res;
+
+import android.annotation.ColorInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.Resources.Theme;
+
+import com.android.internal.R;
+import com.android.internal.util.GrowingArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.LinearGradient;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Lets you define a gradient color, which is used inside
+ * {@link android.graphics.drawable.VectorDrawable}.
+ *
+ * {@link android.content.res.GradientColor}s are created from XML resource files defined in the
+ * "color" subdirectory directory of an application's resource directory. The XML file contains
+ * a single "gradient" element with a number of attributes and elements inside. For example:
+ * <pre>
+ * <gradient xmlns:android="http://schemas.android.com/apk/res/android">
+ * <android:startColor="?android:attr/colorPrimary"/>
+ * <android:endColor="?android:attr/colorControlActivated"/>
+ * <.../>
+ * <android:type="linear"/>
+ * </gradient>
+ * </pre>
+ *
+ * This can describe either a {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ *
+ * Note that different attributes are relevant for different types of gradient.
+ * For example, android:gradientRadius is only applied to RadialGradient.
+ * android:centerX and android:centerY are only applied to SweepGradient or RadialGradient.
+ * android:startX, android:startY, android:endX and android:endY are only applied to LinearGradient.
+ *
+ * Also note if any color "item" element is defined, then startColor, centerColor and endColor will
+ * be ignored.
+ * @hide
+ */
+public class GradientColor extends ComplexColor {
+ private static final String TAG = "GradientColor";
+
+ private static final boolean DBG_GRADIENT = false;
+
+ @IntDef(prefix = { "TILE_MODE_" }, value = {
+ TILE_MODE_CLAMP,
+ TILE_MODE_REPEAT,
+ TILE_MODE_MIRROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface GradientTileMode {}
+
+ private static final int TILE_MODE_CLAMP = 0;
+ private static final int TILE_MODE_REPEAT = 1;
+ private static final int TILE_MODE_MIRROR = 2;
+
+ /** Lazily-created factory for this GradientColor. */
+ private GradientColorFactory mFactory;
+
+ private @Config int mChangingConfigurations;
+ private int mDefaultColor;
+
+ // After parsing all the attributes from XML, this shader is the ultimate result containing
+ // all the XML information.
+ private Shader mShader = null;
+
+ // Below are the attributes at the root element <gradient>.
+ // NOTE: they need to be copied in the copy constructor!
+ private int mGradientType = GradientDrawable.LINEAR_GRADIENT;
+
+ private float mCenterX = 0f;
+ private float mCenterY = 0f;
+
+ private float mStartX = 0f;
+ private float mStartY = 0f;
+ private float mEndX = 0f;
+ private float mEndY = 0f;
+
+ private int mStartColor = 0;
+ private int mCenterColor = 0;
+ private int mEndColor = 0;
+ private boolean mHasCenterColor = false;
+
+ private int mTileMode = 0; // Clamp mode.
+
+ private float mGradientRadius = 0f;
+
+ // Below are the attributes for the <item> element.
+ private int[] mItemColors;
+ private float[] mItemOffsets;
+
+ // Theme attributes for the root and item elements.
+ private int[] mThemeAttrs;
+ private int[][] mItemsThemeAttrs;
+
+ private GradientColor() {
+ }
+
+ private GradientColor(GradientColor copy) {
+ if (copy != null) {
+ mChangingConfigurations = copy.mChangingConfigurations;
+ mDefaultColor = copy.mDefaultColor;
+ mShader = copy.mShader;
+ mGradientType = copy.mGradientType;
+ mCenterX = copy.mCenterX;
+ mCenterY = copy.mCenterY;
+ mStartX = copy.mStartX;
+ mStartY = copy.mStartY;
+ mEndX = copy.mEndX;
+ mEndY = copy.mEndY;
+ mStartColor = copy.mStartColor;
+ mCenterColor = copy.mCenterColor;
+ mEndColor = copy.mEndColor;
+ mHasCenterColor = copy.mHasCenterColor;
+ mGradientRadius = copy.mGradientRadius;
+ mTileMode = copy.mTileMode;
+
+ if (copy.mItemColors != null) {
+ mItemColors = copy.mItemColors.clone();
+ }
+ if (copy.mItemOffsets != null) {
+ mItemOffsets = copy.mItemOffsets.clone();
+ }
+
+ if (copy.mThemeAttrs != null) {
+ mThemeAttrs = copy.mThemeAttrs.clone();
+ }
+ if (copy.mItemsThemeAttrs != null) {
+ mItemsThemeAttrs = copy.mItemsThemeAttrs.clone();
+ }
+ }
+ }
+
+ // Set the default to clamp mode.
+ private static Shader.TileMode parseTileMode(@GradientTileMode int tileMode) {
+ switch (tileMode) {
+ case TILE_MODE_CLAMP:
+ return Shader.TileMode.CLAMP;
+ case TILE_MODE_REPEAT:
+ return Shader.TileMode.REPEAT;
+ case TILE_MODE_MIRROR:
+ return Shader.TileMode.MIRROR;
+ default:
+ return Shader.TileMode.CLAMP;
+ }
+ }
+
+ /**
+ * Update the root level's attributes, either for inflate or applyTheme.
+ */
+ private void updateRootElementState(TypedArray a) {
+ // Extract the theme attributes, if any.
+ mThemeAttrs = a.extractThemeAttrs();
+
+ mStartX = a.getFloat(
+ R.styleable.GradientColor_startX, mStartX);
+ mStartY = a.getFloat(
+ R.styleable.GradientColor_startY, mStartY);
+ mEndX = a.getFloat(
+ R.styleable.GradientColor_endX, mEndX);
+ mEndY = a.getFloat(
+ R.styleable.GradientColor_endY, mEndY);
+
+ mCenterX = a.getFloat(
+ R.styleable.GradientColor_centerX, mCenterX);
+ mCenterY = a.getFloat(
+ R.styleable.GradientColor_centerY, mCenterY);
+
+ mGradientType = a.getInt(
+ R.styleable.GradientColor_type, mGradientType);
+
+ mStartColor = a.getColor(
+ R.styleable.GradientColor_startColor, mStartColor);
+ mHasCenterColor |= a.hasValue(
+ R.styleable.GradientColor_centerColor);
+ mCenterColor = a.getColor(
+ R.styleable.GradientColor_centerColor, mCenterColor);
+ mEndColor = a.getColor(
+ R.styleable.GradientColor_endColor, mEndColor);
+
+ mTileMode = a.getInt(
+ R.styleable.GradientColor_tileMode, mTileMode);
+
+ if (DBG_GRADIENT) {
+ Log.v(TAG, "hasCenterColor is " + mHasCenterColor);
+ if (mHasCenterColor) {
+ Log.v(TAG, "centerColor:" + mCenterColor);
+ }
+ Log.v(TAG, "startColor: " + mStartColor);
+ Log.v(TAG, "endColor: " + mEndColor);
+ Log.v(TAG, "tileMode: " + mTileMode);
+ }
+
+ mGradientRadius = a.getFloat(R.styleable.GradientColor_gradientRadius,
+ mGradientRadius);
+ }
+
+ /**
+ * Check if the XML content is valid.
+ *
+ * @throws XmlPullParserException if errors were found.
+ */
+ private void validateXmlContent() throws XmlPullParserException {
+ if (mGradientRadius <= 0
+ && mGradientType == GradientDrawable.RADIAL_GRADIENT) {
+ throw new XmlPullParserException(
+ "<gradient> tag requires 'gradientRadius' "
+ + "attribute with radial type");
+ }
+ }
+
+ /**
+ * The shader information will be applied to the native VectorDrawable's path.
+ * @hide
+ */
+ public Shader getShader() {
+ return mShader;
+ }
+
+ /**
+ * A public method to create GradientColor from a XML resource.
+ */
+ public static GradientColor createFromXml(Resources r, XmlResourceParser parser, Theme theme)
+ throws XmlPullParserException, IOException {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ return createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a GradientColor from that tag.
+ *
+ * @return A new GradientColor for the current tag.
+ * @throws XmlPullParserException if the current tag is not <gradient>
+ */
+ @NonNull
+ static GradientColor createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getName();
+ if (!name.equals("gradient")) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ": invalid gradient color tag " + name);
+ }
+
+ final GradientColor gradientColor = new GradientColor();
+ gradientColor.inflate(r, parser, attrs, theme);
+ return gradientColor;
+ }
+
+ /**
+ * Fill in this object based on the contents of an XML "gradient" element.
+ */
+ private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs, R.styleable.GradientColor);
+ updateRootElementState(a);
+ mChangingConfigurations |= a.getChangingConfigurations();
+ a.recycle();
+
+ // Check correctness and throw exception if errors found.
+ validateXmlContent();
+
+ inflateChildElements(r, parser, attrs, theme);
+
+ onColorsChange();
+ }
+
+ /**
+ * Inflates child elements "item"s for each color stop.
+ *
+ * Note that at root level, we need to save ThemeAttrs for theme applied later.
+ * Here similarly, at each child item, we need to save the theme's attributes, and apply theme
+ * later as applyItemsAttrsTheme().
+ */
+ private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @NonNull Theme theme)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth() + 1;
+ int type;
+ int depth;
+
+ // Pre-allocate the array with some size, for better performance.
+ float[] offsetList = new float[20];
+ int[] colorList = new int[offsetList.length];
+ int[][] themeAttrsList = new int[offsetList.length][];
+
+ int listSize = 0;
+ boolean hasUnresolvedAttrs = false;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth
+ || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (depth > innerDepth || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
+ R.styleable.GradientColorItem);
+ boolean hasColor = a.hasValue(R.styleable.GradientColorItem_color);
+ boolean hasOffset = a.hasValue(R.styleable.GradientColorItem_offset);
+ if (!hasColor || !hasOffset) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription()
+ + ": <item> tag requires a 'color' attribute and a 'offset' "
+ + "attribute!");
+ }
+
+ final int[] themeAttrs = a.extractThemeAttrs();
+ int color = a.getColor(R.styleable.GradientColorItem_color, 0);
+ float offset = a.getFloat(R.styleable.GradientColorItem_offset, 0);
+
+ if (DBG_GRADIENT) {
+ Log.v(TAG, "new item color " + color + " " + Integer.toHexString(color));
+ Log.v(TAG, "offset" + offset);
+ }
+ mChangingConfigurations |= a.getChangingConfigurations();
+ a.recycle();
+
+ if (themeAttrs != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ offsetList = GrowingArrayUtils.append(offsetList, listSize, offset);
+ themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
+ listSize++;
+ }
+ if (listSize > 0) {
+ if (hasUnresolvedAttrs) {
+ mItemsThemeAttrs = new int[listSize][];
+ System.arraycopy(themeAttrsList, 0, mItemsThemeAttrs, 0, listSize);
+ } else {
+ mItemsThemeAttrs = null;
+ }
+
+ mItemColors = new int[listSize];
+ mItemOffsets = new float[listSize];
+ System.arraycopy(colorList, 0, mItemColors, 0, listSize);
+ System.arraycopy(offsetList, 0, mItemOffsets, 0, listSize);
+ }
+ }
+
+ /**
+ * Apply theme to all the items.
+ */
+ private void applyItemsAttrsTheme(Theme t) {
+ if (mItemsThemeAttrs == null) {
+ return;
+ }
+
+ boolean hasUnresolvedAttrs = false;
+
+ final int[][] themeAttrsList = mItemsThemeAttrs;
+ final int N = themeAttrsList.length;
+ for (int i = 0; i < N; i++) {
+ if (themeAttrsList[i] != null) {
+ final TypedArray a = t.resolveAttributes(themeAttrsList[i],
+ R.styleable.GradientColorItem);
+
+ // Extract the theme attributes, if any, before attempting to
+ // read from the typed array. This prevents a crash if we have
+ // unresolved attrs.
+ themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
+ if (themeAttrsList[i] != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ mItemColors[i] = a.getColor(R.styleable.GradientColorItem_color, mItemColors[i]);
+ mItemOffsets[i] = a.getFloat(R.styleable.GradientColorItem_offset, mItemOffsets[i]);
+ if (DBG_GRADIENT) {
+ Log.v(TAG, "applyItemsAttrsTheme Colors[i] " + i + " " +
+ Integer.toHexString(mItemColors[i]));
+ Log.v(TAG, "Offsets[i] " + i + " " + mItemOffsets[i]);
+ }
+
+ // Account for any configuration changes.
+ mChangingConfigurations |= a.getChangingConfigurations();
+
+ a.recycle();
+ }
+ }
+
+ if (!hasUnresolvedAttrs) {
+ mItemsThemeAttrs = null;
+ }
+ }
+
+ private void onColorsChange() {
+ int[] tempColors = null;
+ float[] tempOffsets = null;
+
+ if (mItemColors != null) {
+ int length = mItemColors.length;
+ tempColors = new int[length];
+ tempOffsets = new float[length];
+
+ for (int i = 0; i < length; i++) {
+ tempColors[i] = mItemColors[i];
+ tempOffsets[i] = mItemOffsets[i];
+ }
+ } else {
+ if (mHasCenterColor) {
+ tempColors = new int[3];
+ tempColors[0] = mStartColor;
+ tempColors[1] = mCenterColor;
+ tempColors[2] = mEndColor;
+
+ tempOffsets = new float[3];
+ tempOffsets[0] = 0.0f;
+ // Since 0.5f is default value, try to take the one that isn't 0.5f
+ tempOffsets[1] = 0.5f;
+ tempOffsets[2] = 1f;
+ } else {
+ tempColors = new int[2];
+ tempColors[0] = mStartColor;
+ tempColors[1] = mEndColor;
+ }
+ }
+ if (tempColors.length < 2) {
+ Log.w(TAG, "<gradient> tag requires 2 color values specified!" + tempColors.length
+ + " " + tempColors);
+ }
+
+ if (mGradientType == GradientDrawable.LINEAR_GRADIENT) {
+ mShader = new LinearGradient(mStartX, mStartY, mEndX, mEndY, tempColors, tempOffsets,
+ parseTileMode(mTileMode));
+ } else {
+ if (mGradientType == GradientDrawable.RADIAL_GRADIENT) {
+ mShader = new RadialGradient(mCenterX, mCenterY, mGradientRadius, tempColors,
+ tempOffsets, parseTileMode(mTileMode));
+ } else {
+ mShader = new SweepGradient(mCenterX, mCenterY, tempColors, tempOffsets);
+ }
+ }
+ mDefaultColor = tempColors[0];
+ }
+
+ /**
+ * For Gradient color, the default color is not very useful, since the gradient will override
+ * the color information anyway.
+ */
+ @Override
+ @ColorInt
+ public int getDefaultColor() {
+ return mDefaultColor;
+ }
+
+ /**
+ * Similar to ColorStateList, setup constant state and its factory.
+ * @hide only for resource preloading
+ */
+ @Override
+ public ConstantState<ComplexColor> getConstantState() {
+ if (mFactory == null) {
+ mFactory = new GradientColorFactory(this);
+ }
+ return mFactory;
+ }
+
+ private static class GradientColorFactory extends ConstantState<ComplexColor> {
+ private final GradientColor mSrc;
+
+ public GradientColorFactory(GradientColor src) {
+ mSrc = src;
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mSrc.mChangingConfigurations;
+ }
+
+ @Override
+ public GradientColor newInstance() {
+ return mSrc;
+ }
+
+ @Override
+ public GradientColor newInstance(Resources res, Theme theme) {
+ return mSrc.obtainForTheme(theme);
+ }
+ }
+
+ /**
+ * Returns an appropriately themed gradient color.
+ *
+ * @param t the theme to apply
+ * @return a copy of the gradient color the theme applied, or the
+ * gradient itself if there were no unresolved theme
+ * attributes
+ * @hide only for resource preloading
+ */
+ @Override
+ public GradientColor obtainForTheme(Theme t) {
+ if (t == null || !canApplyTheme()) {
+ return this;
+ }
+
+ final GradientColor clone = new GradientColor(this);
+ clone.applyTheme(t);
+ return clone;
+ }
+
+ /**
+ * Returns a mask of the configuration parameters for which this gradient
+ * may change, requiring that it be re-created.
+ *
+ * @return a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}
+ *
+ * @see android.content.pm.ActivityInfo
+ */
+ public int getChangingConfigurations() {
+ return super.getChangingConfigurations() | mChangingConfigurations;
+ }
+
+ private void applyTheme(Theme t) {
+ if (mThemeAttrs != null) {
+ applyRootAttrsTheme(t);
+ }
+ if (mItemsThemeAttrs != null) {
+ applyItemsAttrsTheme(t);
+ }
+ onColorsChange();
+ }
+
+ private void applyRootAttrsTheme(Theme t) {
+ final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.GradientColor);
+ // mThemeAttrs will be set to null if if there are no theme attributes in the
+ // typed array.
+ mThemeAttrs = a.extractThemeAttrs(mThemeAttrs);
+ // merging the attributes update inside the updateRootElementState().
+ updateRootElementState(a);
+
+ // Account for any configuration changes.
+ mChangingConfigurations |= a.getChangingConfigurations();
+ a.recycle();
+ }
+
+
+ /**
+ * Returns whether a theme can be applied to this gradient color, which
+ * usually indicates that the gradient color has unresolved theme
+ * attributes.
+ *
+ * @return whether a theme can be applied to this gradient color.
+ * @hide only for resource preloading
+ */
+ @Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || mItemsThemeAttrs != null;
+ }
+
+}
diff --git a/android/content/res/ObbInfo.java b/android/content/res/ObbInfo.java
new file mode 100644
index 0000000..6cafb31
--- /dev/null
+++ b/android/content/res/ObbInfo.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Basic information about a Opaque Binary Blob (OBB) that reflects the info
+ * from the footer on the OBB file. This information may be manipulated by a
+ * developer with the <code>obbtool</code> program in the Android SDK.
+ */
+public class ObbInfo implements Parcelable {
+ /** Flag noting that this OBB is an overlay patch for a base OBB. */
+ public static final int OBB_OVERLAY = 1 << 0;
+
+ /**
+ * The canonical filename of the OBB.
+ */
+ public String filename;
+
+ /**
+ * The name of the package to which the OBB file belongs.
+ */
+ public String packageName;
+
+ /**
+ * The version of the package to which the OBB file belongs.
+ */
+ public int version;
+
+ /**
+ * The flags relating to the OBB.
+ */
+ public int flags;
+
+ /**
+ * The salt for the encryption algorithm.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public byte[] salt;
+
+ // Only allow things in this package to instantiate.
+ /* package */ ObbInfo() {
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ObbInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" packageName=");
+ sb.append(packageName);
+ sb.append(",version=");
+ sb.append(version);
+ sb.append(",flags=");
+ sb.append(flags);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ // Keep this in sync with writeToParcel() in ObbInfo.cpp
+ dest.writeString(filename);
+ dest.writeString(packageName);
+ dest.writeInt(version);
+ dest.writeInt(flags);
+ dest.writeByteArray(salt);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ObbInfo> CREATOR
+ = new Parcelable.Creator<ObbInfo>() {
+ public ObbInfo createFromParcel(Parcel source) {
+ return new ObbInfo(source);
+ }
+
+ public ObbInfo[] newArray(int size) {
+ return new ObbInfo[size];
+ }
+ };
+
+ private ObbInfo(Parcel source) {
+ filename = source.readString();
+ packageName = source.readString();
+ version = source.readInt();
+ flags = source.readInt();
+ salt = source.createByteArray();
+ }
+}
diff --git a/android/content/res/ObbScanner.java b/android/content/res/ObbScanner.java
new file mode 100644
index 0000000..1b38eea
--- /dev/null
+++ b/android/content/res/ObbScanner.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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 java.io.File;
+import java.io.IOException;
+
+/**
+ * Class to scan Opaque Binary Blob (OBB) files. Use this to get information
+ * about an OBB file for use in a program via {@link ObbInfo}.
+ */
+public class ObbScanner {
+ // Don't allow others to instantiate this class
+ private ObbScanner() {}
+
+ /**
+ * Scan a file for OBB information.
+ *
+ * @param filePath path to the OBB file to be scanned.
+ * @return ObbInfo object information corresponding to the file path
+ * @throws IllegalArgumentException if the OBB file couldn't be found
+ * @throws IOException if the OBB file couldn't be read
+ */
+ public static ObbInfo getObbInfo(String filePath) throws IOException {
+ if (filePath == null) {
+ throw new IllegalArgumentException("file path cannot be null");
+ }
+
+ final File obbFile = new File(filePath);
+ if (!obbFile.exists()) {
+ throw new IllegalArgumentException("OBB file does not exist: " + filePath);
+ }
+
+ /*
+ * XXX This will fail to find the real canonical path if bind mounts are
+ * used, but we don't use any bind mounts right now.
+ */
+ final String canonicalFilePath = obbFile.getCanonicalPath();
+
+ ObbInfo obbInfo = new ObbInfo();
+ obbInfo.filename = canonicalFilePath;
+ getObbInfo_native(canonicalFilePath, obbInfo);
+
+ return obbInfo;
+ }
+
+ private native static void getObbInfo_native(String filePath, ObbInfo obbInfo)
+ throws IOException;
+}
diff --git a/android/content/res/ResourceId.java b/android/content/res/ResourceId.java
new file mode 100644
index 0000000..3c7b5fc
--- /dev/null
+++ b/android/content/res/ResourceId.java
@@ -0,0 +1,38 @@
+/*
+ * 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.AnyRes;
+
+/**
+ * Provides a set of utility methods for dealing with Resource IDs.
+ * @hide
+ */
+public final class ResourceId {
+ /**
+ * Checks whether the integer {@code id} is a valid resource ID, as generated by AAPT.
+ * <p>Note that a negative integer is not necessarily an invalid resource ID, and custom
+ * validations that compare the {@code id} against {@code 0} are incorrect.</p>
+ * @param id The integer to validate.
+ * @return {@code true} if the integer is a valid resource ID.
+ */
+ public static boolean isValid(@AnyRes int id) {
+ // With the introduction of packages with IDs > 0x7f, resource IDs can be negative when
+ // represented as a signed Java int. Some legacy code assumes -1 is an invalid resource ID,
+ // despite the existing documentation.
+ return id != -1 && (id & 0xff000000) != 0 && (id & 0x00ff0000) != 0;
+ }
+}
diff --git a/android/content/res/Resources.java b/android/content/res/Resources.java
new file mode 100644
index 0000000..12e41e2
--- /dev/null
+++ b/android/content/res/Resources.java
@@ -0,0 +1,2605 @@
+/*
+ * Copyright (C) 2006 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.animation.Animator;
+import android.animation.StateListAnimator;
+import android.annotation.AnimRes;
+import android.annotation.AnimatorRes;
+import android.annotation.AnyRes;
+import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
+import android.annotation.BoolRes;
+import android.annotation.ColorInt;
+import android.annotation.ColorRes;
+import android.annotation.DimenRes;
+import android.annotation.DrawableRes;
+import android.annotation.FontRes;
+import android.annotation.FractionRes;
+import android.annotation.IntegerRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.PluralsRes;
+import android.annotation.RawRes;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
+import android.annotation.StyleableRes;
+import android.annotation.XmlRes;
+import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.loader.ResourcesLoader;
+import android.graphics.Movie;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Drawable.ConstantState;
+import android.graphics.drawable.DrawableInflater;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pools.SynchronizedPool;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Class for accessing an application's resources. This sits on top of the
+ * asset manager of the application (accessible through {@link #getAssets}) and
+ * provides a high-level API for getting typed data from the assets.
+ *
+ * <p>The Android resource system keeps track of all non-code assets associated with an
+ * application. You can use this class to access your application's resources. You can generally
+ * acquire the {@link android.content.res.Resources} instance associated with your application
+ * with {@link android.content.Context#getResources getResources()}.</p>
+ *
+ * <p>The Android SDK tools compile your application's resources into the application binary
+ * at build time. To use a resource, you must install it correctly in the source tree (inside
+ * your project's {@code res/} directory) and build your application. As part of the build
+ * process, the SDK tools generate symbols for each resource, which you can use in your application
+ * code to access the resources.</p>
+ *
+ * <p>Using application resources makes it easy to update various characteristics of your
+ * application without modifying code, and—by providing sets of alternative
+ * resources—enables you to optimize your application for a variety of device configurations
+ * (such as for different languages and screen sizes). This is an important aspect of developing
+ * Android applications that are compatible on different types of devices.</p>
+ *
+ * <p>After {@link Build.VERSION_CODES#R}, {@link Resources} must be obtained by
+ * {@link android.app.Activity} or {@link android.content.Context} created with
+ * {@link android.content.Context#createWindowContext(int, Bundle)}.
+ * {@link Application#getResources()} may report wrong values in multi-window or on secondary
+ * displays.
+ *
+ * <p>For more information about using resources, see the documentation about <a
+ * href="{@docRoot}guide/topics/resources/index.html">Application Resources</a>.</p>
+ */
+public class Resources {
+ /**
+ * The {@code null} resource ID. This denotes an invalid resource ID that is returned by the
+ * system when a resource is not found or the value is set to {@code @null} in XML.
+ */
+ public static final @AnyRes int ID_NULL = 0;
+
+ static final String TAG = "Resources";
+
+ private static final Object sSync = new Object();
+ private final Object mUpdateLock = new Object();
+
+ // Used by BridgeResources in layoutlib
+ @UnsupportedAppUsage
+ static Resources mSystem = null;
+
+ @UnsupportedAppUsage
+ private ResourcesImpl mResourcesImpl;
+
+ // Pool of TypedArrays targeted to this Resources object.
+ @UnsupportedAppUsage
+ final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
+
+ /** Used to inflate drawable objects from XML. */
+ @UnsupportedAppUsage
+ private DrawableInflater mDrawableInflater;
+
+ /** Used to override the returned adjustments of {@link #getDisplayAdjustments}. */
+ private DisplayAdjustments mOverrideDisplayAdjustments;
+
+ /** Lock object used to protect access to {@link #mTmpValue}. */
+ private final Object mTmpValueLock = new Object();
+
+ /** Single-item pool used to minimize TypedValue allocations. */
+ @UnsupportedAppUsage
+ private TypedValue mTmpValue = new TypedValue();
+
+ @UnsupportedAppUsage
+ final ClassLoader mClassLoader;
+
+ @GuardedBy("mUpdateLock")
+ private UpdateCallbacks mCallbacks = null;
+
+ /**
+ * WeakReferences to Themes that were constructed from this Resources object.
+ * We keep track of these in case our underlying implementation is changed, in which case
+ * the Themes must also get updated ThemeImpls.
+ */
+ private final ArrayList<WeakReference<Theme>> mThemeRefs = new ArrayList<>();
+
+ /**
+ * To avoid leaking WeakReferences to garbage collected Themes on the
+ * mThemeRefs list, we flush the list of stale references any time the
+ * mThemeRefNextFlushSize is reached.
+ */
+ private static final int MIN_THEME_REFS_FLUSH_SIZE = 32;
+ private int mThemeRefsNextFlushSize = MIN_THEME_REFS_FLUSH_SIZE;
+
+ private int mBaseApkAssetsSize;
+
+ /**
+ * Returns the most appropriate default theme for the specified target SDK version.
+ * <ul>
+ * <li>Below API 11: Gingerbread
+ * <li>APIs 12 thru 14: Holo
+ * <li>APIs 15 thru 23: Device default dark
+ * <li>APIs 24 and above: Device default light with dark action bar
+ * </ul>
+ *
+ * @param curTheme The current theme, or 0 if not specified.
+ * @param targetSdkVersion The target SDK version.
+ * @return A theme resource identifier
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
+ return selectSystemTheme(curTheme, targetSdkVersion,
+ com.android.internal.R.style.Theme,
+ com.android.internal.R.style.Theme_Holo,
+ com.android.internal.R.style.Theme_DeviceDefault,
+ com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
+ }
+
+ /** @hide */
+ public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
+ int dark, int deviceDefault) {
+ if (curTheme != ID_NULL) {
+ return curTheme;
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
+ return orig;
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return holo;
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.N) {
+ return dark;
+ }
+ return deviceDefault;
+ }
+
+ /**
+ * Return a global shared Resources object that provides access to only
+ * system resources (no application resources), is not configured for the
+ * current screen (can not use dimension units, does not change based on
+ * orientation, etc), and is not affected by Runtime Resource Overlay.
+ */
+ public static Resources getSystem() {
+ synchronized (sSync) {
+ Resources ret = mSystem;
+ if (ret == null) {
+ ret = new Resources();
+ mSystem = ret;
+ }
+ return ret;
+ }
+ }
+
+ /**
+ * This exception is thrown by the resource APIs when a requested resource
+ * can not be found.
+ */
+ public static class NotFoundException extends RuntimeException {
+ public NotFoundException() {
+ }
+
+ public NotFoundException(String name) {
+ super(name);
+ }
+
+ public NotFoundException(String name, Exception cause) {
+ super(name, cause);
+ }
+ }
+
+ /** @hide */
+ public interface UpdateCallbacks extends ResourcesLoader.UpdateCallbacks {
+ /**
+ * Invoked when a {@link Resources} instance has a {@link ResourcesLoader} added, removed,
+ * or reordered.
+ *
+ * @param resources the instance being updated
+ * @param newLoaders the new set of loaders for the instance
+ */
+ void onLoadersChanged(@NonNull Resources resources,
+ @NonNull List<ResourcesLoader> newLoaders);
+ }
+
+ /**
+ * Handler that propagates updates of the {@link Resources} instance to the underlying
+ * {@link AssetManager} when the Resources is not registered with a
+ * {@link android.app.ResourcesManager}.
+ * @hide
+ */
+ public class AssetManagerUpdateHandler implements UpdateCallbacks{
+
+ @Override
+ public void onLoadersChanged(@NonNull Resources resources,
+ @NonNull List<ResourcesLoader> newLoaders) {
+ Preconditions.checkArgument(Resources.this == resources);
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.clearAllCaches();
+ impl.getAssets().setLoaders(newLoaders);
+ }
+
+ @Override
+ public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
+ final ResourcesImpl impl = mResourcesImpl;
+ final AssetManager assets = impl.getAssets();
+ if (assets.getLoaders().contains(loader)) {
+ impl.clearAllCaches();
+ assets.setLoaders(assets.getLoaders());
+ }
+ }
+ }
+
+ /**
+ * Create a new Resources object on top of an existing set of assets in an
+ * AssetManager.
+ *
+ * @deprecated Resources should not be constructed by apps.
+ * See {@link android.content.Context#createConfigurationContext(Configuration)}.
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
+ * selecting/computing resource values.
+ * @param config Desired device configuration to consider when
+ * selecting/computing resource values (optional).
+ */
+ @Deprecated
+ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
+ this(null);
+ mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
+ }
+
+ /**
+ * Creates a new Resources object with CompatibilityInfo.
+ *
+ * @param classLoader class loader for the package used to load custom
+ * resource classes, may be {@code null} to use system
+ * class loader
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Resources(@Nullable ClassLoader classLoader) {
+ mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+ }
+
+ /**
+ * Only for creating the System resources.
+ */
+ @UnsupportedAppUsage
+ private Resources() {
+ this(null);
+
+ final DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+
+ final Configuration config = new Configuration();
+ config.setToDefaults();
+
+ mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
+ new DisplayAdjustments());
+ }
+
+ /**
+ * Set the underlying implementation (containing all the resources and caches)
+ * and updates all Theme implementations as well.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setImpl(ResourcesImpl impl) {
+ if (impl == mResourcesImpl) {
+ return;
+ }
+
+ mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
+ mResourcesImpl = impl;
+
+ // Rebase the ThemeImpls using the new ResourcesImpl.
+ synchronized (mThemeRefs) {
+ final int count = mThemeRefs.size();
+ for (int i = 0; i < count; i++) {
+ WeakReference<Theme> weakThemeRef = mThemeRefs.get(i);
+ Theme theme = weakThemeRef != null ? weakThemeRef.get() : null;
+ if (theme != null) {
+ theme.rebase(mResourcesImpl);
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ public void setCallbacks(UpdateCallbacks callbacks) {
+ if (mCallbacks != null) {
+ throw new IllegalStateException("callback already registered");
+ }
+
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public ResourcesImpl getImpl() {
+ return mResourcesImpl;
+ }
+
+ /**
+ * @hide
+ */
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * @return the inflater used to create drawable objects
+ * @hide Pending API finalization.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final DrawableInflater getDrawableInflater() {
+ if (mDrawableInflater == null) {
+ mDrawableInflater = new DrawableInflater(this, mClassLoader);
+ }
+ return mDrawableInflater;
+ }
+
+ /**
+ * Used by AnimatorInflater.
+ *
+ * @hide
+ */
+ public ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
+ return mResourcesImpl.getAnimatorCache();
+ }
+
+ /**
+ * Used by AnimatorInflater.
+ *
+ * @hide
+ */
+ public ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
+ return mResourcesImpl.getStateListAnimatorCache();
+ }
+
+ /**
+ * Return the string value associated with a particular resource ID. The
+ * returned object will be a String if this is a plain string; it will be
+ * some other type of CharSequence if it is styled.
+ * {@more}
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return CharSequence The string data associated with the resource, plus
+ * possibly styled text information.
+ */
+ @NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
+ CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("String resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+ /**
+ * Return the Typeface value associated with a particular resource ID.
+ * {@more}
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Typeface The Typeface data associated with the resource.
+ */
+ @NonNull public Typeface getFont(@FontRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ Typeface typeface = impl.loadFont(this, value, id);
+ if (typeface != null) {
+ return typeface;
+ }
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ throw new NotFoundException("Font resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+ @NonNull
+ Typeface getFont(@NonNull TypedValue value, @FontRes int id) throws NotFoundException {
+ return mResourcesImpl.loadFont(this, value, id);
+ }
+
+ /**
+ * @hide
+ */
+ public void preloadFonts(@ArrayRes int id) {
+ final TypedArray array = obtainTypedArray(id);
+ try {
+ final int size = array.length();
+ for (int i = 0; i < size; i++) {
+ array.getFont(i);
+ }
+ } finally {
+ array.recycle();
+ }
+ }
+
+ /**
+ * Returns the character sequence necessary for grammatically correct pluralization
+ * of the given resource ID for the given quantity.
+ * Note that the character sequence is selected based solely on grammatical necessity,
+ * and that such rules differ between languages. Do not assume you know which string
+ * will be returned for a given quantity. See
+ * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a>
+ * for more detail.
+ *
+ * @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.
+ * @param quantity The number used to get the correct string for the current language's
+ * plural rules.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return CharSequence The string data associated with the resource, plus
+ * possibly styled text information.
+ */
+ @NonNull
+ public CharSequence getQuantityText(@PluralsRes int id, int quantity)
+ throws NotFoundException {
+ return mResourcesImpl.getQuantityText(id, quantity);
+ }
+
+ /**
+ * Return the string value associated with a particular resource ID. It
+ * will be stripped of any styled text information.
+ * {@more}
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return String The string data associated with the resource,
+ * stripped of styled text information.
+ */
+ @NonNull
+ public String getString(@StringRes int id) throws NotFoundException {
+ return getText(id).toString();
+ }
+
+
+ /**
+ * Return the string value associated with a particular resource ID,
+ * substituting the format arguments as defined in {@link java.util.Formatter}
+ * and {@link java.lang.String#format}. It will be stripped of any styled text
+ * information.
+ * {@more}
+ *
+ * @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.
+ *
+ * @param formatArgs The format arguments that will be used for substitution.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return String The string data associated with the resource,
+ * stripped of styled text information.
+ */
+ @NonNull
+ public String getString(@StringRes int id, Object... formatArgs) throws NotFoundException {
+ final String raw = getString(id);
+ return String.format(mResourcesImpl.getConfiguration().getLocales().get(0), raw,
+ formatArgs);
+ }
+
+ /**
+ * Formats the string necessary for grammatically correct pluralization
+ * of the given resource ID for the given quantity, using the given arguments.
+ * Note that the string is selected based solely on grammatical necessity,
+ * and that such rules differ between languages. Do not assume you know which string
+ * will be returned for a given quantity. See
+ * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a>
+ * for more detail.
+ *
+ * <p>Substitution of format arguments works as if using
+ * {@link java.util.Formatter} and {@link java.lang.String#format}.
+ * The resulting string will be stripped of any styled text information.
+ *
+ * @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.
+ * @param quantity The number used to get the correct string for the current language's
+ * plural rules.
+ * @param formatArgs The format arguments that will be used for substitution.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return String The string data associated with the resource,
+ * stripped of styled text information.
+ */
+ @NonNull
+ public String getQuantityString(@PluralsRes int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ String raw = getQuantityText(id, quantity).toString();
+ return String.format(mResourcesImpl.getConfiguration().getLocales().get(0), raw,
+ formatArgs);
+ }
+
+ /**
+ * Returns the string necessary for grammatically correct pluralization
+ * of the given resource ID for the given quantity.
+ * Note that the string is selected based solely on grammatical necessity,
+ * and that such rules differ between languages. Do not assume you know which string
+ * will be returned for a given quantity. See
+ * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a>
+ * for more detail.
+ *
+ * @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.
+ * @param quantity The number used to get the correct string for the current language's
+ * plural rules.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return String The string data associated with the resource,
+ * stripped of styled text information.
+ */
+ @NonNull
+ public String getQuantityString(@PluralsRes int id, int quantity) throws NotFoundException {
+ return getQuantityText(id, quantity).toString();
+ }
+
+ /**
+ * Return the string value associated with a particular resource ID. The
+ * returned object will be a String if this is a plain string; it will be
+ * some other type of CharSequence if it is styled.
+ *
+ * @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.
+ *
+ * @param def The default CharSequence to return.
+ *
+ * @return CharSequence The string data associated with the resource, plus
+ * possibly styled text information, or def if id is 0 or not found.
+ */
+ public CharSequence getText(@StringRes int id, CharSequence def) {
+ CharSequence res = id != 0 ? mResourcesImpl.getAssets().getResourceText(id) : null;
+ return res != null ? res : def;
+ }
+
+ /**
+ * Return the styled text array associated with a particular resource ID.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return The styled text array associated with the resource.
+ */
+ @NonNull
+ public CharSequence[] getTextArray(@ArrayRes int id) throws NotFoundException {
+ CharSequence[] res = mResourcesImpl.getAssets().getResourceTextArray(id);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("Text array resource ID #0x" + Integer.toHexString(id));
+ }
+
+ /**
+ * Return the string array associated with a particular resource ID.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return The string array associated with the resource.
+ */
+ @NonNull
+ public String[] getStringArray(@ArrayRes int id)
+ throws NotFoundException {
+ String[] res = mResourcesImpl.getAssets().getResourceStringArray(id);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("String array resource ID #0x" + Integer.toHexString(id));
+ }
+
+ /**
+ * Return the int array associated with a particular resource ID.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return The int array associated with the resource.
+ */
+ @NonNull
+ public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
+ int[] res = mResourcesImpl.getAssets().getResourceIntArray(id);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("Int array resource ID #0x" + Integer.toHexString(id));
+ }
+
+ /**
+ * Return an array of heterogeneous values.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Returns a TypedArray holding an array of the array values.
+ * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+ * when done with it.
+ */
+ @NonNull
+ public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException {
+ final ResourcesImpl impl = mResourcesImpl;
+ 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().getResourceArray(id, array.mData);
+ array.mIndices[0] = 0;
+
+ return array;
+ }
+
+ /**
+ * Retrieve a dimensional for a particular resource ID. Unit
+ * conversions are based on the current {@link DisplayMetrics} associated
+ * with the resources.
+ *
+ * @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 Resource dimension value multiplied by the appropriate metric to convert to pixels.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getDimensionPixelOffset
+ * @see #getDimensionPixelSize
+ */
+ public float getDimension(@DimenRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimension(value.data, impl.getDisplayMetrics());
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Retrieve a dimensional for a particular resource ID for use
+ * as an offset in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for you. An offset conversion involves simply
+ * truncating the base value to an integer.
+ *
+ * @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 Resource dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelSize
+ */
+ public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(value.data,
+ impl.getDisplayMetrics());
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Retrieve a dimensional for a particular resource ID for use
+ * as a size in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for use as a size. A size conversion involves
+ * rounding the base value, and ensuring that a non-zero base value
+ * is at least one pixel in size.
+ *
+ * @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 Resource dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelOffset
+ */
+ public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(value.data, impl.getDisplayMetrics());
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Retrieve a fractional unit for a particular resource ID.
+ *
+ * @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.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ */
+ public float getFraction(@FractionRes int id, int base, int pbase) {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ mResourcesImpl.getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_FRACTION) {
+ return TypedValue.complexToFraction(value.data, base, pbase);
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID.
+ * Various types of objects will be returned depending on the underlying
+ * resource -- for example, a solid color, PNG image, scalable image, etc.
+ * The Drawable API hides these implementation details.
+ *
+ * <p class="note"><strong>Note:</strong> Prior to
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, this function
+ * would not correctly retrieve the final configuration density when
+ * the resource ID passed here is an alias to another Drawable resource.
+ * This means that if the density configuration of the alias resource
+ * is different than the actual resource, the density of the returned
+ * Drawable would be incorrect, resulting in bad scaling. To work
+ * around this, you can instead manually resolve the aliased reference
+ * by using {@link #getValue(int, TypedValue, boolean)} and passing
+ * {@code true} for {@code resolveRefs}. The resulting
+ * {@link TypedValue#resourceId} value may be passed to this method.</p>
+ *
+ * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use
+ * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)}
+ * or {@link #getDrawable(int, Theme)} passing the desired theme.</p>
+ *
+ * @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 Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ * @see #getDrawable(int, Theme)
+ * @deprecated Use {@link #getDrawable(int, Theme)} instead.
+ */
+ @Deprecated
+ public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
+ final Drawable d = getDrawable(id, null);
+ if (d != null && d.canApplyTheme()) {
+ Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme "
+ + "attributes! Consider using Resources.getDrawable(int, Theme) or "
+ + "Context.getDrawable(int).", new RuntimeException());
+ }
+ return d;
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID and
+ * styled for the specified theme. Various types of objects will be
+ * returned depending on the underlying resource -- for example, a solid
+ * color, PNG image, scalable image, etc.
+ *
+ * @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.
+ * @param theme The theme used to style the drawable attributes, may be {@code null}.
+ * @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ */
+ public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
+ throws NotFoundException {
+ return getDrawableForDensity(id, 0, theme);
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID for the
+ * given screen density in DPI. This will set the drawable's density to be
+ * the device's density multiplied by the ratio of actual drawable density
+ * to requested density. This allows the drawable to be scaled up to the
+ * correct size if needed. Various types of objects will be returned
+ * depending on the underlying resource -- for example, a solid color, PNG
+ * image, scalable image, etc. The Drawable API hides these implementation
+ * details.
+ *
+ * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use
+ * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)}
+ * or {@link #getDrawableForDensity(int, int, Theme)} passing the desired
+ * theme.</p>
+ *
+ * @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.
+ * @param density the desired screen density indicated by the resource as
+ * 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)}.
+ * @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ * @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 {
+ return getDrawableForDensity(id, density, null);
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID for the
+ * given screen density in DPI and styled for the specified theme.
+ *
+ * @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.
+ * @param density The desired screen density indicated by the resource as
+ * 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} 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 {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValueForDensity(id, density, value, true);
+ return loadDrawable(value, id, density, theme);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ @NonNull
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
+ throws NotFoundException {
+ return mResourcesImpl.loadDrawable(this, value, id, density, theme);
+ }
+
+ /**
+ * Return a movie object associated with the particular resource ID.
+ * @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.
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}.
+ */
+ @Deprecated
+ public Movie getMovie(@RawRes int id) throws NotFoundException {
+ final InputStream is = openRawResource(id);
+ final Movie movie = Movie.decodeStream(is);
+ try {
+ is.close();
+ } catch (IOException e) {
+ // No one cares.
+ }
+ return movie;
+ }
+
+ /**
+ * Returns a color integer associated with a particular resource ID. If the
+ * resource holds a complex {@link ColorStateList}, then the default color
+ * from the set is returned.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A single color value in the form 0xAARRGGBB.
+ * @deprecated Use {@link #getColor(int, Theme)} instead.
+ */
+ @ColorInt
+ @Deprecated
+ public int getColor(@ColorRes int id) throws NotFoundException {
+ return getColor(id, null);
+ }
+
+ /**
+ * Returns a themed color integer associated with a particular resource ID.
+ * If the resource holds a complex {@link ColorStateList}, then the default
+ * color from the set is returned.
+ *
+ * @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.
+ * @param theme The theme used to style the color attributes, may be
+ * {@code null}.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A single color value in the form 0xAARRGGBB.
+ */
+ @ColorInt
+ public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ if (value.type >= TypedValue.TYPE_FIRST_INT
+ && value.type <= TypedValue.TYPE_LAST_INT) {
+ return value.data;
+ } else if (value.type != TypedValue.TYPE_STRING) {
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ }
+
+ final ColorStateList csl = impl.loadColorStateList(this, value, id, theme);
+ return csl.getDefaultColor();
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Returns a color state list associated with a particular resource ID. The
+ * resource may contain either a single raw color value or a complex
+ * {@link ColorStateList} holding multiple possible colors.
+ *
+ * @param id The desired resource identifier of a {@link ColorStateList},
+ * as generated by the aapt tool. This integer encodes the
+ * package, type, and resource entry. The value 0 is an invalid
+ * identifier.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A ColorStateList object containing either a single solid color
+ * or multiple colors that can be selected based on a state.
+ * @deprecated Use {@link #getColorStateList(int, Theme)} instead.
+ */
+ @NonNull
+ @Deprecated
+ public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
+ final ColorStateList csl = getColorStateList(id, null);
+ if (csl != null && csl.canApplyTheme()) {
+ Log.w(TAG, "ColorStateList " + getResourceName(id) + " has "
+ + "unresolved theme attributes! Consider using "
+ + "Resources.getColorStateList(int, Theme) or "
+ + "Context.getColorStateList(int).", new RuntimeException());
+ }
+ return csl;
+ }
+
+ /**
+ * Returns a themed color state list associated with a particular resource
+ * ID. The resource may contain either a single raw color value or a
+ * complex {@link ColorStateList} holding multiple possible colors.
+ *
+ * @param id The desired resource identifier of a {@link ColorStateList},
+ * as generated by the aapt tool. This integer encodes the
+ * package, type, and resource entry. The value 0 is an invalid
+ * identifier.
+ * @param theme The theme used to style the color attributes, may be
+ * {@code null}.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A themed ColorStateList object containing either a single solid
+ * color or multiple colors that can be selected based on a state.
+ */
+ @NonNull
+ public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
+ throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ return impl.loadColorStateList(this, value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ @NonNull
+ ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme)
+ throws NotFoundException {
+ return mResourcesImpl.loadColorStateList(this, value, id, theme);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, @Nullable Theme theme) {
+ return mResourcesImpl.loadComplexColor(this, value, id, theme);
+ }
+
+ /**
+ * Return a boolean associated with a particular resource ID. This can be
+ * used with any integral resource value, and will return true if it is
+ * non-zero.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Returns the boolean value contained in the resource.
+ */
+ public boolean getBoolean(@BoolRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ mResourcesImpl.getValue(id, value, true);
+ if (value.type >= TypedValue.TYPE_FIRST_INT
+ && value.type <= TypedValue.TYPE_LAST_INT) {
+ return value.data != 0;
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Return an integer associated with a particular resource ID.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Returns the integer value contained in the resource.
+ */
+ public int getInteger(@IntegerRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ mResourcesImpl.getValue(id, value, true);
+ if (value.type >= TypedValue.TYPE_FIRST_INT
+ && value.type <= TypedValue.TYPE_LAST_INT) {
+ return value.data;
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Retrieve a floating-point value for a particular resource ID.
+ *
+ * @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 Returns the floating-point value contained in the resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist or is not a floating-point value.
+ */
+ public float getFloat(@DimenRes int id) {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ mResourcesImpl.getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_FLOAT) {
+ return value.getFloat();
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Return an XmlResourceParser through which you can read a view layout
+ * description for the given resource ID. This parser has limited
+ * functionality -- in particular, you can't change its input, and only
+ * the high-level events are available.
+ *
+ * <p>This function is really a simple wrapper for calling
+ * {@link #getXml} with a layout resource.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return A new parser object through which you can read
+ * the XML data.
+ *
+ * @see #getXml
+ */
+ @NonNull
+ public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
+ return loadXmlResourceParser(id, "layout");
+ }
+
+ /**
+ * Return an XmlResourceParser through which you can read an animation
+ * description for the given resource ID. This parser has limited
+ * functionality -- in particular, you can't change its input, and only
+ * the high-level events are available.
+ *
+ * <p>This function is really a simple wrapper for calling
+ * {@link #getXml} with an animation resource.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return A new parser object through which you can read
+ * the XML data.
+ *
+ * @see #getXml
+ */
+ @NonNull
+ public XmlResourceParser getAnimation(@AnimatorRes @AnimRes int id) throws NotFoundException {
+ return loadXmlResourceParser(id, "anim");
+ }
+
+ /**
+ * Return an XmlResourceParser through which you can read a generic XML
+ * resource for the given resource ID.
+ *
+ * <p>The XmlPullParser implementation returned here has some limited
+ * functionality. In particular, you can't change its input, and only
+ * high-level parsing events are available (since the document was
+ * pre-parsed for you at build time, which involved merging text and
+ * stripping comments).
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return A new parser object through which you can read
+ * the XML data.
+ *
+ * @see android.util.AttributeSet
+ */
+ @NonNull
+ public XmlResourceParser getXml(@XmlRes int id) throws NotFoundException {
+ return loadXmlResourceParser(id, "xml");
+ }
+
+ /**
+ * Open a data stream for reading a raw resource. This can only be used
+ * with resources whose value is the name of an asset files -- that is, it can be
+ * used to open drawable, sound, and raw resources; it will fail on string
+ * and color resources.
+ *
+ * @param id The resource identifier to open, as generated by the aapt tool.
+ *
+ * @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 {
+ return openRawResource(id, value);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Returns a TypedValue suitable for temporary use. The obtained TypedValue
+ * should be released using {@link #releaseTempTypedValue(TypedValue)}.
+ *
+ * @return a typed value suitable for temporary use
+ */
+ private TypedValue obtainTempTypedValue() {
+ TypedValue tmpValue = null;
+ synchronized (mTmpValueLock) {
+ if (mTmpValue != null) {
+ tmpValue = mTmpValue;
+ mTmpValue = null;
+ }
+ }
+ if (tmpValue == null) {
+ return new TypedValue();
+ }
+ return tmpValue;
+ }
+
+ /**
+ * Returns a TypedValue to the pool. After calling this method, the
+ * specified TypedValue should no longer be accessed.
+ *
+ * @param value the typed value to return to the pool
+ */
+ private void releaseTempTypedValue(TypedValue value) {
+ synchronized (mTmpValueLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
+ }
+ }
+
+ /**
+ * Open a data stream for reading a raw resource. This can only be used
+ * with resources whose value is the name of an asset file -- that is, it can be
+ * used to open drawable, sound, and raw resources; it will fail on string
+ * and color resources.
+ *
+ * @param id The resource identifier to open, as generated by the aapt tool.
+ * @param value The TypedValue object to hold the resource information.
+ *
+ * @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, TypedValue value)
+ throws NotFoundException {
+ return mResourcesImpl.openRawResource(id, value);
+ }
+
+ /**
+ * Open a file descriptor for reading a raw resource. This can only be used
+ * with resources whose value is the name of an asset files -- that is, it can be
+ * used to open drawable, sound, and raw resources; it will fail on string
+ * and color resources.
+ *
+ * <p>This function only works for resources that are stored in the package
+ * as uncompressed data, which typically includes things like mp3 files
+ * and png images.
+ *
+ * @param id The resource identifier to open, as generated by the aapt tool.
+ *
+ * @return AssetFileDescriptor A new file descriptor you can use to read
+ * the resource. This includes the file descriptor itself, as well as the
+ * offset and length of data where the resource appears in the file. A
+ * null is returned if the file exists but is compressed.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ */
+ public AssetFileDescriptor openRawResourceFd(@RawRes int id)
+ throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ return mResourcesImpl.openRawResourceFd(id, value);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Return the raw data associated with a particular resource ID.
+ *
+ * @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.
+ * @param outValue Object in which to place the resource data.
+ * @param resolveRefs If true, a resource that is a reference to another
+ * resource will be followed so that you receive the
+ * actual final resource data. If false, the TypedValue
+ * will be filled in with the reference itself.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ */
+ public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResourcesImpl.getValue(id, outValue, resolveRefs);
+ }
+
+ /**
+ * Get the raw value associated with a resource with associated density.
+ *
+ * @param id resource identifier
+ * @param density density in DPI
+ * @param resolveRefs If true, a resource that is a reference to another
+ * resource will be followed so that you receive the actual final
+ * resource data. If false, the TypedValue will be filled in with
+ * the reference itself.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ * @see #getValue(String, TypedValue, boolean)
+ */
+ public void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
+ boolean resolveRefs) throws NotFoundException {
+ mResourcesImpl.getValueForDensity(id, density, outValue, resolveRefs);
+ }
+
+ /**
+ * Return the raw data associated with a particular resource ID.
+ * See getIdentifier() for information on how names are mapped to resource
+ * IDs, and getString(int) for information on how string resources are
+ * retrieved.
+ *
+ * <p>Note: use of this function is discouraged. It is much more
+ * efficient to retrieve resources by identifier than by name.
+ *
+ * @param name The name of the desired resource. This is passed to
+ * getIdentifier() with a default type of "string".
+ * @param outValue Object in which to place the resource data.
+ * @param resolveRefs If true, a resource that is a reference to another
+ * resource will be followed so that you receive the
+ * actual final resource data. If false, the TypedValue
+ * will be filled in with the reference itself.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ */
+ public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResourcesImpl.getValue(name, outValue, resolveRefs);
+ }
+
+
+ /**
+ * Returns the resource ID of the resource that was used to create this AttributeSet.
+ *
+ * @param set AttributeSet for which we want to find the source.
+ * @return The resource ID for the source that is backing the given AttributeSet or
+ * {@link Resources#ID_NULL} if the AttributeSet is {@code null}.
+ */
+ @AnyRes
+ public static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
+ return ResourcesImpl.getAttributeSetSourceResId(set);
+ }
+
+ /**
+ * This class holds the current attribute values for a particular theme.
+ * In other words, a Theme is a set of values for resource attributes;
+ * these are used in conjunction with {@link TypedArray}
+ * to resolve the final value for an attribute.
+ *
+ * <p>The Theme's attributes come into play in two ways: (1) a styled
+ * attribute can explicit reference a value in the theme through the
+ * "?themeAttribute" syntax; (2) if no value has been defined for a
+ * particular styled attribute, as a last resort we will try to find that
+ * attribute's value in the Theme.
+ *
+ * <p>You will normally use the {@link #obtainStyledAttributes} APIs to
+ * retrieve XML attributes with style and theme information applied.
+ */
+ public final class Theme {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ @UnsupportedAppUsage
+ private ResourcesImpl.ThemeImpl mThemeImpl;
+
+ private Theme() {
+ }
+
+ void setImpl(ResourcesImpl.ThemeImpl impl) {
+ synchronized (mLock) {
+ mThemeImpl = impl;
+ }
+ }
+
+ /**
+ * Place new attribute values into the theme. The style resource
+ * specified by <var>resid</var> will be retrieved from this Theme's
+ * resources, its values placed into the Theme object.
+ *
+ * <p>The semantics of this function depends on the <var>force</var>
+ * argument: If false, only values that are not already defined in
+ * the theme will be copied from the system resource; otherwise, if
+ * any of the style's attributes are already defined in the theme, the
+ * current values in the theme will be overwritten.
+ *
+ * @param resId The resource ID of a style resource from which to
+ * obtain attribute values.
+ * @param force If true, values in the style resource will always be
+ * used in the theme; otherwise, they will only be used
+ * if not already defined in the theme.
+ */
+ public void applyStyle(int resId, boolean force) {
+ synchronized (mLock) {
+ mThemeImpl.applyStyle(resId, force);
+ }
+ }
+
+ /**
+ * Set this theme to hold the same contents as the theme
+ * <var>other</var>. If both of these themes are from the same
+ * Resources object, they will be identical after this function
+ * returns. If they are from different Resources, only the resources
+ * they have in common will be set in this theme.
+ *
+ * @param other The existing Theme to copy from.
+ */
+ public void setTo(Theme other) {
+ synchronized (mLock) {
+ synchronized (other.mLock) {
+ mThemeImpl.setTo(other.mThemeImpl);
+ }
+ }
+ }
+
+ /**
+ * Return a TypedArray holding the values defined by
+ * <var>Theme</var> which are listed in <var>attrs</var>.
+ *
+ * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
+ * with the array.
+ *
+ * @param attrs The desired attributes. These attribute IDs must be sorted in ascending
+ * order.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Returns a TypedArray holding an array of the attribute values.
+ * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+ * when done with it.
+ *
+ * @see Resources#obtainAttributes
+ * @see #obtainStyledAttributes(int, int[])
+ * @see #obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ @NonNull
+ public TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[] attrs) {
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
+ }
+ }
+
+ /**
+ * Return a TypedArray holding the values defined by the style
+ * resource <var>resid</var> which are listed in <var>attrs</var>.
+ *
+ * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
+ * with the array.
+ *
+ * @param resId The desired style resource.
+ * @param attrs The desired attributes in the style. These attribute IDs must be sorted in
+ * ascending order.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Returns a TypedArray holding an array of the attribute values.
+ * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+ * when done with it.
+ *
+ * @see Resources#obtainAttributes
+ * @see #obtainStyledAttributes(int[])
+ * @see #obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ @NonNull
+ public TypedArray obtainStyledAttributes(@StyleRes int resId,
+ @NonNull @StyleableRes int[] attrs)
+ throws NotFoundException {
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
+ }
+ }
+
+ /**
+ * Return a TypedArray holding the attribute values in
+ * <var>set</var>
+ * that are listed in <var>attrs</var>. In addition, if the given
+ * AttributeSet specifies a style class (through the "style" attribute),
+ * that style will be applied on top of the base attributes it defines.
+ *
+ * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
+ * with the array.
+ *
+ * <p>When determining the final value of a particular attribute, there
+ * are four inputs that come into play:</p>
+ *
+ * <ol>
+ * <li> Any attribute values in the given AttributeSet.
+ * <li> The style resource specified in the AttributeSet (named
+ * "style").
+ * <li> The default style specified by <var>defStyleAttr</var> and
+ * <var>defStyleRes</var>
+ * <li> The base values in this theme.
+ * </ol>
+ *
+ * <p>Each of these inputs is considered in-order, with the first listed
+ * taking precedence over the following ones. In other words, if in the
+ * AttributeSet you have supplied <code><Button
+ * textColor="#ff000000"></code>, then the button's text will
+ * <em>always</em> be black, regardless of what is specified in any of
+ * the styles.
+ *
+ * @param set The base set of attribute values. May be null.
+ * @param attrs The desired attributes to be retrieved. These attribute IDs must be sorted
+ * in ascending order.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the TypedArray. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the TypedArray,
+ * used only if defStyleAttr is 0 or can not be found
+ * in the theme. Can be 0 to not look for defaults.
+ *
+ * @return Returns a TypedArray holding an array of the attribute values.
+ * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+ * when done with it.
+ *
+ * @see Resources#obtainAttributes
+ * @see #obtainStyledAttributes(int[])
+ * @see #obtainStyledAttributes(int, int[])
+ */
+ @NonNull
+ public TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
+ @NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes) {
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr,
+ defStyleRes);
+ }
+ }
+
+ /**
+ * Retrieve the values for a set of attributes in the Theme. The
+ * contents of the typed array are ultimately filled in by
+ * {@link Resources#getValue}.
+ *
+ * @param values The base set of attribute values, must be equal in
+ * length to {@code attrs}. All values must be of type
+ * {@link TypedValue#TYPE_ATTRIBUTE}.
+ * @param attrs The desired attributes to be retrieved. These attribute IDs must be sorted
+ * in ascending order.
+ * @return Returns a TypedArray holding an array of the attribute
+ * values. Be sure to call {@link TypedArray#recycle()}
+ * when done with it.
+ * @hide
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ public TypedArray resolveAttributes(@NonNull int[] values, @NonNull int[] attrs) {
+ synchronized (mLock) {
+ return mThemeImpl.resolveAttributes(this, values, attrs);
+ }
+ }
+
+ /**
+ * Retrieve the value of an attribute in the Theme. The contents of
+ * <var>outValue</var> are ultimately filled in by
+ * {@link Resources#getValue}.
+ *
+ * @param resid The resource identifier of the desired theme
+ * attribute.
+ * @param outValue Filled in with the ultimate resource value supplied
+ * by the attribute.
+ * @param resolveRefs If true, resource references will be walked; if
+ * false, <var>outValue</var> may be a
+ * TYPE_REFERENCE. In either case, it will never
+ * be a TYPE_ATTRIBUTE.
+ *
+ * @return boolean Returns true if the attribute was found and
+ * <var>outValue</var> is valid, else false.
+ */
+ public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
+ synchronized (mLock) {
+ return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs);
+ }
+ }
+
+ /**
+ * Gets all of the attribute ids associated with this {@link Theme}. For debugging only.
+ *
+ * @return The int array containing attribute ids associated with this {@link Theme}.
+ * @hide
+ */
+ public int[] getAllAttributes() {
+ synchronized (mLock) {
+ return mThemeImpl.getAllAttributes();
+ }
+ }
+
+ /**
+ * Returns the resources to which this theme belongs.
+ *
+ * @return Resources to which this theme belongs.
+ */
+ public Resources getResources() {
+ return Resources.this;
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID
+ * and styled for the Theme.
+ *
+ * @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 Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID
+ * does not exist.
+ */
+ public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
+ return Resources.this.getDrawable(id, this);
+ }
+
+ /**
+ * Returns a bit mask of configuration changes that will impact this
+ * theme (and thus require completely reloading it).
+ *
+ * @return a bit mask of configuration changes, as defined by
+ * {@link ActivityInfo}
+ * @see ActivityInfo
+ */
+ public @Config int getChangingConfigurations() {
+ synchronized (mLock) {
+ return mThemeImpl.getChangingConfigurations();
+ }
+ }
+
+ /**
+ * Print contents of this theme out to the log. For debugging only.
+ *
+ * @param priority The log priority to use.
+ * @param tag The log tag to use.
+ * @param prefix Text to prefix each line printed.
+ */
+ public void dump(int priority, String tag, String prefix) {
+ synchronized (mLock) {
+ mThemeImpl.dump(priority, tag, prefix);
+ }
+ }
+
+ // Needed by layoutlib.
+ /*package*/ long getNativeTheme() {
+ synchronized (mLock) {
+ return mThemeImpl.getNativeTheme();
+ }
+ }
+
+ /*package*/ int getAppliedStyleResId() {
+ synchronized (mLock) {
+ return mThemeImpl.getAppliedStyleResId();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public ThemeKey getKey() {
+ synchronized (mLock) {
+ return mThemeImpl.getKey();
+ }
+ }
+
+ private String getResourceNameFromHexString(String hexString) {
+ return getResourceName(Integer.parseInt(hexString, 16));
+ }
+
+ /**
+ * Parses {@link #getKey()} and returns a String array that holds pairs of
+ * adjacent Theme data: resource name followed by whether or not it was
+ * forced, as specified by {@link #applyStyle(int, boolean)}.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true)
+ public String[] getTheme() {
+ synchronized (mLock) {
+ return mThemeImpl.getTheme();
+ }
+ }
+
+ /** @hide */
+ public void encode(@NonNull ViewHierarchyEncoder encoder) {
+ encoder.beginObject(this);
+ final String[] properties = getTheme();
+ for (int i = 0; i < properties.length; i += 2) {
+ encoder.addProperty(properties[i], properties[i+1]);
+ }
+ encoder.endObject();
+ }
+
+ /**
+ * Rebases the theme against the parent Resource object's current
+ * configuration by re-applying the styles passed to
+ * {@link #applyStyle(int, boolean)}.
+ */
+ public void rebase() {
+ synchronized (mLock) {
+ mThemeImpl.rebase();
+ }
+ }
+
+ void rebase(ResourcesImpl resImpl) {
+ synchronized (mLock) {
+ mThemeImpl.rebase(resImpl.mAssets);
+ }
+ }
+
+ /**
+ * Returns the resource ID for the style specified using {@code style="..."} in the
+ * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise if not
+ * specified or otherwise not applicable.
+ * <p>
+ * Each {@link android.view.View} can have an explicit style specified in the layout file.
+ * This style is used first during the {@link android.view.View} attribute resolution, then
+ * if an attribute is not defined there the resource system looks at default style and theme
+ * as fallbacks.
+ *
+ * @param set The base set of attribute values.
+ *
+ * @return The resource ID for the style specified using {@code style="..."} in the
+ * {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise
+ * if not specified or otherwise not applicable.
+ */
+ @StyleRes
+ public int getExplicitStyle(@Nullable AttributeSet set) {
+ if (set == null) {
+ return ID_NULL;
+ }
+ int styleAttr = set.getStyleAttribute();
+ if (styleAttr == ID_NULL) {
+ return ID_NULL;
+ }
+ String styleAttrType = getResources().getResourceTypeName(styleAttr);
+ if ("attr".equals(styleAttrType)) {
+ TypedValue explicitStyle = new TypedValue();
+ boolean resolved = resolveAttribute(styleAttr, explicitStyle, true);
+ if (resolved) {
+ return explicitStyle.resourceId;
+ }
+ } else if ("style".equals(styleAttrType)) {
+ return styleAttr;
+ }
+ return ID_NULL;
+ }
+
+ /**
+ * Returns the ordered list of resource ID that are considered when resolving attribute
+ * values when making an equivalent call to
+ * {@link #obtainStyledAttributes(AttributeSet, int[], int, int)} . The list will include
+ * a set of explicit styles ({@code explicitStyleRes} and it will include the default styles
+ * ({@code defStyleAttr} and {@code defStyleRes}).
+ *
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the TypedArray. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the TypedArray,
+ * used only if defStyleAttr is 0 or can not be found
+ * in the theme. Can be 0 to not look for defaults.
+ * @param explicitStyleRes A resource identifier of an explicit style resource.
+ * @return ordered list of resource ID that are considered when resolving attribute values.
+ */
+ @NonNull
+ public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
+ synchronized (mLock) {
+ int[] stack = mThemeImpl.getAttributeResolutionStack(
+ defStyleAttr, defStyleRes, explicitStyleRes);
+ if (stack == null) {
+ return new int[0];
+ } else {
+ return stack;
+ }
+ }
+ }
+ }
+
+ static class ThemeKey implements Cloneable {
+ int[] mResId;
+ boolean[] mForce;
+ int mCount;
+
+ private int mHashCode = 0;
+
+ public void append(int resId, boolean force) {
+ if (mResId == null) {
+ mResId = new int[4];
+ }
+
+ if (mForce == null) {
+ mForce = new boolean[4];
+ }
+
+ mResId = GrowingArrayUtils.append(mResId, mCount, resId);
+ mForce = GrowingArrayUtils.append(mForce, mCount, force);
+ mCount++;
+
+ mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0);
+ }
+
+ /**
+ * Sets up this key as a deep copy of another key.
+ *
+ * @param other the key to deep copy into this key
+ */
+ public void setTo(ThemeKey other) {
+ mResId = other.mResId == null ? null : other.mResId.clone();
+ mForce = other.mForce == null ? null : other.mForce.clone();
+ mCount = other.mCount;
+ mHashCode = other.mHashCode;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ final ThemeKey t = (ThemeKey) o;
+ if (mCount != t.mCount) {
+ return false;
+ }
+
+ final int N = mCount;
+ for (int i = 0; i < N; i++) {
+ if (mResId[i] != t.mResId[i] || mForce[i] != t.mForce[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return a shallow copy of this key
+ */
+ @Override
+ public ThemeKey clone() {
+ final ThemeKey other = new ThemeKey();
+ other.mResId = mResId;
+ other.mForce = mForce;
+ other.mCount = mCount;
+ other.mHashCode = mHashCode;
+ return other;
+ }
+ }
+
+ /**
+ * Generate a new Theme object for this set of Resources. It initially
+ * starts out empty.
+ *
+ * @return Theme The newly created Theme container.
+ */
+ public final Theme newTheme() {
+ Theme theme = new Theme();
+ theme.setImpl(mResourcesImpl.newThemeImpl());
+ synchronized (mThemeRefs) {
+ mThemeRefs.add(new WeakReference<>(theme));
+
+ // Clean up references to garbage collected themes
+ if (mThemeRefs.size() > mThemeRefsNextFlushSize) {
+ mThemeRefs.removeIf(ref -> ref.get() == null);
+ mThemeRefsNextFlushSize = Math.max(MIN_THEME_REFS_FLUSH_SIZE,
+ 2 * mThemeRefs.size());
+ }
+ }
+ return theme;
+ }
+
+ /**
+ * Retrieve a set of basic attribute values from an AttributeSet, not
+ * performing styling of them using a theme and/or style resources.
+ *
+ * @param set The current attribute values to retrieve.
+ * @param attrs The specific attributes to be retrieved. These attribute IDs must be sorted in
+ * ascending order.
+ * @return Returns a TypedArray holding an array of the attribute values.
+ * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
+ * when done with it.
+ *
+ * @see Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ public TypedArray obtainAttributes(AttributeSet set, @StyleableRes int[] attrs) {
+ int len = attrs.length;
+ TypedArray array = TypedArray.obtain(this, len);
+
+ // XXX note that for now we only work with compiled XML files.
+ // To support generic XML files we will need to manually parse
+ // 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, attrs, array.mData, array.mIndices);
+
+ array.mXml = parser;
+
+ return array;
+ }
+
+ /**
+ * Store the newly updated configuration.
+ *
+ * @deprecated See {@link android.content.Context#createConfigurationContext(Configuration)}.
+ */
+ @Deprecated
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
+ updateConfiguration(config, metrics, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics,
+ CompatibilityInfo compat) {
+ mResourcesImpl.updateConfiguration(config, metrics, compat);
+ }
+
+ /**
+ * Update the system resources configuration if they have previously
+ * been initialized.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics,
+ CompatibilityInfo compat) {
+ if (mSystem != null) {
+ mSystem.updateConfiguration(config, metrics, compat);
+ //Log.i(TAG, "Updated system resources " + mSystem
+ // + ": " + mSystem.getConfiguration());
+ }
+ }
+
+ /**
+ * Return the current display metrics that are in effect for this resource
+ * object. The returned object should be treated as read-only.
+ *
+ * <p>Note that the reported value may be different than the window this application is
+ * interested in.</p>
+ *
+ * <p>Best practices are to obtain metrics from {@link WindowManager#getCurrentWindowMetrics()}
+ * for window bounds, {@link Display#getRealMetrics(DisplayMetrics)} for display bounds and
+ * obtain density from {@link Configuration#densityDpi}. The value obtained from this API may be
+ * wrong if the {@link Resources} is from the context which is different than the window is
+ * attached such as {@link Application#getResources()}.
+ * <p/>
+ *
+ * @return The resource's current display metrics.
+ */
+ public DisplayMetrics getDisplayMetrics() {
+ return mResourcesImpl.getDisplayMetrics();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(trackingBug = 176190631)
+ public DisplayAdjustments getDisplayAdjustments() {
+ final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments;
+ if (overrideDisplayAdjustments != null) {
+ return overrideDisplayAdjustments;
+ }
+ return mResourcesImpl.getDisplayAdjustments();
+ }
+
+ /**
+ * Customize the display adjustments based on the current one in {@link #mResourcesImpl}, in
+ * order to isolate the effect with other instances of {@link Resource} that may share the same
+ * instance of {@link ResourcesImpl}.
+ *
+ * @param override The operation to override the existing display adjustments. If it is null,
+ * the override adjustments will be cleared.
+ * @hide
+ */
+ public void overrideDisplayAdjustments(@Nullable Consumer<DisplayAdjustments> override) {
+ if (override != null) {
+ mOverrideDisplayAdjustments = new DisplayAdjustments(
+ mResourcesImpl.getDisplayAdjustments());
+ override.accept(mOverrideDisplayAdjustments);
+ } else {
+ mOverrideDisplayAdjustments = null;
+ }
+ }
+
+ /**
+ * Return {@code true} if the override display adjustments have been set.
+ * @hide
+ */
+ public boolean hasOverrideDisplayAdjustments() {
+ return mOverrideDisplayAdjustments != null;
+ }
+
+ /**
+ * Return the current configuration that is in effect for this resource
+ * object. The returned object should be treated as read-only.
+ *
+ * @return The resource's current configuration.
+ */
+ public Configuration getConfiguration() {
+ return mResourcesImpl.getConfiguration();
+ }
+
+ /** @hide */
+ public Configuration[] getSizeConfigurations() {
+ return mResourcesImpl.getSizeConfigurations();
+ }
+
+ /**
+ * Return the compatibility mode information for the application.
+ * The returned object should be treated as read-only.
+ *
+ * @return compatibility info.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mResourcesImpl.getCompatibilityInfo();
+ }
+
+ /**
+ * This is just for testing.
+ * @hide
+ */
+ @VisibleForTesting
+ @UnsupportedAppUsage
+ public void setCompatibilityInfo(CompatibilityInfo ci) {
+ if (ci != null) {
+ mResourcesImpl.updateConfiguration(null, null, ci);
+ }
+ }
+
+ /**
+ * Return a resource identifier for the given resource name. A fully
+ * qualified resource name is of the form "package:type/entry". The first
+ * two components (package and type) are optional if defType and
+ * defPackage, respectively, are specified here.
+ *
+ * <p>Note: use of this function is discouraged. It is much more
+ * efficient to retrieve resources by identifier than by name.
+ *
+ * @param name The name of the desired resource.
+ * @param defType Optional default resource type to find, if "type/" is
+ * not included in the name. Can be null to require an
+ * explicit type.
+ * @param defPackage Optional default package to find, if "package:" is
+ * not included in the name. Can be null to require an
+ * explicit package.
+ *
+ * @return int The associated resource identifier. Returns 0 if no such
+ * resource was found. (0 is not a valid resource ID.)
+ */
+ public int getIdentifier(String name, String defType, String defPackage) {
+ return mResourcesImpl.getIdentifier(name, defType, defPackage);
+ }
+
+ /**
+ * Return true if given resource identifier includes a package.
+ *
+ * @hide
+ */
+ public static boolean resourceHasPackage(@AnyRes int resid) {
+ return (resid >>> 24) != 0;
+ }
+
+ /**
+ * Return the full name for a given resource identifier. This name is
+ * a single string of the form "package:type/entry".
+ *
+ * @param resid The resource identifier whose name is to be retrieved.
+ *
+ * @return A string holding the name of the resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getResourcePackageName
+ * @see #getResourceTypeName
+ * @see #getResourceEntryName
+ */
+ public String getResourceName(@AnyRes int resid) throws NotFoundException {
+ return mResourcesImpl.getResourceName(resid);
+ }
+
+ /**
+ * Return the package name for a given resource identifier.
+ *
+ * @param resid The resource identifier whose package name is to be
+ * retrieved.
+ *
+ * @return A string holding the package name of the resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getResourceName
+ */
+ public String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
+ return mResourcesImpl.getResourcePackageName(resid);
+ }
+
+ /**
+ * Return the type name for a given resource identifier.
+ *
+ * @param resid The resource identifier whose type name is to be
+ * retrieved.
+ *
+ * @return A string holding the type name of the resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getResourceName
+ */
+ public String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
+ return mResourcesImpl.getResourceTypeName(resid);
+ }
+
+ /**
+ * Return the entry name for a given resource identifier.
+ *
+ * @param resid The resource identifier whose entry name is to be
+ * retrieved.
+ *
+ * @return A string holding the entry name of the resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getResourceName
+ */
+ public String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
+ return mResourcesImpl.getResourceEntryName(resid);
+ }
+
+ /**
+ * Return formatted log of the last retrieved resource's resolution path.
+ *
+ * @return A string holding a formatted log of the steps taken to resolve the last resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if there hasn't been a resource
+ * resolved yet.
+ *
+ * @hide
+ */
+ public String getLastResourceResolution() throws NotFoundException {
+ return mResourcesImpl.getLastResourceResolution();
+ }
+
+ /**
+ * Parse a series of {@link android.R.styleable#Extra <extra>} tags from
+ * an XML file. You call this when you are at the parent tag of the
+ * extra tags, and it will return once all of the child tags have been parsed.
+ * This will call {@link #parseBundleExtra} for each extra tag encountered.
+ *
+ * @param parser The parser from which to retrieve the extras.
+ * @param outBundle A Bundle in which to place all parsed extras.
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String nodeName = parser.getName();
+ if (nodeName.equals("extra")) {
+ parseBundleExtra("extra", parser, outBundle);
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ /**
+ * Parse a name/value pair out of an XML tag holding that data. The
+ * AttributeSet must be holding the data defined by
+ * {@link android.R.styleable#Extra}. The following value types are supported:
+ * <ul>
+ * <li> {@link TypedValue#TYPE_STRING}:
+ * {@link Bundle#putCharSequence Bundle.putCharSequence()}
+ * <li> {@link TypedValue#TYPE_INT_BOOLEAN}:
+ * {@link Bundle#putCharSequence Bundle.putBoolean()}
+ * <li> {@link TypedValue#TYPE_FIRST_INT}-{@link TypedValue#TYPE_LAST_INT}:
+ * {@link Bundle#putCharSequence Bundle.putBoolean()}
+ * <li> {@link TypedValue#TYPE_FLOAT}:
+ * {@link Bundle#putCharSequence Bundle.putFloat()}
+ * </ul>
+ *
+ * @param tagName The name of the tag these attributes come from; this is
+ * only used for reporting error messages.
+ * @param attrs The attributes from which to retrieve the name/value pair.
+ * @param outBundle The Bundle in which to place the parsed value.
+ * @throws XmlPullParserException If the attributes are not valid.
+ */
+ public void parseBundleExtra(String tagName, AttributeSet attrs,
+ Bundle outBundle) throws XmlPullParserException {
+ TypedArray sa = obtainAttributes(attrs,
+ com.android.internal.R.styleable.Extra);
+
+ String name = sa.getString(
+ com.android.internal.R.styleable.Extra_name);
+ if (name == null) {
+ sa.recycle();
+ throw new XmlPullParserException("<" + tagName
+ + "> requires an android:name attribute at "
+ + attrs.getPositionDescription());
+ }
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.Extra_value);
+ if (v != null) {
+ if (v.type == TypedValue.TYPE_STRING) {
+ CharSequence cs = v.coerceToString();
+ outBundle.putCharSequence(name, cs);
+ } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+ outBundle.putBoolean(name, v.data != 0);
+ } else if (v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT) {
+ outBundle.putInt(name, v.data);
+ } else if (v.type == TypedValue.TYPE_FLOAT) {
+ outBundle.putFloat(name, v.getFloat());
+ } else {
+ sa.recycle();
+ throw new XmlPullParserException("<" + tagName
+ + "> only supports string, integer, float, color, and boolean at "
+ + attrs.getPositionDescription());
+ }
+ } else {
+ sa.recycle();
+ throw new XmlPullParserException("<" + tagName
+ + "> requires an android:value or android:resource attribute at "
+ + attrs.getPositionDescription());
+ }
+
+ sa.recycle();
+ }
+
+ /**
+ * Retrieve underlying AssetManager storage for these resources.
+ */
+ public final AssetManager getAssets() {
+ return mResourcesImpl.getAssets();
+ }
+
+ /**
+ * Call this to remove all cached loaded layout resources from the
+ * Resources object. Only intended for use with performance testing
+ * tools.
+ */
+ public final void flushLayoutCache() {
+ mResourcesImpl.flushLayoutCache();
+ }
+
+ /**
+ * Start preloading of resource data using this Resources object. Only
+ * for use by the zygote process for loading common system resources.
+ * {@hide}
+ */
+ public final void startPreloading() {
+ mResourcesImpl.startPreloading();
+ }
+
+ /**
+ * Called by zygote when it is done preloading resources, to change back
+ * to normal Resources operation.
+ */
+ public final void finishPreloading() {
+ mResourcesImpl.finishPreloading();
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public LongSparseArray<ConstantState> getPreloadedDrawables() {
+ return mResourcesImpl.getPreloadedDrawables();
+ }
+
+ /**
+ * Loads an XML parser for the specified file.
+ *
+ * @param id the resource identifier for the file
+ * @param type the type of resource (used for logging)
+ * @return a parser for the specified XML file
+ * @throws NotFoundException if the file could not be loaded
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
+ throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_STRING) {
+ return loadXmlResourceParser(value.string.toString(), id,
+ value.assetCookie, type);
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Loads an XML parser for the specified file.
+ *
+ * @param file the path for the XML file to parse
+ * @param id the resource identifier for the file
+ * @param assetCookie the asset cookie for the file
+ * @param type the type of resource (used for logging)
+ * @return a parser for the specified XML file
+ * @throws NotFoundException if the file could not be loaded
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
+ String type) throws NotFoundException {
+ return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
+ }
+
+ /**
+ * Called by ConfigurationBoundResourceCacheTest.
+ * @hide
+ */
+ @VisibleForTesting
+ public int calcConfigChanges(Configuration config) {
+ return mResourcesImpl.calcConfigChanges(config);
+ }
+
+ /**
+ * Obtains styled attributes from the theme, if available, or unstyled
+ * resources if the theme is null.
+ *
+ * @hide
+ */
+ public static TypedArray obtainAttributes(
+ Resources res, Theme theme, AttributeSet set, int[] attrs) {
+ if (theme == null) {
+ return res.obtainAttributes(set, attrs);
+ }
+ return theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ private void checkCallbacksRegistered() {
+ if (mCallbacks == null) {
+ // Fallback to updating the underlying AssetManager if the Resources is not associated
+ // with a ResourcesManager.
+ mCallbacks = new AssetManagerUpdateHandler();
+ }
+ }
+
+ /**
+ * Retrieves the list of loaders.
+ *
+ * <p>Loaders are listed in increasing precedence order. A loader will override the resources
+ * and assets of loaders listed before itself.
+ * @hide
+ */
+ @NonNull
+ public List<ResourcesLoader> getLoaders() {
+ return mResourcesImpl.getAssets().getLoaders();
+ }
+
+ /**
+ * Adds a loader to the list of loaders. If the loader is already present in the list, the list
+ * will not be modified.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * loader changes.
+ *
+ * @param loaders the loaders to add
+ */
+ public void addLoaders(@NonNull ResourcesLoader... loaders) {
+ synchronized (mUpdateLock) {
+ checkCallbacksRegistered();
+ final List<ResourcesLoader> newLoaders =
+ new ArrayList<>(mResourcesImpl.getAssets().getLoaders());
+ final ArraySet<ResourcesLoader> loaderSet = new ArraySet<>(newLoaders);
+
+ for (int i = 0; i < loaders.length; i++) {
+ final ResourcesLoader loader = loaders[i];
+ if (!loaderSet.contains(loader)) {
+ newLoaders.add(loader);
+ }
+ }
+
+ if (loaderSet.size() == newLoaders.size()) {
+ return;
+ }
+
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (int i = loaderSet.size(), n = newLoaders.size(); i < n; i++) {
+ newLoaders.get(i).registerOnProvidersChangedCallback(this, mCallbacks);
+ }
+ }
+ }
+
+ /**
+ * Removes loaders from the list of loaders. If the loader is not present in the list, the list
+ * will not be modified.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * loader changes.
+ *
+ * @param loaders the loaders to remove
+ */
+ public void removeLoaders(@NonNull ResourcesLoader... loaders) {
+ synchronized (mUpdateLock) {
+ checkCallbacksRegistered();
+ final ArraySet<ResourcesLoader> removedLoaders = new ArraySet<>(loaders);
+ final List<ResourcesLoader> newLoaders = new ArrayList<>();
+ final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+
+ for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+ final ResourcesLoader loader = oldLoaders.get(i);
+ if (!removedLoaders.contains(loader)) {
+ newLoaders.add(loader);
+ }
+ }
+
+ if (oldLoaders.size() == newLoaders.size()) {
+ return;
+ }
+
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (int i = 0; i < loaders.length; i++) {
+ loaders[i].unregisterOnProvidersChangedCallback(this);
+ }
+ }
+ }
+
+ /**
+ * Removes all {@link ResourcesLoader ResourcesLoader(s)}.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * loader changes.
+ * @hide
+ */
+ @VisibleForTesting
+ public void clearLoaders() {
+ synchronized (mUpdateLock) {
+ checkCallbacksRegistered();
+ final List<ResourcesLoader> newLoaders = Collections.emptyList();
+ final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (ResourcesLoader loader : oldLoaders) {
+ loader.unregisterOnProvidersChangedCallback(this);
+ }
+ }
+ }
+}
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
new file mode 100644
index 0000000..b9f93b8
--- /dev/null
+++ b/android/content/res/ResourcesImpl.java
@@ -0,0 +1,1468 @@
+/*
+ * 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.res;
+
+import static android.content.res.Resources.ID_NULL;
+
+import android.animation.Animator;
+import android.animation.StateListAnimator;
+import android.annotation.AnyRes;
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.PluralsRes;
+import android.annotation.RawRes;
+import android.annotation.StyleRes;
+import android.annotation.StyleableRes;
+import android.compat.annotation.UnsupportedAppUsage;
+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.ImageDecoder;
+import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.ColorStateListDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.icu.text.PluralRules;
+import android.os.Build;
+import android.os.LocaleList;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.DisplayAdjustments;
+
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.NativeAllocationRegistry;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * The implementation of Resource access. This class contains the AssetManager and all caches
+ * associated with it.
+ *
+ * {@link Resources} is just a thing wrapper around this class. When a configuration change
+ * occurs, clients can retain the same {@link Resources} reference because the underlying
+ * {@link ResourcesImpl} object will be updated or re-created.
+ *
+ * @hide
+ */
+public class ResourcesImpl {
+ static final String TAG = "Resources";
+
+ private static final boolean DEBUG_LOAD = false;
+ private static final boolean DEBUG_CONFIG = false;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private static final boolean TRACE_FOR_MISS_PRELOAD = false; // Do we still need it?
+
+ private static final int ID_OTHER = 0x01000004;
+
+ private static final Object sSync = new Object();
+
+ private static boolean sPreloaded;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private boolean mPreloading;
+
+ // Information about preloaded resources. Note that they are not
+ // protected by a lock, because while preloading in zygote we are all
+ // single-threaded, and after that these are immutable.
+ @UnsupportedAppUsage
+ private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
+ @UnsupportedAppUsage
+ private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
+ = new LongSparseArray<>();
+ @UnsupportedAppUsage
+ private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
+ sPreloadedComplexColors = new LongSparseArray<>();
+
+ /** Lock object used to protect access to caches and configuration. */
+ @UnsupportedAppUsage
+ private final Object mAccessLock = new Object();
+
+ // These are protected by mAccessLock.
+ private final Configuration mTmpConfig = new Configuration();
+ @UnsupportedAppUsage
+ private final DrawableCache mDrawableCache = new DrawableCache();
+ @UnsupportedAppUsage
+ private final DrawableCache mColorDrawableCache = new DrawableCache();
+ private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
+ new ConfigurationBoundResourceCache<>();
+ @UnsupportedAppUsage
+ private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
+ new ConfigurationBoundResourceCache<>();
+ @UnsupportedAppUsage
+ private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
+ new ConfigurationBoundResourceCache<>();
+
+ // A stack of all the resourceIds already referenced when parsing a resource. This is used to
+ // detect circular references in the xml.
+ // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
+ // calls to ResourcesImpl
+ private final ThreadLocal<LookupStack> mLookupStack =
+ ThreadLocal.withInitial(() -> new LookupStack());
+
+ /** Size of the cyclical cache used to map XML files to blocks. */
+ private static final int XML_BLOCK_CACHE_SIZE = 4;
+
+ // Cyclical cache used for recently-accessed XML files.
+ private int mLastCachedXmlBlockIndex = -1;
+ private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
+ private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
+ private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
+
+
+ @UnsupportedAppUsage
+ final AssetManager mAssets;
+ private final DisplayMetrics mMetrics = new DisplayMetrics();
+ private final DisplayAdjustments mDisplayAdjustments;
+
+ private PluralRules mPluralRule;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final Configuration mConfiguration = new Configuration();
+
+ static {
+ sPreloadedDrawables = new LongSparseArray[2];
+ sPreloadedDrawables[0] = new LongSparseArray<>();
+ sPreloadedDrawables[1] = new LongSparseArray<>();
+ }
+
+ /**
+ * Creates a new ResourcesImpl object with CompatibilityInfo.
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
+ * selecting/computing resource values.
+ * @param config Desired device configuration to consider when
+ * selecting/computing resource values (optional).
+ * @param displayAdjustments this resource's Display override and compatibility info.
+ * Must not be null.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
+ @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
+ mAssets = assets;
+ mMetrics.setToDefaults();
+ mDisplayAdjustments = displayAdjustments;
+ mConfiguration.setToDefaults();
+ updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
+ }
+
+ public DisplayAdjustments getDisplayAdjustments() {
+ return mDisplayAdjustments;
+ }
+
+ @UnsupportedAppUsage
+ public AssetManager getAssets() {
+ return mAssets;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ DisplayMetrics getDisplayMetrics() {
+ if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
+ + "x" + mMetrics.heightPixels + " " + mMetrics.density);
+ return mMetrics;
+ }
+
+ Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ Configuration[] getSizeConfigurations() {
+ return mAssets.getSizeConfigurations();
+ }
+
+ CompatibilityInfo getCompatibilityInfo() {
+ return mDisplayAdjustments.getCompatibilityInfo();
+ }
+
+ private PluralRules getPluralRule() {
+ synchronized (sSync) {
+ if (mPluralRule == null) {
+ mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
+ }
+ return mPluralRule;
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
+ if (found) {
+ return;
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
+ }
+
+ void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
+ boolean resolveRefs) throws NotFoundException {
+ boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
+ if (found) {
+ return;
+ }
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
+ }
+
+ void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ int id = getIdentifier(name, "string", null);
+ if (id != 0) {
+ getValue(id, outValue, resolveRefs);
+ return;
+ }
+ throw new NotFoundException("String resource name " + name);
+ }
+
+ int getIdentifier(String name, String defType, String defPackage) {
+ if (name == null) {
+ throw new NullPointerException("name is null");
+ }
+ try {
+ return Integer.parseInt(name);
+ } catch (Exception e) {
+ // Ignore
+ }
+ return mAssets.getResourceIdentifier(name, defType, defPackage);
+ }
+
+ @NonNull
+ String getResourceName(@AnyRes int resid) throws NotFoundException {
+ String str = mAssets.getResourceName(resid);
+ if (str != null) return str;
+ throw new NotFoundException("Unable to find resource ID #0x"
+ + Integer.toHexString(resid));
+ }
+
+ @NonNull
+ String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
+ String str = mAssets.getResourcePackageName(resid);
+ if (str != null) return str;
+ throw new NotFoundException("Unable to find resource ID #0x"
+ + Integer.toHexString(resid));
+ }
+
+ @NonNull
+ String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
+ String str = mAssets.getResourceTypeName(resid);
+ if (str != null) return str;
+ throw new NotFoundException("Unable to find resource ID #0x"
+ + Integer.toHexString(resid));
+ }
+
+ @NonNull
+ String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
+ String str = mAssets.getResourceEntryName(resid);
+ if (str != null) return str;
+ throw new NotFoundException("Unable to find resource ID #0x"
+ + Integer.toHexString(resid));
+ }
+
+ @NonNull
+ String getLastResourceResolution() throws NotFoundException {
+ String str = mAssets.getLastResourceResolution();
+ if (str != null) return str;
+ throw new NotFoundException("Associated AssetManager hasn't resolved a resource");
+ }
+
+ @NonNull
+ CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
+ PluralRules rule = getPluralRule();
+ CharSequence res = mAssets.getResourceBagText(id,
+ attrForQuantityCode(rule.select(quantity)));
+ if (res != null) {
+ return res;
+ }
+ res = mAssets.getResourceBagText(id, ID_OTHER);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
+ + " quantity=" + quantity
+ + " item=" + rule.select(quantity));
+ }
+
+ private static int attrForQuantityCode(String quantityCode) {
+ switch (quantityCode) {
+ case PluralRules.KEYWORD_ZERO: return 0x01000005;
+ case PluralRules.KEYWORD_ONE: return 0x01000006;
+ case PluralRules.KEYWORD_TWO: return 0x01000007;
+ case PluralRules.KEYWORD_FEW: return 0x01000008;
+ case PluralRules.KEYWORD_MANY: return 0x01000009;
+ default: return ID_OTHER;
+ }
+ }
+
+ @NonNull
+ AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue)
+ throws NotFoundException {
+ getValue(id, tempValue, true);
+ try {
+ return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
+ } catch (Exception e) {
+ throw new NotFoundException("File " + tempValue.string.toString() + " from "
+ + "resource ID #0x" + Integer.toHexString(id), e);
+ }
+ }
+
+ @NonNull
+ InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {
+ getValue(id, value, true);
+ try {
+ return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
+ AssetManager.ACCESS_STREAMING);
+ } catch (Exception e) {
+ // Note: value.string might be null
+ NotFoundException rnf = new NotFoundException("File "
+ + (value.string == null ? "(null)" : value.string.toString())
+ + " from resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ }
+
+ ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
+ return mAnimatorCache;
+ }
+
+ ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
+ return mStateListAnimatorCache;
+ }
+
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics,
+ CompatibilityInfo compat) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
+ try {
+ synchronized (mAccessLock) {
+ if (DEBUG_CONFIG) {
+ Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ + mConfiguration + " old compat is "
+ + mDisplayAdjustments.getCompatibilityInfo());
+ Slog.i(TAG, "**** Updating config of " + this + ": new config is "
+ + config + " new compat is " + compat);
+ }
+ if (compat != null) {
+ mDisplayAdjustments.setCompatibilityInfo(compat);
+ }
+ if (metrics != null) {
+ mMetrics.setTo(metrics);
+ }
+ // NOTE: We should re-arrange this code to create a Display
+ // with the CompatibilityInfo that is used everywhere we deal
+ // with the display in relation to this app, rather than
+ // doing the conversion here. This impl should be okay because
+ // we make sure to return a compatible display in the places
+ // where there are public APIs to retrieve the display... but
+ // it would be cleaner and more maintainable to just be
+ // consistently dealing with a compatible display everywhere in
+ // the framework.
+ mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
+
+ final @Config int configChanges = calcConfigChanges(config);
+
+ // If even after the update there are no Locales set, grab the default locales.
+ LocaleList locales = mConfiguration.getLocales();
+ if (locales.isEmpty()) {
+ locales = LocaleList.getDefault();
+ mConfiguration.setLocales(locales);
+ }
+
+ if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
+ if (locales.size() > 1) {
+ // The LocaleList has changed. We must query the AssetManager's available
+ // Locales and figure out the best matching Locale in the new LocaleList.
+ String[] availableLocales = mAssets.getNonSystemLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ // No app defined locales, so grab the system locales.
+ availableLocales = mAssets.getLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ availableLocales = null;
+ }
+ }
+
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null && bestLocale != locales.get(0)) {
+ mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+ }
+ }
+ }
+ }
+
+ if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
+ mMetrics.densityDpi = mConfiguration.densityDpi;
+ mMetrics.density =
+ mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ }
+
+ // Protect against an unset fontScale.
+ mMetrics.scaledDensity = mMetrics.density *
+ (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
+
+ final int width, height;
+ if (mMetrics.widthPixels >= mMetrics.heightPixels) {
+ width = mMetrics.widthPixels;
+ height = mMetrics.heightPixels;
+ } else {
+ //noinspection SuspiciousNameCombination
+ width = mMetrics.heightPixels;
+ //noinspection SuspiciousNameCombination
+ height = mMetrics.widthPixels;
+ }
+
+ final int keyboardHidden;
+ if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
+ && mConfiguration.hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
+ } else {
+ keyboardHidden = mConfiguration.keyboardHidden;
+ }
+
+ mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
+ adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
+ mConfiguration.orientation,
+ mConfiguration.touchscreen,
+ mConfiguration.densityDpi, mConfiguration.keyboard,
+ keyboardHidden, mConfiguration.navigation, width, height,
+ mConfiguration.smallestScreenWidthDp,
+ mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
+ mConfiguration.screenLayout, mConfiguration.uiMode,
+ mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
+
+ if (DEBUG_CONFIG) {
+ Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+ + mConfiguration + " final compat is "
+ + mDisplayAdjustments.getCompatibilityInfo());
+ }
+
+ mDrawableCache.onConfigurationChange(configChanges);
+ mColorDrawableCache.onConfigurationChange(configChanges);
+ mComplexColorCache.onConfigurationChange(configChanges);
+ mAnimatorCache.onConfigurationChange(configChanges);
+ mStateListAnimatorCache.onConfigurationChange(configChanges);
+
+ flushLayoutCache();
+ }
+ synchronized (sSync) {
+ if (mPluralRule != null) {
+ mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+
+ /**
+ * Applies the new configuration, returning a bitmask of the changes
+ * between the old and new configurations.
+ *
+ * @param config the new configuration
+ * @return bitmask of config changes
+ */
+ public @Config int calcConfigChanges(@Nullable Configuration config) {
+ if (config == null) {
+ // If there is no configuration, assume all flags have changed.
+ return 0xFFFFFFFF;
+ }
+
+ mTmpConfig.setTo(config);
+ int density = config.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = mMetrics.noncompatDensityDpi;
+ }
+
+ mDisplayAdjustments.getCompatibilityInfo().applyToConfiguration(density, mTmpConfig);
+
+ if (mTmpConfig.getLocales().isEmpty()) {
+ mTmpConfig.setLocales(LocaleList.getDefault());
+ }
+ return mConfiguration.updateFrom(mTmpConfig);
+ }
+
+ /**
+ * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
+ * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
+ *
+ * All released versions of android prior to "L" used the deprecated language
+ * tags, so we will need to support them for backwards compatibility.
+ *
+ * Note that this conversion needs to take place *after* the call to
+ * {@code toLanguageTag} because that will convert all the deprecated codes to
+ * the new ones, even if they're set manually.
+ */
+ private static String adjustLanguageTag(String languageTag) {
+ final int separator = languageTag.indexOf('-');
+ final String language;
+ final String remainder;
+
+ if (separator == -1) {
+ language = languageTag;
+ remainder = "";
+ } else {
+ language = languageTag.substring(0, separator);
+ remainder = languageTag.substring(separator);
+ }
+
+ // No need to convert to lower cases because the language in the return value of
+ // Locale.toLanguageTag has been lower-cased.
+ final String adjustedLanguage;
+ switch(language) {
+ case "id":
+ adjustedLanguage = "in";
+ break;
+ case "yi":
+ adjustedLanguage = "ji";
+ break;
+ case "he":
+ adjustedLanguage = "iw";
+ break;
+ default:
+ adjustedLanguage = language;
+ break;
+ }
+ return adjustedLanguage + remainder;
+ }
+
+ /**
+ * Call this to remove all cached loaded layout resources from the
+ * Resources object. Only intended for use with performance testing
+ * tools.
+ */
+ public void flushLayoutCache() {
+ synchronized (mCachedXmlBlocks) {
+ Arrays.fill(mCachedXmlBlockCookies, 0);
+ Arrays.fill(mCachedXmlBlockFiles, null);
+
+ final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
+ for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
+ final XmlBlock oldBlock = cachedXmlBlocks[i];
+ if (oldBlock != null) {
+ oldBlock.close();
+ }
+ }
+ Arrays.fill(cachedXmlBlocks, null);
+ }
+ }
+
+ /**
+ * Wipe all caches that might be read and return an outdated object when resolving a resource.
+ */
+ public void clearAllCaches() {
+ synchronized (mAccessLock) {
+ mDrawableCache.clear();
+ mColorDrawableCache.clear();
+ mComplexColorCache.clear();
+ mAnimatorCache.clear();
+ mStateListAnimatorCache.clear();
+ flushLayoutCache();
+ }
+ }
+
+ @Nullable
+ Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
+ int density, @Nullable Resources.Theme theme)
+ throws NotFoundException {
+ // If the drawable's XML lives in our current density qualifier,
+ // it's okay to use a scaled version from the cache. Otherwise, we
+ // need to actually load the drawable from XML.
+ final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
+
+ // Pretend the requested density is actually the display density. If
+ // the drawable returned is not the requested density, then force it
+ // to be scaled later by dividing its density by the ratio of
+ // requested density to actual device density. Drawables that have
+ // undefined density or no density don't need to be handled here.
+ if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
+ if (value.density == density) {
+ value.density = mMetrics.densityDpi;
+ } else {
+ value.density = (value.density * mMetrics.densityDpi) / density;
+ }
+ }
+
+ try {
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d("PreloadDrawable", name);
+ }
+ }
+ }
+
+ final boolean isColorDrawable;
+ final DrawableCache caches;
+ final long key;
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ isColorDrawable = true;
+ caches = mColorDrawableCache;
+ key = value.data;
+ } else {
+ isColorDrawable = false;
+ caches = mDrawableCache;
+ key = (((long) value.assetCookie) << 32) | value.data;
+ }
+
+ // First, check whether we have a cached version of this drawable
+ // that was inflated against the specified theme. Skip the cache if
+ // we're currently preloading or we're not using the cache.
+ if (!mPreloading && useCache) {
+ final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
+ if (cachedDrawable != null) {
+ cachedDrawable.setChangingConfigurations(value.changingConfigurations);
+ return cachedDrawable;
+ }
+ }
+
+ // Next, check preloaded drawables. Preloaded drawables may contain
+ // unresolved theme attributes.
+ final Drawable.ConstantState cs;
+ if (isColorDrawable) {
+ cs = sPreloadedColorDrawables.get(key);
+ } else {
+ cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
+ }
+
+ Drawable dr;
+ boolean needsNewDrawableAfterCache = false;
+ if (cs != null) {
+ dr = cs.newDrawable(wrapper);
+ } else if (isColorDrawable) {
+ dr = new ColorDrawable(value.data);
+ } else {
+ 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
+ // added to cache.
+ if (dr instanceof DrawableContainer) {
+ needsNewDrawableAfterCache = true;
+ }
+
+ // Determine if the drawable has unresolved theme attributes. If it
+ // does, we'll need to apply a theme and store it in a theme-specific
+ // cache.
+ final boolean canApplyTheme = dr != null && dr.canApplyTheme();
+ if (canApplyTheme && theme != null) {
+ dr = dr.mutate();
+ dr.applyTheme(theme);
+ dr.clearMutated();
+ }
+
+ // If we were able to obtain a drawable, store it in the appropriate
+ // cache: preload, not themed, null theme, or theme-specific. Don't
+ // pollute the cache with drawables loaded from a foreign density.
+ if (dr != null) {
+ dr.setChangingConfigurations(value.changingConfigurations);
+ if (useCache) {
+ cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
+ if (needsNewDrawableAfterCache) {
+ Drawable.ConstantState state = dr.getConstantState();
+ if (state != null) {
+ dr = state.newDrawable(wrapper);
+ }
+ }
+ }
+ }
+
+ return dr;
+ } catch (Exception e) {
+ String name;
+ try {
+ name = getResourceName(id);
+ } catch (NotFoundException e2) {
+ name = "(missing name)";
+ }
+
+ // The target drawable might fail to load for any number of
+ // reasons, but we always want to include the resource name.
+ // Since the client already expects this method to throw a
+ // NotFoundException, just throw one of those.
+ final NotFoundException nfe = new NotFoundException("Drawable " + name
+ + " with resource ID #0x" + Integer.toHexString(id), e);
+ nfe.setStackTrace(new StackTraceElement[0]);
+ throw nfe;
+ }
+ }
+
+ private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
+ Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
+ final Drawable.ConstantState cs = dr.getConstantState();
+ if (cs == null) {
+ return;
+ }
+
+ if (mPreloading) {
+ final int changingConfigs = cs.getChangingConfigurations();
+ if (isColorDrawable) {
+ if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
+ sPreloadedColorDrawables.put(key, cs);
+ }
+ } else {
+ if (verifyPreloadConfig(
+ changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
+ if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
+ // If this resource does not vary based on layout direction,
+ // we can put it in all of the preload maps.
+ sPreloadedDrawables[0].put(key, cs);
+ sPreloadedDrawables[1].put(key, cs);
+ } else {
+ // Otherwise, only in the layout dir we loaded it for.
+ sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
+ }
+ }
+ }
+ } else {
+ synchronized (mAccessLock) {
+ caches.put(key, theme, cs, usesTheme);
+ }
+ }
+ }
+
+ private boolean verifyPreloadConfig(@Config int changingConfigurations,
+ @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
+ // We allow preloading of resources even if they vary by font scale (which
+ // doesn't impact resource selection) or density (which we handle specially by
+ // simply turning off all preloading), as well as any other configs specified
+ // by the caller.
+ if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
+ ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
+ String resName;
+ try {
+ resName = getResourceName(resourceId);
+ } catch (NotFoundException e) {
+ resName = "?";
+ }
+ // This should never happen in production, so we should log a
+ // warning even if we're not debugging.
+ Log.w(TAG, "Preloaded " + name + " resource #0x"
+ + Integer.toHexString(resourceId)
+ + " (" + resName + ") that varies with configuration!!");
+ return false;
+ }
+ if (TRACE_FOR_PRELOAD) {
+ String resName;
+ try {
+ resName = getResourceName(resourceId);
+ } catch (NotFoundException e) {
+ resName = "?";
+ }
+ Log.w(TAG, "Preloading " + name + " resource #0x"
+ + Integer.toHexString(resourceId)
+ + " (" + resName + ")");
+ }
+ return true;
+ }
+
+ /**
+ * 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) {
+ if (value.string == null) {
+ throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
+ }
+
+ final String file = value.string.toString();
+
+ if (TRACE_FOR_MISS_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
+ + ": " + name + " at " + file);
+ }
+ }
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
+ }
+
+
+ final Drawable dr;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ LookupStack stack = mLookupStack.get();
+ try {
+ // Perform a linear search to check if we have already referenced this resource before.
+ if (stack.contains(id)) {
+ throw new Exception("Recursive reference in drawable");
+ }
+ stack.push(id);
+ try {
+ if (file.endsWith(".xml")) {
+ final String typeName = getResourceTypeName(id);
+ if (typeName != null && typeName.equals("color")) {
+ dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
+ } else {
+ dr = loadXmlDrawable(wrapper, value, id, density, file);
+ }
+ } else {
+ final InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+ final AssetInputStream ais = (AssetInputStream) is;
+ dr = decodeImageDrawable(ais, wrapper, value);
+ }
+ } finally {
+ stack.pop();
+ }
+ } catch (Exception | StackOverflowError e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+
+ return dr;
+ }
+
+ private Drawable loadColorOrXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
+ int id, int density, String file) {
+ try {
+ ColorStateList csl = loadColorStateList(wrapper, value, id, null);
+ return new ColorStateListDrawable(csl);
+ } catch (NotFoundException originalException) {
+ // If we fail to load as color, try as normal XML drawable
+ try {
+ return loadXmlDrawable(wrapper, value, id, density, file);
+ } catch (Exception ignored) {
+ // If fallback also fails, throw the original exception
+ throw originalException;
+ }
+ }
+ }
+
+ private Drawable loadXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
+ int id, int density, String file)
+ throws IOException, XmlPullParserException {
+ try (
+ XmlResourceParser rp =
+ loadXmlResourceParser(file, id, value.assetCookie, "drawable")
+ ) {
+ return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
+ }
+ }
+
+ /**
+ * Loads a font from XML or resources stream.
+ */
+ @Nullable
+ public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
+ if (value.string == null) {
+ throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ + Integer.toHexString(id) + ") is not a Font: " + value);
+ }
+
+ final String file = value.string.toString();
+ if (!file.startsWith("res/")) {
+ return null;
+ }
+
+ Typeface cached = Typeface.findFromCache(mAssets, file);
+ if (cached != null) {
+ return cached;
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ try {
+ if (file.endsWith("xml")) {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "font");
+ final FontResourcesParser.FamilyResourceEntry familyEntry =
+ FontResourcesParser.parse(rp, wrapper);
+ if (familyEntry == null) {
+ return null;
+ }
+ return Typeface.createFromResources(familyEntry, mAssets, file);
+ }
+ return new Typeface.Builder(mAssets, file, false /* isAsset */, value.assetCookie)
+ .build();
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse xml resource " + file, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read xml resource " + file, e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ return null;
+ }
+
+ /**
+ * Given the value and id, we can get the XML filename as in value.data, based on that, we
+ * 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;
+ final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
+ ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
+ if (complexColor != null) {
+ return complexColor;
+ }
+
+ final android.content.res.ConstantState<ComplexColor> factory =
+ sPreloadedComplexColors.get(key);
+
+ if (factory != null) {
+ complexColor = factory.newInstance(wrapper, theme);
+ }
+ if (complexColor == null) {
+ complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
+ }
+
+ if (complexColor != null) {
+ complexColor.setBaseChangingConfigurations(value.changingConfigurations);
+
+ if (mPreloading) {
+ if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
+ 0, value.resourceId, "color")) {
+ sPreloadedComplexColors.put(key, complexColor.getConstantState());
+ }
+ } else {
+ cache.put(key, theme, complexColor.getConstantState());
+ }
+ }
+ return complexColor;
+ }
+
+ @Nullable
+ ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
+ Resources.Theme theme) {
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) android.util.Log.d("loadComplexColor", name);
+ }
+ }
+
+ final long key = (((long) value.assetCookie) << 32) | value.data;
+
+ // Handle inline color definitions.
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ return getColorStateListFromInt(value, key);
+ }
+
+ final String file = value.string.toString();
+
+ ComplexColor complexColor;
+ if (file.endsWith(".xml")) {
+ try {
+ complexColor = loadComplexColorFromName(wrapper, theme, value, id);
+ } catch (Exception e) {
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from complex color resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ } else {
+ throw new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id) + ": .xml extension required");
+ }
+
+ return complexColor;
+ }
+
+ @NonNull
+ ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
+ Resources.Theme theme)
+ throws NotFoundException {
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) android.util.Log.d("PreloadColorStateList", name);
+ }
+ }
+
+ final long key = (((long) value.assetCookie) << 32) | value.data;
+
+ // Handle inline color definitions.
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ return getColorStateListFromInt(value, key);
+ }
+
+ ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
+ if (complexColor != null && complexColor instanceof ColorStateList) {
+ return (ColorStateList) complexColor;
+ }
+
+ throw new NotFoundException(
+ "Can't find ColorStateList from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+ @NonNull
+ private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
+ ColorStateList csl;
+ final android.content.res.ConstantState<ComplexColor> factory =
+ sPreloadedComplexColors.get(key);
+ if (factory != null) {
+ return (ColorStateList) factory.newInstance();
+ }
+
+ csl = ColorStateList.valueOf(value.data);
+
+ if (mPreloading) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
+ "color")) {
+ sPreloadedComplexColors.put(key, csl.getConstantState());
+ }
+ }
+
+ return csl;
+ }
+
+ /**
+ * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
+ * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
+ *
+ * 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, or
+ * {@code null} if the XML file is neither.
+ */
+ @NonNull
+ private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
+ Resources.Theme theme) {
+ if (value.string == null) {
+ throw new UnsupportedOperationException(
+ "Can't convert to ComplexColor: type=0x" + value.type);
+ }
+
+ final String file = value.string.toString();
+
+ if (TRACE_FOR_MISS_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
+ + ": " + name + " at " + file);
+ }
+ }
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
+ }
+
+ ComplexColor complexColor = null;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ if (file.endsWith(".xml")) {
+ try {
+ final XmlResourceParser parser = loadXmlResourceParser(
+ file, id, value.assetCookie, "ComplexColor");
+
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ final String name = parser.getName();
+ if (name.equals("gradient")) {
+ complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
+ } else if (name.equals("selector")) {
+ complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
+ }
+ parser.close();
+ } catch (Exception e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from ComplexColor resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ } else {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ throw new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id) + ": .xml extension required");
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+
+ return complexColor;
+ }
+
+ /**
+ * Loads an XML parser for the specified file.
+ *
+ * @param file the path for the XML file to parse
+ * @param id the resource identifier for the file
+ * @param assetCookie the asset cookie for the file
+ * @param type the type of resource (used for logging)
+ * @return a parser for the specified XML file
+ * @throws NotFoundException if the file could not be loaded
+ */
+ @NonNull
+ XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
+ @NonNull String type)
+ throws NotFoundException {
+ if (id != 0) {
+ try {
+ synchronized (mCachedXmlBlocks) {
+ final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
+ final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
+ final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
+ // First see if this block is in our cache.
+ final int num = cachedXmlBlockFiles.length;
+ for (int i = 0; i < num; i++) {
+ if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
+ && cachedXmlBlockFiles[i].equals(file)) {
+ return cachedXmlBlocks[i].newParser(id);
+ }
+ }
+
+ // Not in the cache, create a new block and put it at
+ // the next slot in the cache.
+ final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
+ if (block != null) {
+ final int pos = (mLastCachedXmlBlockIndex + 1) % num;
+ mLastCachedXmlBlockIndex = pos;
+ final XmlBlock oldBlock = cachedXmlBlocks[pos];
+ if (oldBlock != null) {
+ oldBlock.close();
+ }
+ cachedXmlBlockCookies[pos] = assetCookie;
+ cachedXmlBlockFiles[pos] = file;
+ cachedXmlBlocks[pos] = block;
+ return block.newParser(id);
+ }
+ }
+ } catch (Exception e) {
+ final NotFoundException rnf = new NotFoundException("File " + file
+ + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ }
+
+ throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+ /**
+ * Start preloading of resource data using this Resources object. Only
+ * for use by the zygote process for loading common system resources.
+ * {@hide}
+ */
+ public final void startPreloading() {
+ synchronized (sSync) {
+ if (sPreloaded) {
+ throw new IllegalStateException("Resources already preloaded");
+ }
+ sPreloaded = true;
+ mPreloading = true;
+ mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
+ updateConfiguration(null, null, null);
+ }
+ }
+
+ /**
+ * Called by zygote when it is done preloading resources, to change back
+ * to normal Resources operation.
+ */
+ void finishPreloading() {
+ if (mPreloading) {
+ mPreloading = false;
+ flushLayoutCache();
+ }
+ }
+
+ @AnyRes
+ static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
+ if (set == null || !(set instanceof XmlBlock.Parser)) {
+ return ID_NULL;
+ }
+ return ((XmlBlock.Parser) set).getSourceResId();
+ }
+
+ LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
+ return sPreloadedDrawables[0];
+ }
+
+ ThemeImpl newThemeImpl() {
+ return new ThemeImpl();
+ }
+
+ private static final NativeAllocationRegistry sThemeRegistry =
+ NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(),
+ AssetManager.getThemeFreeFunction());
+
+ public class ThemeImpl {
+ /**
+ * Unique key for the series of styles applied to this theme.
+ */
+ private final Resources.ThemeKey mKey = new Resources.ThemeKey();
+
+ @SuppressWarnings("hiding")
+ private AssetManager mAssets;
+ private final long mTheme;
+
+ /**
+ * Resource identifier for the theme.
+ */
+ private int mThemeResId = 0;
+
+ /*package*/ ThemeImpl() {
+ mAssets = ResourcesImpl.this.mAssets;
+ mTheme = mAssets.createTheme();
+ sThemeRegistry.registerNativeAllocation(this, mTheme);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ mAssets.releaseTheme(mTheme);
+ }
+
+ /*package*/ Resources.ThemeKey getKey() {
+ return mKey;
+ }
+
+ /*package*/ long getNativeTheme() {
+ return mTheme;
+ }
+
+ /*package*/ int getAppliedStyleResId() {
+ return mThemeResId;
+ }
+
+ void applyStyle(int resId, boolean force) {
+ mAssets.applyStyleToTheme(mTheme, resId, force);
+ mThemeResId = resId;
+ mKey.append(resId, force);
+ }
+
+ void setTo(ThemeImpl other) {
+ mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
+
+ mThemeResId = other.mThemeResId;
+ mKey.setTo(other.getKey());
+ }
+
+ @NonNull
+ TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
+ AttributeSet set,
+ @StyleableRes int[] attrs,
+ @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes) {
+ final int len = attrs.length;
+ final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+
+ // XXX note that for now we only work with compiled XML files.
+ // To support generic XML files we will need to manually parse
+ // out the attributes from the XML file (applying type information
+ // contained in the resources and such).
+ final XmlBlock.Parser parser = (XmlBlock.Parser) set;
+ mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
+ array.mDataAddress, array.mIndicesAddress);
+ array.mTheme = wrapper;
+ array.mXml = parser;
+ return array;
+ }
+
+ @NonNull
+ TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
+ @NonNull int[] values,
+ @NonNull int[] attrs) {
+ final int len = attrs.length;
+ if (values == null || len != values.length) {
+ throw new IllegalArgumentException(
+ "Base attribute values must the same length as attrs");
+ }
+
+ final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+ mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
+ array.mTheme = wrapper;
+ array.mXml = null;
+ return array;
+ }
+
+ boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
+ return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
+ }
+
+ int[] getAllAttributes() {
+ return mAssets.getStyleAttributes(getAppliedStyleResId());
+ }
+
+ @Config int getChangingConfigurations() {
+ final @NativeConfig int nativeChangingConfig =
+ AssetManager.nativeThemeGetChangingConfigurations(mTheme);
+ return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
+ }
+
+ public void dump(int priority, String tag, String prefix) {
+ mAssets.dumpTheme(mTheme, priority, tag, prefix);
+ }
+
+ String[] getTheme() {
+ final int n = mKey.mCount;
+ final String[] themes = new String[n * 2];
+ for (int i = 0, j = n - 1; i < themes.length; i += 2, --j) {
+ final int resId = mKey.mResId[j];
+ final boolean forced = mKey.mForce[j];
+ try {
+ themes[i] = getResourceName(resId);
+ } catch (NotFoundException e) {
+ themes[i] = Integer.toHexString(i);
+ }
+ themes[i + 1] = forced ? "forced" : "not forced";
+ }
+ return themes;
+ }
+
+ /**
+ * Rebases the theme against the parent Resource object's current
+ * configuration by re-applying the styles passed to
+ * {@link #applyStyle(int, boolean)}.
+ */
+ void rebase() {
+ rebase(mAssets);
+ }
+
+ /**
+ * Rebases the theme against the {@code newAssets} by re-applying the styles passed to
+ * {@link #applyStyle(int, boolean)}.
+ *
+ * The theme will use {@code newAssets} for all future invocations of
+ * {@link #applyStyle(int, boolean)}.
+ */
+ void rebase(AssetManager newAssets) {
+ mAssets = mAssets.rebaseTheme(mTheme, newAssets, mKey.mResId, mKey.mForce, mKey.mCount);
+ }
+
+ /**
+ * Returns the ordered list of resource ID that are considered when resolving attribute
+ * values when making an equivalent call to
+ * {@link #obtainStyledAttributes(Resources.Theme, AttributeSet, int[], int, int)}. The list
+ * will include a set of explicit styles ({@code explicitStyleRes} and it will include the
+ * default styles ({@code defStyleAttr} and {@code defStyleRes}).
+ *
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the TypedArray. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the TypedArray,
+ * used only if defStyleAttr is 0 or can not be found
+ * in the theme. Can be 0 to not look for defaults.
+ * @param explicitStyleRes A resource identifier of an explicit style resource.
+ * @return ordered list of resource ID that are considered when resolving attribute values.
+ */
+ @Nullable
+ public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
+ return mAssets.getAttributeResolutionStack(
+ mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
+ }
+ }
+
+ private static class LookupStack {
+
+ // Pick a reasonable default size for the array, it is grown as needed.
+ private int[] mIds = new int[4];
+ private int mSize = 0;
+
+ public void push(int id) {
+ mIds = GrowingArrayUtils.append(mIds, mSize, id);
+ mSize++;
+ }
+
+ public boolean contains(int id) {
+ for (int i = 0; i < mSize; i++) {
+ if (mIds[i] == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void pop() {
+ mSize--;
+ }
+ }
+}
diff --git a/android/content/res/ResourcesKey.java b/android/content/res/ResourcesKey.java
new file mode 100644
index 0000000..99b56a8
--- /dev/null
+++ b/android/content/res/ResourcesKey.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesLoader;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/** @hide */
+public final class ResourcesKey {
+ @Nullable
+ @UnsupportedAppUsage
+ public final String mResDir;
+
+ @Nullable
+ @UnsupportedAppUsage
+ public final String[] mSplitResDirs;
+
+ @Nullable
+ public final String[] mOverlayPaths;
+
+ @Nullable
+ public final String[] mLibDirs;
+
+ /**
+ * The display ID that overrides the global resources display to produce the Resources display.
+ * If set to something other than {@link android.view.Display#INVALID_DISPLAY} this will
+ * override the global resources display for this key.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = O)
+ public int mDisplayId;
+
+ /**
+ * The configuration applied to the global configuration to produce the Resources configuration.
+ */
+ @NonNull
+ public final Configuration mOverrideConfiguration;
+
+ @NonNull
+ public final CompatibilityInfo mCompatInfo;
+
+ @Nullable
+ public final ResourcesLoader[] mLoaders;
+
+ private final int mHash;
+
+ public ResourcesKey(@Nullable String resDir,
+ @Nullable String[] splitResDirs,
+ @Nullable String[] overlayPaths,
+ @Nullable String[] libDirs,
+ int overrideDisplayId,
+ @Nullable Configuration overrideConfig,
+ @Nullable CompatibilityInfo compatInfo,
+ @Nullable ResourcesLoader[] loader) {
+ mResDir = resDir;
+ mSplitResDirs = splitResDirs;
+ mOverlayPaths = overlayPaths;
+ mLibDirs = libDirs;
+ mLoaders = (loader != null && loader.length == 0) ? null : loader;
+ mDisplayId = overrideDisplayId;
+ mOverrideConfiguration = new Configuration(overrideConfig != null
+ ? overrideConfig : Configuration.EMPTY);
+ mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+
+ int hash = 17;
+ hash = 31 * hash + Objects.hashCode(mResDir);
+ hash = 31 * hash + Arrays.hashCode(mSplitResDirs);
+ hash = 31 * hash + Arrays.hashCode(mOverlayPaths);
+ hash = 31 * hash + Arrays.hashCode(mLibDirs);
+ hash = 31 * hash + Objects.hashCode(mDisplayId);
+ hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
+ hash = 31 * hash + Objects.hashCode(mCompatInfo);
+ hash = 31 * hash + Arrays.hashCode(mLoaders);
+ mHash = hash;
+ }
+
+ @UnsupportedAppUsage
+ public ResourcesKey(@Nullable String resDir,
+ @Nullable String[] splitResDirs,
+ @Nullable String[] overlayPaths,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @Nullable CompatibilityInfo compatInfo) {
+ this(resDir, splitResDirs, overlayPaths, libDirs, displayId, overrideConfig, compatInfo,
+ null);
+ }
+
+ public boolean hasOverrideConfiguration() {
+ return !Configuration.EMPTY.equals(mOverrideConfiguration);
+ }
+
+ public boolean isPathReferenced(String path) {
+ if (mResDir != null && mResDir.startsWith(path)) {
+ return true;
+ } else {
+ return anyStartsWith(mSplitResDirs, path) || anyStartsWith(mOverlayPaths, path)
+ || anyStartsWith(mLibDirs, path);
+ }
+ }
+
+ private static boolean anyStartsWith(String[] list, String prefix) {
+ if (list != null) {
+ for (String s : list) {
+ if (s != null && s.startsWith(prefix)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHash;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof ResourcesKey)) {
+ return false;
+ }
+
+ ResourcesKey peer = (ResourcesKey) obj;
+ if (mHash != peer.mHash) {
+ // If the hashes don't match, the objects can't match.
+ return false;
+ }
+
+ if (!Objects.equals(mResDir, peer.mResDir)) {
+ return false;
+ }
+ if (!Arrays.equals(mSplitResDirs, peer.mSplitResDirs)) {
+ return false;
+ }
+ if (!Arrays.equals(mOverlayPaths, peer.mOverlayPaths)) {
+ return false;
+ }
+ if (!Arrays.equals(mLibDirs, peer.mLibDirs)) {
+ return false;
+ }
+ if (mDisplayId != peer.mDisplayId) {
+ return false;
+ }
+ if (!Objects.equals(mOverrideConfiguration, peer.mOverrideConfiguration)) {
+ return false;
+ }
+ if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) {
+ return false;
+ }
+ if (!Arrays.equals(mLoaders, peer.mLoaders)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder().append("ResourcesKey{");
+ builder.append(" mHash=").append(Integer.toHexString(mHash));
+ builder.append(" mResDir=").append(mResDir);
+ builder.append(" mSplitDirs=[");
+ if (mSplitResDirs != null) {
+ builder.append(TextUtils.join(",", mSplitResDirs));
+ }
+ builder.append("]");
+ builder.append(" mOverlayDirs=[");
+ if (mOverlayPaths != null) {
+ builder.append(TextUtils.join(",", mOverlayPaths));
+ }
+ builder.append("]");
+ builder.append(" mLibDirs=[");
+ if (mLibDirs != null) {
+ builder.append(TextUtils.join(",", mLibDirs));
+ }
+ builder.append("]");
+ builder.append(" mDisplayId=").append(mDisplayId);
+ builder.append(" mOverrideConfig=").append(Configuration.resourceQualifierString(
+ mOverrideConfiguration));
+ builder.append(" mCompatInfo=").append(mCompatInfo);
+ builder.append(" mLoaders=[");
+ if (mLoaders != null) {
+ builder.append(TextUtils.join(",", mLoaders));
+ }
+ builder.append("]}");
+ return builder.toString();
+ }
+}
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
new file mode 100644
index 0000000..2689acd
--- /dev/null
+++ b/android/content/res/Resources_Delegate.java
@@ -0,0 +1,1170 @@
+/*
+ * 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.res;
+
+import com.android.SdkConstants;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.PluralsResourceValue;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.ResourceValueImpl;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.layoutlib.bridge.util.NinePatchInputStream;
+import com.android.ninepatch.NinePatch;
+import com.android.resources.ResourceType;
+import com.android.resources.ResourceUrl;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import com.android.utils.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableInflater_Delegate;
+import android.icu.text.PluralRules;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LruCache;
+import android.util.TypedValue;
+import android.view.DisplayAdjustments;
+import android.view.ViewGroup.LayoutParams;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.WeakHashMap;
+
+import static android.content.res.AssetManager.ACCESS_STREAMING;
+import static com.android.SdkConstants.ANDROID_PKG;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+
+public class Resources_Delegate {
+ private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks =
+ new WeakHashMap<>();
+ private static WeakHashMap<Resources, BridgeContext> sContexts = new WeakHashMap<>();
+
+ // TODO: This cache is cleared every time a render session is disposed. Look into making this
+ // more long lived.
+ private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50);
+
+ public static Resources initSystem(@NonNull BridgeContext context,
+ @NonNull AssetManager assets,
+ @NonNull DisplayMetrics metrics,
+ @NonNull Configuration config,
+ @NonNull LayoutlibCallback layoutlibCallback) {
+ assert Resources.mSystem == null :
+ "Resources_Delegate.initSystem called twice before disposeSystem was called";
+ Resources resources = new Resources(Resources_Delegate.class.getClassLoader());
+ resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments()));
+ sContexts.put(resources, Objects.requireNonNull(context));
+ sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback));
+ return Resources.mSystem = resources;
+ }
+
+ /** Returns the {@link BridgeContext} associated to the given {@link Resources} */
+ @VisibleForTesting
+ @NonNull
+ public static BridgeContext getContext(@NonNull Resources resources) {
+ assert sContexts.containsKey(resources) :
+ "Resources_Delegate.getContext called before initSystem";
+ return sContexts.get(resources);
+ }
+
+ /** Returns the {@link LayoutlibCallback} associated to the given {@link Resources} */
+ @VisibleForTesting
+ @NonNull
+ public static LayoutlibCallback getLayoutlibCallback(@NonNull Resources resources) {
+ assert sLayoutlibCallbacks.containsKey(resources) :
+ "Resources_Delegate.getLayoutlibCallback called before initSystem";
+ return sLayoutlibCallbacks.get(resources);
+ }
+
+ /**
+ * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that
+ * would prevent us from unloading the library.
+ */
+ public static void disposeSystem() {
+ sDrawableCache.evictAll();
+ sContexts.clear();
+ sLayoutlibCallbacks.clear();
+ DrawableInflater_Delegate.clearConstructorCache();
+ Resources.mSystem = null;
+ }
+
+ public static BridgeTypedArray newTypeArray(Resources resources, int numEntries) {
+ return new BridgeTypedArray(resources, getContext(resources), numEntries);
+ }
+
+ private static ResourceReference getResourceInfo(Resources resources, int id) {
+ // first get the String related to this id in the framework
+ ResourceReference resourceInfo = Bridge.resolveResourceId(id);
+
+ assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called";
+ // Set the layoutlib callback and context for resources
+ if (resources != Resources.mSystem &&
+ (!sContexts.containsKey(resources) || !sLayoutlibCallbacks.containsKey(resources))) {
+ sLayoutlibCallbacks.put(resources, getLayoutlibCallback(Resources.mSystem));
+ sContexts.put(resources, getContext(Resources.mSystem));
+ }
+
+ if (resourceInfo == null) {
+ // Didn't find a match in the framework? Look in the project.
+ resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id);
+ }
+
+ return resourceInfo;
+ }
+
+ private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id) {
+ ResourceReference resourceInfo = getResourceInfo(resources, id);
+
+ if (resourceInfo != null) {
+ String attributeName = resourceInfo.getName();
+ RenderResources renderResources = getContext(resources).getRenderResources();
+ ResourceValue value = renderResources.getResolvedResource(resourceInfo);
+ if (value == null) {
+ // Unable to resolve the attribute, just leave the unresolved value.
+ value = new ResourceValueImpl(resourceInfo.getNamespace(),
+ resourceInfo.getResourceType(), attributeName, attributeName);
+ }
+ return Pair.of(attributeName, value);
+ }
+
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static Drawable getDrawable(Resources resources, int id) {
+ return getDrawable(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static Drawable getDrawable(Resources resources, int id, Theme theme) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+ if (value != null) {
+ String key = value.getSecond().getValue();
+
+ Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null;
+ Drawable drawable;
+ if (constantState != null) {
+ drawable = constantState.newDrawable(resources, theme);
+ } else {
+ drawable =
+ ResourceHelper.getDrawable(value.getSecond(), getContext(resources), theme);
+
+ if (key != null) {
+ sDrawableCache.put(key, drawable.getConstantState());
+ }
+ }
+
+ return drawable;
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static int getColor(Resources resources, int id) {
+ return getColor(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resourceValue = value.getSecond();
+ try {
+ return ResourceHelper.getColor(resourceValue.getValue());
+ } catch (NumberFormatException e) {
+ // Check if the value passed is a file. If it is, mostly likely, user is referencing
+ // a color state list from a place where they should reference only a pure color.
+ AssetRepository repository = getAssetRepository(resources);
+ String message;
+ if (repository.isFileResource(resourceValue.getValue())) {
+ String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/"
+ + resourceValue.getName();
+ message = "Hexadecimal color expected, found Color State List for " + resource;
+ } else {
+ message = e.getMessage();
+ }
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT, message, e, null, null);
+ return 0;
+ }
+ }
+
+ // Suppress possible NPE. getColorStateList will never return null, it will instead
+ // throw an exception, but intelliJ can't figure that out
+ //noinspection ConstantConditions
+ return getColorStateList(resources, id, theme).getDefaultColor();
+ }
+
+ @LayoutlibDelegate
+ static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
+ return getColorStateList(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static ColorStateList getColorStateList(Resources resources, int id, Theme theme)
+ throws NotFoundException {
+ Pair<String, ResourceValue> resValue = getResourceValue(resources, id);
+
+ if (resValue != null) {
+ ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
+ getContext(resources), theme);
+ if (stateList != null) {
+ return stateList;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static CharSequence getText(Resources resources, int id, CharSequence def) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ return def;
+ }
+
+ @LayoutlibDelegate
+ static CharSequence getText(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(resources, id);
+ if (resValue == null) {
+ // Error already logged by getArrayResourceValue.
+ return new CharSequence[0];
+ }
+ if (resValue instanceof ArrayResourceValue) {
+ ArrayResourceValue arrayValue = (ArrayResourceValue) resValue;
+ return resolveValues(resources, arrayValue);
+ }
+ RenderResources renderResources = getContext(resources).getRenderResources();
+ return new CharSequence[] { renderResources.resolveResValue(resValue).getValue() };
+ }
+
+ @LayoutlibDelegate
+ static String[] getStringArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(resources, id);
+ if (resValue == null) {
+ // Error already logged by getArrayResourceValue.
+ return new String[0];
+ }
+ if (resValue instanceof ArrayResourceValue) {
+ ArrayResourceValue arv = (ArrayResourceValue) resValue;
+ return resolveValues(resources, arv);
+ }
+ return new String[] { resolveReference(resources, resValue) };
+ }
+
+ /**
+ * Resolves each element in resValue and returns an array of resolved values. The returned array
+ * may contain nulls.
+ */
+ @NonNull
+ static String[] resolveValues(@NonNull Resources resources,
+ @NonNull ArrayResourceValue resValue) {
+ String[] result = new String[resValue.getElementCount()];
+ for (int i = 0; i < resValue.getElementCount(); i++) {
+ String value = resValue.getElement(i);
+ result[i] = resolveReference(resources, value,
+ resValue.getNamespace(), resValue.getNamespaceResolver());
+ }
+ return result;
+ }
+
+ @LayoutlibDelegate
+ static int[] getIntArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue rv = getArrayResourceValue(resources, id);
+ if (rv == null) {
+ // Error already logged by getArrayResourceValue.
+ return new int[0];
+ }
+ if (rv instanceof ArrayResourceValue) {
+ ArrayResourceValue resValue = (ArrayResourceValue) rv;
+ int n = resValue.getElementCount();
+ int[] values = new int[n];
+ for (int i = 0; i < n; i++) {
+ String element = resolveReference(resources, resValue.getElement(i),
+ resValue.getNamespace(), resValue.getNamespaceResolver());
+ if (element != null) {
+ try {
+ if (element.startsWith("#")) {
+ // This integer represents a color (starts with #).
+ values[i] = ResourceHelper.getColor(element);
+ } else {
+ values[i] = getInt(element);
+ }
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" + element +
+ "\"", null, null);
+ } catch (IllegalArgumentException e) {
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains wrong color format: \"" + element +
+ "\"", null, null);
+ }
+ } else {
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" +
+ resValue.getElement(i) + "\"", null, null);
+ }
+ }
+ return values;
+ }
+
+ // This is an older IDE that can only give us the first element of the array.
+ String firstValue = resolveReference(resources, rv);
+ if (firstValue != null) {
+ try {
+ return new int[]{getInt(firstValue)};
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" + firstValue + "\"",
+ null, null);
+ return new int[1];
+ }
+ } else {
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" +
+ rv.getValue() + "\"", null, null);
+ return new int[1];
+ }
+ }
+
+ /**
+ * Try to find the ArrayResourceValue for the given id.
+ * <p/>
+ * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an
+ * error and return null. However, if the ResourceValue found has type {@code
+ * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the
+ * method returns the ResourceValue. This happens on older versions of the IDE, which did not
+ * parse the array resources properly.
+ * <p/>
+ *
+ * @throws NotFoundException if no resource if found
+ */
+ @Nullable
+ private static ResourceValue getArrayResourceValue(Resources resources, int id)
+ throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
+
+ if (v != null) {
+ ResourceValue resValue = v.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ final ResourceType type = resValue.getResourceType();
+ if (type != ResourceType.ARRAY) {
+ Bridge.getLog().error(ILayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format(
+ "Resource with id 0x%1$X is not an array resource, but %2$s",
+ id, type == null ? "null" : type.getDisplayName()),
+ null, null);
+ return null;
+ }
+ if (!(resValue instanceof ArrayResourceValue)) {
+ Bridge.getLog().warning(ILayoutLog.TAG_UNSUPPORTED,
+ "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.",
+ null, null);
+ }
+ return resValue;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Nullable
+ private static String resolveReference(@NonNull Resources resources, @Nullable String value,
+ @NonNull ResourceNamespace contextNamespace,
+ @NonNull ResourceNamespace.Resolver resolver) {
+ if (value != null) {
+ ResourceValue resValue = new UnresolvedResourceValue(value, contextNamespace, resolver);
+ return resolveReference(resources, resValue);
+ }
+ return null;
+ }
+
+ @Nullable
+ private static String resolveReference(@NonNull Resources resources,
+ @NonNull ResourceValue value) {
+ RenderResources renderResources = getContext(resources).getRenderResources();
+ ResourceValue resolvedValue = renderResources.resolveResValue(value);
+ return resolvedValue == null ? null : resolvedValue.getValue();
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+
+ try {
+ BridgeXmlBlockParser parser =
+ ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ if (parser != null) {
+ return parser;
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
+ "Failed to parse " + value.getValue(), e, null, null /*data*/);
+ // we'll return null below.
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id, "layout");
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+
+ try {
+ return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
+ "Failed to parse " + value.getValue(), e, null, null /*data*/);
+ // we'll return null below.
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
+ return getContext(resources).obtainStyledAttributes(set, attrs);
+ }
+
+ @LayoutlibDelegate
+ static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet
+ set, int[] attrs) {
+ return Resources.obtainAttributes_Original(resources, theme, set, attrs);
+ }
+
+ @LayoutlibDelegate
+ static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
+ BridgeContext context = getContext(resources);
+ ResourceReference reference = context.resolveId(id);
+ RenderResources renderResources = context.getRenderResources();
+ ResourceValue value = renderResources.getResolvedResource(reference);
+
+ if (!(value instanceof ArrayResourceValue)) {
+ throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
+ }
+
+ ArrayResourceValue arrayValue = (ArrayResourceValue) value;
+ int length = arrayValue.getElementCount();
+ ResourceNamespace namespace = arrayValue.getNamespace();
+ BridgeTypedArray typedArray = newTypeArray(resources, length);
+
+ for (int i = 0; i < length; i++) {
+ ResourceValue elementValue;
+ ResourceUrl resourceUrl = ResourceUrl.parse(arrayValue.getElement(i));
+ if (resourceUrl != null) {
+ ResourceReference elementRef =
+ resourceUrl.resolve(namespace, arrayValue.getNamespaceResolver());
+ elementValue = renderResources.getResolvedResource(elementRef);
+ } else {
+ elementValue = new ResourceValueImpl(namespace, ResourceType.STRING, "element" + i,
+ arrayValue.getElement(i));
+ }
+ typedArray.bridgeSetValue(i, elementValue.getName(), namespace, i, elementValue);
+ }
+
+ typedArray.sealArray();
+ return typedArray;
+ }
+
+ @LayoutlibDelegate
+ static float getDimension(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (v.equals(BridgeConstants.MATCH_PARENT) ||
+ v.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ }
+ TypedValue tmpValue = new TypedValue();
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return tmpValue.getDimension(resources.getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ TypedValue tmpValue = new TypedValue();
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(tmpValue.data,
+ resources.getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ TypedValue tmpValue = new TypedValue();
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(tmpValue.data,
+ resources.getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ static int getInteger(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ try {
+ return getInt(v);
+ } catch (NumberFormatException e) {
+ // return exception below
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ static float getFloat(Resources resources, int id) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ static boolean getBoolean(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return Boolean.parseBoolean(v);
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return false;
+ }
+
+ @LayoutlibDelegate
+ static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
+ if (resourceInfo != null) {
+ return resourceInfo.getName();
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getResourceName(Resources resources, int resid) throws NotFoundException {
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
+ if (resourceInfo != null) {
+ String packageName = getPackageName(resourceInfo, resources);
+ return packageName + ':' + resourceInfo.getResourceType().getName() + '/' +
+ resourceInfo.getName();
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
+ if (resourceInfo != null) {
+ return getPackageName(resourceInfo, resources);
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
+ if (resourceInfo != null) {
+ return resourceInfo.getResourceType().getName();
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ private static String getPackageName(ResourceReference resourceInfo, Resources resources) {
+ String packageName = resourceInfo.getNamespace().getPackageName();
+ if (packageName == null) {
+ packageName = getContext(resources).getPackageName();
+ if (packageName == null) {
+ packageName = SdkConstants.APP_PREFIX;
+ }
+ }
+ return packageName;
+ }
+
+ @LayoutlibDelegate
+ static String getString(Resources resources, int id, Object... formatArgs)
+ throws NotFoundException {
+ String s = getString(resources, id);
+ if (s != null) {
+ return String.format(s, formatArgs);
+
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getString(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null && value.getSecond().getValue() != null) {
+ return value.getSecond().getValue();
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getQuantityString(Resources resources, int id, int quantity) throws
+ NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ if (value.getSecond() instanceof PluralsResourceValue) {
+ PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond();
+ PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales()
+ .get(0));
+ String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity));
+ if (strValue == null) {
+ strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER);
+ }
+
+ return strValue;
+ }
+ else {
+ return value.getSecond().getValue();
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ String raw = getQuantityString(resources, id, quantity);
+ return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs);
+ }
+
+ @LayoutlibDelegate
+ static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
+ NotFoundException {
+ return getQuantityString(resources, id, quantity);
+ }
+
+ @LayoutlibDelegate
+ static Typeface getFont(Resources resources, int id) throws
+ NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+ if (value != null) {
+ return ResourceHelper.getFont(value.getSecond(), getContext(resources), null);
+ }
+
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
+ NotFoundException {
+ ResourceValue resVal = getResourceValue(resources, id, outValue);
+ if (resVal != null) {
+ return ResourceHelper.getFont(resVal, getContext(resources), null);
+ }
+
+ throwException(resources, id);
+ return null; // This is not used since the method above always throws.
+ }
+
+ @LayoutlibDelegate
+ static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ getResourceValue(resources, id, outValue);
+ }
+
+ private static ResourceValue getResourceValue(Resources resources, int id, TypedValue outValue)
+ throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ ResourceValue resVal = value.getSecond();
+ String v = resVal != null ? resVal.getValue() : null;
+
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
+ false /*requireUnit*/)) {
+ return resVal;
+ }
+ if (resVal instanceof DensityBasedResourceValue) {
+ outValue.density =
+ ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
+ }
+
+ // else it's a string
+ outValue.type = TypedValue.TYPE_STRING;
+ outValue.string = v;
+ return resVal;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+ return null; // This is not used since the method above always throws.
+ }
+
+ @LayoutlibDelegate
+ static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @LayoutlibDelegate
+ static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue,
+ boolean resolveRefs) throws NotFoundException {
+ getValue(resources, id, outValue, resolveRefs);
+ }
+
+ @LayoutlibDelegate
+ static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
+ // Not supported in layoutlib
+ return Resources.ID_NULL;
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+
+ try {
+ return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
+ "Failed to parse " + value.getValue(), e, null, null /*data*/);
+ // we'll return null below.
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
+ String type) throws NotFoundException {
+ return resources.loadXmlResourceParser_Original(id, type);
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id,
+ int assetCookie, String type) throws NotFoundException {
+ // even though we know the XML file to load directly, we still need to resolve the
+ // id so that we can know if it's a platform or project resource.
+ // (mPlatformResouceFlag will get the result and will be used later).
+ Pair<String, ResourceValue> result = getResourceValue(resources, id);
+
+ ResourceNamespace layoutNamespace;
+ if (result != null && result.getSecond() != null) {
+ layoutNamespace = result.getSecond().getNamespace();
+ } else {
+ // We need to pick something, even though the resource system never heard about a layout
+ // with this numeric id.
+ layoutNamespace = ResourceNamespace.RES_AUTO;
+ }
+
+ try {
+ XmlPullParser parser = ParserFactory.create(file);
+ return new BridgeXmlBlockParser(parser, getContext(resources), layoutNamespace);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+
+ @LayoutlibDelegate
+ static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
+
+ if (value != null) {
+ String path = value.getSecond().getValue();
+ if (path != null) {
+ return openRawResource(resources, path);
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static InputStream openRawResource(Resources resources, int id, TypedValue value)
+ throws NotFoundException {
+ getValue(resources, id, value, true);
+
+ String path = value.string.toString();
+ return openRawResource(resources, path);
+ }
+
+ private static InputStream openRawResource(Resources resources, String path)
+ throws NotFoundException {
+ AssetRepository repository = getAssetRepository(resources);
+ try {
+ InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING);
+ if (stream == null) {
+ throw new NotFoundException(path);
+ }
+ // If it's a nine-patch return a custom input stream so that
+ // other methods (mainly bitmap factory) can detect it's a 9-patch
+ // and actually load it as a 9-patch instead of a normal bitmap.
+ if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
+ return new NinePatchInputStream(stream);
+ }
+ return stream;
+ } catch (IOException e) {
+ NotFoundException exception = new NotFoundException();
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ @LayoutlibDelegate
+ static AssetFileDescriptor openRawResourceFd(Resources resources, int id)
+ throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @VisibleForTesting
+ @Nullable
+ static ResourceUrl resourceUrlFromName(
+ @NonNull String name, @Nullable String defType, @Nullable String defPackage) {
+ int colonIdx = name.indexOf(':');
+ int slashIdx = name.indexOf('/');
+
+ if (colonIdx != -1 && slashIdx != -1) {
+ // Easy case
+ return ResourceUrl.parse(PREFIX_RESOURCE_REF + name);
+ }
+
+ if (colonIdx == -1 && slashIdx == -1) {
+ if (defType == null) {
+ throw new IllegalArgumentException("name does not define a type an no defType was" +
+ " passed");
+ }
+
+ // It does not define package or type
+ return ResourceUrl.parse(
+ PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType +
+ "/" + name);
+ }
+
+ if (colonIdx != -1) {
+ if (defType == null) {
+ throw new IllegalArgumentException("name does not define a type an no defType was" +
+ " passed");
+ }
+ // We have package but no type
+ String pkg = name.substring(0, colonIdx);
+ ResourceType type = ResourceType.fromClassName(defType);
+ return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) :
+ null;
+ }
+
+ ResourceType type = ResourceType.fromClassName(name.substring(0, slashIdx));
+ if (type == null) {
+ return null;
+ }
+ // We have type but no package
+ return ResourceUrl.create(defPackage,
+ type,
+ name.substring(slashIdx + 1));
+ }
+
+ @LayoutlibDelegate
+ static int getIdentifier(Resources resources, String name, String defType, String defPackage) {
+ if (name == null) {
+ return 0;
+ }
+
+ ResourceUrl url = resourceUrlFromName(name, defType, defPackage);
+ if (url != null) {
+ if (ANDROID_PKG.equals(url.namespace)) {
+ return Bridge.getResourceId(url.type, url.name);
+ }
+
+ if (getContext(resources).getPackageName().equals(url.namespace)) {
+ return getLayoutlibCallback(resources).getOrGenerateResourceId(
+ new ResourceReference(ResourceNamespace.RES_AUTO, url.type, url.name));
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource
+ * type.
+ *
+ * @param id the id of the resource
+ * @param expectedType the type of resource that was expected
+ *
+ * @throws NotFoundException
+ */
+ private static void throwException(Resources resources, int id, @Nullable String expectedType)
+ throws NotFoundException {
+ throwException(id, getResourceInfo(resources, id), expectedType);
+ }
+
+ private static void throwException(Resources resources, int id) throws NotFoundException {
+ throwException(resources, id, null);
+ }
+
+ private static void throwException(int id, @Nullable ResourceReference resourceInfo) {
+ throwException(id, resourceInfo, null);
+ }
+ private static void throwException(int id, @Nullable ResourceReference resourceInfo,
+ @Nullable String expectedType) {
+ String message;
+ if (resourceInfo != null) {
+ message = String.format(
+ "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
+ resourceInfo.getResourceType(), id, resourceInfo.getName());
+ } else {
+ message = String.format("Could not resolve resource value: 0x%1$X.", id);
+ }
+
+ if (expectedType != null) {
+ message += " Or the resolved value was not of type " + expectedType + " as expected.";
+ }
+ throw new NotFoundException(message);
+ }
+
+ private static int getInt(String v) throws NumberFormatException {
+ int radix = 10;
+ if (v.startsWith("0x")) {
+ v = v.substring(2);
+ radix = 16;
+ } else if (v.startsWith("0")) {
+ radix = 8;
+ }
+ return Integer.parseInt(v, radix);
+ }
+
+ private static AssetRepository getAssetRepository(Resources resources) {
+ BridgeContext context = getContext(resources);
+ BridgeAssetManager assetManager = context.getAssets();
+ return assetManager.getAssetRepository();
+ }
+}
diff --git a/android/content/res/Resources_Theme_Delegate.java b/android/content/res/Resources_Theme_Delegate.java
new file mode 100644
index 0000000..4740fac
--- /dev/null
+++ b/android/content/res/Resources_Theme_Delegate.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2011 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 com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.Nullable;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.Resources.ThemeKey;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Resources.Theme}
+ *
+ * Through the layoutlib_create tool, the original methods of Theme have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Resources_Theme_Delegate {
+
+ // ---- delegate manager ----
+
+ private static final DelegateManager<Resources_Theme_Delegate> sManager =
+ new DelegateManager<Resources_Theme_Delegate>(Resources_Theme_Delegate.class);
+
+ public static DelegateManager<Resources_Theme_Delegate> getDelegateManager() {
+ return sManager;
+ }
+
+ // ---- delegate methods. ----
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtainStyledAttributes(
+ Resources thisResources, Theme thisTheme,
+ int[] attrs) {
+ boolean changed = setupResources(thisTheme);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+ 0, attrs);
+ ta.setTheme(thisTheme);
+ restoreResources(changed);
+ return ta;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtainStyledAttributes(
+ Resources thisResources, Theme thisTheme,
+ int resid, int[] attrs)
+ throws NotFoundException {
+ boolean changed = setupResources(thisTheme);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+ resid, attrs);
+ ta.setTheme(thisTheme);
+ restoreResources(changed);
+ return ta;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtainStyledAttributes(
+ Resources thisResources, Theme thisTheme,
+ AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
+ boolean changed = setupResources(thisTheme);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(set,
+ attrs, defStyleAttr, defStyleRes);
+ ta.setTheme(thisTheme);
+ restoreResources(changed);
+ return ta;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean resolveAttribute(
+ Resources thisResources, Theme thisTheme,
+ int resid, TypedValue outValue,
+ boolean resolveRefs) {
+ boolean changed = setupResources(thisTheme);
+ boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute(resid,
+ outValue, resolveRefs);
+ restoreResources(changed);
+ return found;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray resolveAttributes(Resources thisResources, Theme thisTheme,
+ int[] values, int[] attrs) {
+ // FIXME
+ return null;
+ }
+
+ // ---- private helper methods ----
+
+ private static boolean setupResources(Theme thisTheme) {
+ // Key is a space-separated list of theme ids applied that have been merged into the
+ // BridgeContext's theme to make thisTheme.
+ final ThemeKey key = thisTheme.getKey();
+ final int[] resId = key.mResId;
+ final boolean[] force = key.mForce;
+
+ boolean changed = false;
+ for (int i = 0, N = key.mCount; i < N; i++) {
+ StyleResourceValue style = resolveStyle(resId[i]);
+ if (style != null) {
+ RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle(
+ style, force[i]);
+ changed = true;
+ }
+
+ }
+ return changed;
+ }
+
+ private static void restoreResources(boolean changed) {
+ if (changed) {
+ RenderSessionImpl.getCurrentContext().getRenderResources().clearStyles();
+ }
+ }
+
+ @Nullable
+ private static StyleResourceValue resolveStyle(int nativeResid) {
+ if (nativeResid == 0) {
+ return null;
+ }
+ BridgeContext context = RenderSessionImpl.getCurrentContext();
+ ResourceReference theme = context.resolveId(nativeResid);
+ return (StyleResourceValue) context.getRenderResources().getResolvedResource(theme);
+ }
+}
diff --git a/android/content/res/StringBlock.java b/android/content/res/StringBlock.java
new file mode 100644
index 0000000..5bc235f
--- /dev/null
+++ b/android/content/res/StringBlock.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2006 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 android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.Closeable;
+import java.util.Arrays;
+
+/**
+ * Conveniences for retrieving data out of a compiled string resource.
+ *
+ * {@hide}
+ */
+public final class StringBlock implements Closeable {
+ private static final String TAG = "AssetManager";
+ private static final boolean localLOGV = false;
+
+ private final long mNative;
+ private final boolean mUseSparse;
+ private final boolean mOwnsNative;
+
+ private CharSequence[] mStrings;
+ private SparseArray<CharSequence> mSparseStrings;
+
+ @GuardedBy("this") private boolean mOpen = true;
+
+ StyleIDs mStyleIDs = null;
+
+ public StringBlock(byte[] data, boolean useSparse) {
+ mNative = nativeCreate(data, 0, data.length);
+ mUseSparse = useSparse;
+ mOwnsNative = true;
+ if (localLOGV) Log.v(TAG, "Created string block " + this
+ + ": " + nativeGetSize(mNative));
+ }
+
+ public StringBlock(byte[] data, int offset, int size, boolean useSparse) {
+ mNative = nativeCreate(data, offset, size);
+ mUseSparse = useSparse;
+ mOwnsNative = true;
+ if (localLOGV) Log.v(TAG, "Created string block " + this
+ + ": " + nativeGetSize(mNative));
+ }
+
+ /**
+ * @deprecated use {@link #getSequence(int)} which can return null when a string cannot be found
+ * due to incremental installation.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public CharSequence get(int idx) {
+ CharSequence seq = getSequence(idx);
+ return seq == null ? "" : seq;
+ }
+
+ @Nullable
+ public CharSequence getSequence(int idx) {
+ synchronized (this) {
+ if (mStrings != null) {
+ CharSequence res = mStrings[idx];
+ if (res != null) {
+ return res;
+ }
+ } else if (mSparseStrings != null) {
+ CharSequence res = mSparseStrings.get(idx);
+ if (res != null) {
+ return res;
+ }
+ } else {
+ final int num = nativeGetSize(mNative);
+ if (mUseSparse && num > 250) {
+ mSparseStrings = new SparseArray<CharSequence>();
+ } else {
+ mStrings = new CharSequence[num];
+ }
+ }
+ String str = nativeGetString(mNative, idx);
+ if (str == null) {
+ return null;
+ }
+ CharSequence res = str;
+ int[] style = nativeGetStyle(mNative, idx);
+ if (localLOGV) Log.v(TAG, "Got string: " + str);
+ if (localLOGV) Log.v(TAG, "Got styles: " + Arrays.toString(style));
+ if (style != null) {
+ if (mStyleIDs == null) {
+ mStyleIDs = new StyleIDs();
+ }
+
+ // the style array is a flat array of <type, start, end> hence
+ // the magic constant 3.
+ for (int styleIndex = 0; styleIndex < style.length; styleIndex += 3) {
+ int styleId = style[styleIndex];
+
+ if (styleId == mStyleIDs.boldId || styleId == mStyleIDs.italicId
+ || styleId == mStyleIDs.underlineId || styleId == mStyleIDs.ttId
+ || styleId == mStyleIDs.bigId || styleId == mStyleIDs.smallId
+ || styleId == mStyleIDs.subId || styleId == mStyleIDs.supId
+ || styleId == mStyleIDs.strikeId || styleId == mStyleIDs.listItemId
+ || styleId == mStyleIDs.marqueeId) {
+ // id already found skip to next style
+ continue;
+ }
+
+ String styleTag = nativeGetString(mNative, styleId);
+ if (styleTag == null) {
+ return null;
+ }
+
+ if (styleTag.equals("b")) {
+ mStyleIDs.boldId = styleId;
+ } else if (styleTag.equals("i")) {
+ mStyleIDs.italicId = styleId;
+ } else if (styleTag.equals("u")) {
+ mStyleIDs.underlineId = styleId;
+ } else if (styleTag.equals("tt")) {
+ mStyleIDs.ttId = styleId;
+ } else if (styleTag.equals("big")) {
+ mStyleIDs.bigId = styleId;
+ } else if (styleTag.equals("small")) {
+ mStyleIDs.smallId = styleId;
+ } else if (styleTag.equals("sup")) {
+ mStyleIDs.supId = styleId;
+ } else if (styleTag.equals("sub")) {
+ mStyleIDs.subId = styleId;
+ } else if (styleTag.equals("strike")) {
+ mStyleIDs.strikeId = styleId;
+ } else if (styleTag.equals("li")) {
+ mStyleIDs.listItemId = styleId;
+ } else if (styleTag.equals("marquee")) {
+ mStyleIDs.marqueeId = styleId;
+ }
+ }
+
+ res = applyStyles(str, style, mStyleIDs);
+ }
+ if (res != null) {
+ if (mStrings != null) mStrings[idx] = res;
+ else mSparseStrings.put(idx, res);
+ }
+ return res;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ super.finalize();
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void close() {
+ synchronized (this) {
+ if (mOpen) {
+ mOpen = false;
+
+ if (mOwnsNative) {
+ nativeDestroy(mNative);
+ }
+ }
+ }
+ }
+
+ static final class StyleIDs {
+ private int boldId = -1;
+ private int italicId = -1;
+ private int underlineId = -1;
+ private int ttId = -1;
+ private int bigId = -1;
+ private int smallId = -1;
+ private int subId = -1;
+ private int supId = -1;
+ private int strikeId = -1;
+ private int listItemId = -1;
+ private int marqueeId = -1;
+ }
+
+ @Nullable
+ private CharSequence applyStyles(String str, int[] style, StyleIDs ids) {
+ if (style.length == 0)
+ return str;
+
+ SpannableString buffer = new SpannableString(str);
+ int i=0;
+ while (i < style.length) {
+ int type = style[i];
+ if (localLOGV) Log.v(TAG, "Applying style span id=" + type
+ + ", start=" + style[i+1] + ", end=" + style[i+2]);
+
+
+ if (type == ids.boldId) {
+ buffer.setSpan(new StyleSpan(Typeface.BOLD),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.italicId) {
+ buffer.setSpan(new StyleSpan(Typeface.ITALIC),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.underlineId) {
+ buffer.setSpan(new UnderlineSpan(),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.ttId) {
+ buffer.setSpan(new TypefaceSpan("monospace"),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.bigId) {
+ buffer.setSpan(new RelativeSizeSpan(1.25f),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.smallId) {
+ buffer.setSpan(new RelativeSizeSpan(0.8f),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.subId) {
+ buffer.setSpan(new SubscriptSpan(),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.supId) {
+ buffer.setSpan(new SuperscriptSpan(),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.strikeId) {
+ buffer.setSpan(new StrikethroughSpan(),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (type == ids.listItemId) {
+ addParagraphSpan(buffer, new BulletSpan(10),
+ style[i+1], style[i+2]+1);
+ } else if (type == ids.marqueeId) {
+ buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ } else {
+ String tag = nativeGetString(mNative, type);
+ if (tag == null) {
+ return null;
+ }
+
+ if (tag.startsWith("font;")) {
+ String sub;
+
+ sub = subtag(tag, ";height=");
+ if (sub != null) {
+ int size = Integer.parseInt(sub);
+ addParagraphSpan(buffer, new Height(size),
+ style[i+1], style[i+2]+1);
+ }
+
+ sub = subtag(tag, ";size=");
+ if (sub != null) {
+ int size = Integer.parseInt(sub);
+ buffer.setSpan(new AbsoluteSizeSpan(size, true),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ sub = subtag(tag, ";fgcolor=");
+ if (sub != null) {
+ buffer.setSpan(getColor(sub, true),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ sub = subtag(tag, ";color=");
+ if (sub != null) {
+ buffer.setSpan(getColor(sub, true),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ sub = subtag(tag, ";bgcolor=");
+ if (sub != null) {
+ buffer.setSpan(getColor(sub, false),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ sub = subtag(tag, ";face=");
+ if (sub != null) {
+ buffer.setSpan(new TypefaceSpan(sub),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ } else if (tag.startsWith("a;")) {
+ String sub;
+
+ sub = subtag(tag, ";href=");
+ if (sub != null) {
+ buffer.setSpan(new URLSpan(sub),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ } else if (tag.startsWith("annotation;")) {
+ int len = tag.length();
+ int next;
+
+ for (int t = tag.indexOf(';'); t < len; t = next) {
+ int eq = tag.indexOf('=', t);
+ if (eq < 0) {
+ break;
+ }
+
+ next = tag.indexOf(';', eq);
+ if (next < 0) {
+ next = len;
+ }
+
+ String key = tag.substring(t + 1, eq);
+ String value = tag.substring(eq + 1, next);
+
+ buffer.setSpan(new Annotation(key, value),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ }
+
+ i += 3;
+ }
+ return new SpannedString(buffer);
+ }
+
+ /**
+ * Returns a span for the specified color string representation.
+ * If the specified string does not represent a color (null, empty, etc.)
+ * the color black is returned instead.
+ *
+ * @param color The color as a string. Can be a resource reference,
+ * hexadecimal, octal or a name
+ * @param foreground True if the color will be used as the foreground color,
+ * false otherwise
+ *
+ * @return A CharacterStyle
+ *
+ * @see Color#parseColor(String)
+ */
+ private static CharacterStyle getColor(String color, boolean foreground) {
+ int c = 0xff000000;
+
+ if (!TextUtils.isEmpty(color)) {
+ if (color.startsWith("@")) {
+ Resources res = Resources.getSystem();
+ String name = color.substring(1);
+ int colorRes = res.getIdentifier(name, "color", "android");
+ if (colorRes != 0) {
+ ColorStateList colors = res.getColorStateList(colorRes, null);
+ if (foreground) {
+ return new TextAppearanceSpan(null, 0, 0, colors, null);
+ } else {
+ c = colors.getDefaultColor();
+ }
+ }
+ } else {
+ try {
+ c = Color.parseColor(color);
+ } catch (IllegalArgumentException e) {
+ c = Color.BLACK;
+ }
+ }
+ }
+
+ if (foreground) {
+ return new ForegroundColorSpan(c);
+ } else {
+ return new BackgroundColorSpan(c);
+ }
+ }
+
+ /**
+ * If a translator has messed up the edges of paragraph-level markup,
+ * fix it to actually cover the entire paragraph that it is attached to
+ * instead of just whatever range they put it on.
+ */
+ private static void addParagraphSpan(Spannable buffer, Object what,
+ int start, int end) {
+ int len = buffer.length();
+
+ if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
+ for (start--; start > 0; start--) {
+ if (buffer.charAt(start - 1) == '\n') {
+ break;
+ }
+ }
+ }
+
+ if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
+ for (end++; end < len; end++) {
+ if (buffer.charAt(end - 1) == '\n') {
+ break;
+ }
+ }
+ }
+
+ buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
+ }
+
+ private static String subtag(String full, String attribute) {
+ int start = full.indexOf(attribute);
+ if (start < 0) {
+ return null;
+ }
+
+ start += attribute.length();
+ int end = full.indexOf(';', start);
+
+ if (end < 0) {
+ return full.substring(start);
+ } else {
+ return full.substring(start, end);
+ }
+ }
+
+ /**
+ * Forces the text line to be the specified height, shrinking/stretching
+ * the ascent if possible, or the descent if shrinking the ascent further
+ * will make the text unreadable.
+ */
+ private static class Height implements LineHeightSpan.WithDensity {
+ private int mSize;
+ private static float sProportion = 0;
+
+ public Height(int size) {
+ mSize = size;
+ }
+
+ public void chooseHeight(CharSequence text, int start, int end,
+ int spanstartv, int v,
+ Paint.FontMetricsInt fm) {
+ // Should not get called, at least not by StaticLayout.
+ chooseHeight(text, start, end, spanstartv, v, fm, null);
+ }
+
+ public void chooseHeight(CharSequence text, int start, int end,
+ int spanstartv, int v,
+ Paint.FontMetricsInt fm, TextPaint paint) {
+ int size = mSize;
+ if (paint != null) {
+ size *= paint.density;
+ }
+
+ if (fm.bottom - fm.top < size) {
+ fm.top = fm.bottom - size;
+ fm.ascent = fm.ascent - size;
+ } else {
+ if (sProportion == 0) {
+ /*
+ * Calculate what fraction of the nominal ascent
+ * the height of a capital letter actually is,
+ * so that we won't reduce the ascent to less than
+ * that unless we absolutely have to.
+ */
+
+ Paint p = new Paint();
+ p.setTextSize(100);
+ Rect r = new Rect();
+ p.getTextBounds("ABCDEFG", 0, 7, r);
+
+ sProportion = (r.top) / p.ascent();
+ }
+
+ int need = (int) Math.ceil(-fm.top * sProportion);
+
+ if (size - fm.descent >= need) {
+ /*
+ * It is safe to shrink the ascent this much.
+ */
+
+ fm.top = fm.bottom - size;
+ fm.ascent = fm.descent - size;
+ } else if (size >= need) {
+ /*
+ * We can't show all the descent, but we can at least
+ * show all the ascent.
+ */
+
+ fm.top = fm.ascent = -need;
+ fm.bottom = fm.descent = fm.top + size;
+ } else {
+ /*
+ * Show as much of the ascent as we can, and no descent.
+ */
+
+ fm.top = fm.ascent = -size;
+ fm.bottom = fm.descent = 0;
+ }
+ }
+ }
+ }
+
+ /**
+ * Create from an existing string block native object. This is
+ * -extremely- dangerous -- only use it if you absolutely know what you
+ * are doing! The given native object must exist for the entire lifetime
+ * of this newly creating StringBlock.
+ */
+ @UnsupportedAppUsage
+ public StringBlock(long obj, boolean useSparse) {
+ mNative = obj;
+ mUseSparse = useSparse;
+ mOwnsNative = false;
+ if (localLOGV) Log.v(TAG, "Created string block " + this
+ + ": " + nativeGetSize(mNative));
+ }
+
+ private static native long nativeCreate(byte[] data,
+ int offset,
+ int size);
+ private static native int nativeGetSize(long obj);
+ private static native String nativeGetString(long obj, int idx);
+ private static native int[] nativeGetStyle(long obj, int idx);
+ private static native void nativeDestroy(long obj);
+}
diff --git a/android/content/res/ThemedResourceCache.java b/android/content/res/ThemedResourceCache.java
new file mode 100644
index 0000000..3270944
--- /dev/null
+++ b/android/content/res/ThemedResourceCache.java
@@ -0,0 +1,251 @@
+/*
+ * 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.res;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.Resources.Theme;
+import android.content.res.Resources.ThemeKey;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Data structure used for caching data against themes.
+ *
+ * @param <T> type of data to cache
+ */
+abstract class ThemedResourceCache<T> {
+ @UnsupportedAppUsage
+ private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries;
+ private LongSparseArray<WeakReference<T>> mUnthemedEntries;
+ private LongSparseArray<WeakReference<T>> mNullThemedEntries;
+
+ /**
+ * Adds a new theme-dependent entry to the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme against which this entry was inflated, or
+ * {@code null} if the entry has no theme applied
+ * @param entry the entry to cache
+ */
+ public void put(long key, @Nullable Theme theme, @NonNull T entry) {
+ put(key, theme, entry, true);
+ }
+
+ /**
+ * Adds a new entry to the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme against which this entry was inflated, or
+ * {@code null} if the entry has no theme applied
+ * @param entry the entry to cache
+ * @param usesTheme {@code true} if the entry is affected theme changes,
+ * {@code false} otherwise
+ */
+ public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) {
+ if (entry == null) {
+ return;
+ }
+
+ synchronized (this) {
+ final LongSparseArray<WeakReference<T>> entries;
+ if (!usesTheme) {
+ entries = getUnthemedLocked(true);
+ } else {
+ entries = getThemedLocked(theme, true);
+ }
+ if (entries != null) {
+ entries.put(key, new WeakReference<>(entry));
+ }
+ }
+ }
+
+ /**
+ * Returns an entry from the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme where the entry will be used
+ * @return a cached entry, or {@code null} if not in the cache
+ */
+ @Nullable
+ public T get(long key, @Nullable Theme theme) {
+ // The themed (includes null-themed) and unthemed caches are mutually
+ // exclusive, so we'll give priority to whichever one we think we'll
+ // hit first. Since most of the framework drawables are themed, that's
+ // probably going to be the themed cache.
+ synchronized (this) {
+ final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false);
+ if (themedEntries != null) {
+ final WeakReference<T> themedEntry = themedEntries.get(key);
+ if (themedEntry != null) {
+ return themedEntry.get();
+ }
+ }
+
+ final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false);
+ if (unthemedEntries != null) {
+ final WeakReference<T> unthemedEntry = unthemedEntries.get(key);
+ if (unthemedEntry != null) {
+ return unthemedEntry.get();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prunes cache entries that have been invalidated by a configuration
+ * change.
+ *
+ * @param configChanges a bitmask of configuration changes
+ */
+ @UnsupportedAppUsage
+ public void onConfigurationChange(@Config int configChanges) {
+ prune(configChanges);
+ }
+
+ /**
+ * Returns whether a cached entry has been invalidated by a configuration
+ * change.
+ *
+ * @param entry a cached entry
+ * @param configChanges a non-zero bitmask of configuration changes
+ * @return {@code true} if the entry is invalid, {@code false} otherwise
+ */
+ protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges);
+
+ /**
+ * Returns the cached data for the specified theme, optionally creating a
+ * new entry if one does not already exist.
+ *
+ * @param t the theme for which to return cached data
+ * @param create {@code true} to create an entry if one does not already
+ * exist, {@code false} otherwise
+ * @return the cached data for the theme, or {@code null} if the cache is
+ * empty and {@code create} was {@code false}
+ */
+ @Nullable
+ private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) {
+ if (t == null) {
+ if (mNullThemedEntries == null && create) {
+ mNullThemedEntries = new LongSparseArray<>(1);
+ }
+ return mNullThemedEntries;
+ }
+
+ if (mThemedEntries == null) {
+ if (create) {
+ mThemedEntries = new ArrayMap<>(1);
+ } else {
+ return null;
+ }
+ }
+
+ final ThemeKey key = t.getKey();
+ LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key);
+ if (cache == null && create) {
+ cache = new LongSparseArray<>(1);
+
+ final ThemeKey keyClone = key.clone();
+ mThemedEntries.put(keyClone, cache);
+ }
+
+ return cache;
+ }
+
+ /**
+ * Returns the theme-agnostic cached data.
+ *
+ * @param create {@code true} to create an entry if one does not already
+ * exist, {@code false} otherwise
+ * @return the theme-agnostic cached data, or {@code null} if the cache is
+ * empty and {@code create} was {@code false}
+ */
+ @Nullable
+ private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) {
+ if (mUnthemedEntries == null && create) {
+ mUnthemedEntries = new LongSparseArray<>(1);
+ }
+ return mUnthemedEntries;
+ }
+
+ /**
+ * Prunes cache entries affected by configuration changes or where weak
+ * references have expired.
+ *
+ * @param configChanges a bitmask of configuration changes, or {@code 0} to
+ * simply prune missing weak references
+ * @return {@code true} if the cache is completely empty after pruning
+ */
+ private boolean prune(@Config int configChanges) {
+ synchronized (this) {
+ if (mThemedEntries != null) {
+ for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
+ if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
+ mThemedEntries.removeAt(i);
+ }
+ }
+ }
+
+ pruneEntriesLocked(mNullThemedEntries, configChanges);
+ pruneEntriesLocked(mUnthemedEntries, configChanges);
+
+ return mThemedEntries == null && mNullThemedEntries == null
+ && mUnthemedEntries == null;
+ }
+ }
+
+ private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
+ @Config int configChanges) {
+ if (entries == null) {
+ return true;
+ }
+
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ final WeakReference<T> ref = entries.valueAt(i);
+ if (ref == null || pruneEntryLocked(ref.get(), configChanges)) {
+ entries.removeAt(i);
+ }
+ }
+
+ return entries.size() == 0;
+ }
+
+ private boolean pruneEntryLocked(@Nullable T entry, @Config int configChanges) {
+ return entry == null || (configChanges != 0
+ && shouldInvalidateEntry(entry, configChanges));
+ }
+
+ public synchronized void clear() {
+ if (mThemedEntries != null) {
+ mThemedEntries.clear();
+ }
+
+ if (mUnthemedEntries != null) {
+ mUnthemedEntries.clear();
+ }
+
+ if (mNullThemedEntries != null) {
+ mNullThemedEntries.clear();
+ }
+ }
+}
diff --git a/android/content/res/TypedArray.java b/android/content/res/TypedArray.java
new file mode 100644
index 0000000..d6f55d6
--- /dev/null
+++ b/android/content/res/TypedArray.java
@@ -0,0 +1,1403 @@
+/*
+ * Copyright (C) 2008 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.AnyRes;
+import android.annotation.ColorInt;
+import android.annotation.Nullable;
+import android.annotation.StyleableRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.Config;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.StrictMode;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import com.android.internal.util.XmlUtils;
+
+import dalvik.system.VMRuntime;
+
+import java.util.Arrays;
+
+/**
+ * Container for an array of values that were retrieved with
+ * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * or {@link Resources#obtainAttributes}. Be
+ * sure to call {@link #recycle} when done with them.
+ *
+ * The indices used to retrieve values from this structure correspond to
+ * the positions of the attributes given to obtainStyledAttributes.
+ */
+public class TypedArray implements AutoCloseable {
+
+ static TypedArray obtain(Resources res, int len) {
+ TypedArray attrs = res.mTypedArrayPool.acquire();
+ if (attrs == null) {
+ attrs = new TypedArray(res);
+ }
+
+ attrs.mRecycled = false;
+ // Reset the assets, which may have changed due to configuration changes
+ // or further resource loading.
+ attrs.mAssets = res.getAssets();
+ attrs.mMetrics = res.getDisplayMetrics();
+ attrs.resize(len);
+ return attrs;
+ }
+
+ // STYLE_ prefixed constants are offsets within the typed data array.
+ // Keep this in sync with libs/androidfw/include/androidfw/AttributeResolution.h
+ static final int STYLE_NUM_ENTRIES = 7;
+ 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;
+ static final int STYLE_SOURCE_RESOURCE_ID = 6;
+
+ @UnsupportedAppUsage
+ private final Resources mResources;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private DisplayMetrics mMetrics;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private AssetManager mAssets;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private boolean mRecycled;
+
+ @UnsupportedAppUsage
+ /*package*/ XmlBlock.Parser mXml;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ /*package*/ Resources.Theme mTheme;
+ /**
+ * mData is used to hold the value/id and other metadata about each attribute.
+ *
+ * [type, data, asset cookie, resource id, changing configuration, density]
+ *
+ * type - type of this attribute, see TypedValue#TYPE_*
+ *
+ * data - can be used in various ways:
+ * a) actual value of the attribute if type is between #TYPE_FIRST_INT and #TYPE_LAST_INT
+ * 1) color represented by an integer (#TYPE_INT_COLOR_*)
+ * 2) boolean represented by an integer (#TYPE_INT_BOOLEAN)
+ * 3) integer number (#TYPE_TYPE_INT_DEC or #TYPE_INT_HEX)
+ * 4) float number where integer gets interpreted as float (#TYPE_FLOAT, #TYPE_FRACTION
+ * and #TYPE_DIMENSION)
+ * b) index into string block inside AssetManager (#TYPE_STRING)
+ * c) attribute resource id in the current theme/style (#TYPE_ATTRIBUTE)
+ *
+ * asset cookie - used in two ways:
+ * a) for strings, drawables, and fonts it specifies the set of apk assets to look at
+ * (multi-apk case)
+ * b) cookie + asset as a unique identifier for drawable caches
+ *
+ * resource id - id that was finally used to resolve this attribute
+ *
+ * changing configuration - a mask of the configuration parameters for which the values in this
+ * attribute may change
+ *
+ * density - density of drawable pointed to by this attribute
+ */
+ @UnsupportedAppUsage
+ /*package*/ int[] mData;
+ /**
+ * Pointer to the start of the memory address of mData. It is passed via JNI and used to write
+ * to mData array directly from native code (AttributeResolution.cpp).
+ */
+ /*package*/ long mDataAddress;
+ @UnsupportedAppUsage
+ /*package*/ int[] mIndices;
+ /**
+ * Similar to mDataAddress, but instead it is a pointer to mIndices address.
+ */
+ /*package*/ long mIndicesAddress;
+ @UnsupportedAppUsage
+ /*package*/ int mLength;
+ @UnsupportedAppUsage
+ /*package*/ TypedValue mValue = new TypedValue();
+
+ private void resize(int len) {
+ mLength = len;
+ final int dataLen = len * STYLE_NUM_ENTRIES;
+ final int indicesLen = len + 1;
+ final VMRuntime runtime = VMRuntime.getRuntime();
+ if (mDataAddress == 0 || mData.length < dataLen) {
+ mData = (int[]) runtime.newNonMovableArray(int.class, dataLen);
+ mDataAddress = runtime.addressOf(mData);
+ mIndices = (int[]) runtime.newNonMovableArray(int.class, indicesLen);
+ mIndicesAddress = runtime.addressOf(mIndices);
+ }
+ }
+
+ /**
+ * Returns the number of values in this array.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public int length() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ return mLength;
+ }
+
+ /**
+ * Returns the number of indices in the array that actually have data. Attributes with a value
+ * of @empty are included, as this is an explicit indicator.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public int getIndexCount() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ return mIndices[0];
+ }
+
+ /**
+ * Returns an index in the array that has data. Attributes with a value of @empty are included,
+ * as this is an explicit indicator.
+ *
+ * @param at The index you would like to returned, ranging from 0 to
+ * {@link #getIndexCount()}.
+ *
+ * @return The index at the given offset, which can be used with
+ * {@link #getValue} and related APIs.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public int getIndex(int at) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ return mIndices[1+at];
+ }
+
+ /**
+ * Returns the Resources object this array was loaded from.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public Resources getResources() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ return mResources;
+ }
+
+ /**
+ * Retrieves the styled string value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a string, this method will attempt to coerce
+ * it to a string.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence holding string data. May be styled. Returns
+ * {@code null} if the attribute is not defined or could not be
+ * coerced to a string.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public CharSequence getText(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return null;
+ } else if (type == TypedValue.TYPE_STRING) {
+ return loadStringValueAt(index);
+ }
+
+ final TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ return v.coerceToString();
+ }
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieves the string value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a string, this method will attempt to coerce
+ * it to a string.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined or could
+ * not be coerced to a string.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ @Nullable
+ public String getString(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return null;
+ } else if (type == TypedValue.TYPE_STRING) {
+ return loadStringValueAt(index).toString();
+ }
+
+ final TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ final CharSequence cs = v.coerceToString();
+ return cs != null ? cs.toString() : null;
+ }
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieves the string value for the attribute at <var>index</var>, but
+ * only if that string comes from an immediate value in an XML file. That
+ * is, this does not allow references to string resources, string
+ * attributes, or conversions from other types. As such, this method
+ * will only return strings for TypedArray objects that come from
+ * attributes in an XML file.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined or is not
+ * an immediate string value.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public String getNonResourceString(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ if (type == TypedValue.TYPE_STRING) {
+ final int cookie = data[index + STYLE_ASSET_COOKIE];
+ if (cookie < 0) {
+ return mXml.getPooledString(data[index + STYLE_DATA]).toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the string value for the attribute at <var>index</var> that is
+ * not allowed to change with the given configurations.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param allowedChangingConfigs Bit mask of configurations from
+ * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
+ *
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String getNonConfigurationString(@StyleableRes int index,
+ @Config int allowedChangingConfigs) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
+ if ((changingConfigs & ~allowedChangingConfigs) != 0) {
+ return null;
+ }
+ if (type == TypedValue.TYPE_NULL) {
+ return null;
+ } else if (type == TypedValue.TYPE_STRING) {
+ return loadStringValueAt(index).toString();
+ }
+
+ final TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ final CharSequence cs = v.coerceToString();
+ return cs != null ? cs.toString() : null;
+ }
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getNonConfigurationString of bad type: 0x"
+ + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve the boolean value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is an integer value, this method returns false if the
+ * attribute is equal to zero, and true otherwise.
+ * If the attribute is not a boolean or integer value,
+ * this method will attempt to coerce it to an integer using
+ * {@link Integer#decode(String)} and return whether it is equal to zero.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
+ *
+ * @return Boolean value of the attribute, or defValue if the attribute was
+ * not defined or could not be coerced to an integer.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public boolean getBoolean(@StyleableRes int index, boolean defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ 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 + STYLE_DATA] != 0;
+ }
+
+ final TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
+ }
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not an integer, this method will attempt to coerce
+ * it to an integer using {@link Integer#decode(String)}.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
+ *
+ * @return Integer value of the attribute, or defValue if the attribute was
+ * not defined or could not be coerced to an integer.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public int getInt(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ 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 + STYLE_DATA];
+ }
+
+ final TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToInt(v.coerceToString(), defValue);
+ }
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve the float value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a float or an integer, this method will attempt
+ * to coerce it to a float using {@link Float#parseFloat(String)}.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Attribute float value, or defValue if the attribute was
+ * not defined or could not be coerced to a float.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public float getFloat(@StyleableRes int index, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ 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 + STYLE_DATA]);
+ } else if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index + STYLE_DATA];
+ }
+
+ final TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ final CharSequence str = v.coerceToString();
+ if (str != null) {
+ StrictMode.noteResourceMismatch(v);
+ return Float.parseFloat(str.toString());
+ }
+ }
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve the color value for the attribute at <var>index</var>. If
+ * the attribute references a color resource holding a complex
+ * {@link android.content.res.ColorStateList}, then the default color from
+ * the set is returned.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not an integer color or color state list.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute color value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color or color state list.
+ */
+ @ColorInt
+ public int getColor(@StyleableRes int index, @ColorInt int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ 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 + STYLE_DATA];
+ } else if (type == TypedValue.TYPE_STRING) {
+ final TypedValue value = mValue;
+ if (getValueAt(index, value)) {
+ final ColorStateList csl = mResources.loadColorStateList(
+ value, value.resourceId, mTheme);
+ return csl.getDefaultColor();
+ }
+ return defValue;
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to color: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve the ComplexColor for the attribute at <var>index</var>.
+ * The value may be either a {@link android.content.res.ColorStateList} which can wrap a simple
+ * color value or a {@link android.content.res.GradientColor}
+ * <p>
+ * This method will return {@code null} if the attribute is not defined or
+ * is not an integer color, color state list or GradientColor.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return ComplexColor for the attribute, or {@code null} if not defined.
+ * @throws RuntimeException if the attribute if the TypedArray has already
+ * been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color, color state list or GradientColor.
+ * @hide
+ */
+ @Nullable
+ public ComplexColor getComplexColor(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final TypedValue value = mValue;
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
+ if (value.type == TypedValue.TYPE_ATTRIBUTE) {
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
+ }
+ return mResources.loadComplexColor(value, value.resourceId, mTheme);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the ColorStateList for the attribute at <var>index</var>.
+ * The value may be either a single solid color or a reference to
+ * a color or complex {@link android.content.res.ColorStateList}
+ * description.
+ * <p>
+ * This method will return {@code null} if the attribute is not defined or
+ * is not an integer color or color state list.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return ColorStateList for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the attribute if the TypedArray has already
+ * been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color or color state list.
+ */
+ @Nullable
+ public ColorStateList getColorStateList(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final TypedValue value = mValue;
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
+ if (value.type == TypedValue.TYPE_ATTRIBUTE) {
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
+ }
+ return mResources.loadColorStateList(value, value.resourceId, mTheme);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ * <p>
+ * Unlike {@link #getInt(int, int)}, this method will throw an exception if
+ * the attribute is defined but is not an integer.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute integer value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
+ */
+ public int getInteger(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ 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 + STYLE_DATA];
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to integer: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * conversions are based on the current {@link DisplayMetrics}
+ * associated with the resources this {@link TypedArray} object
+ * came from.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
+ *
+ * @see #getDimensionPixelOffset
+ * @see #getDimensionPixelSize
+ */
+ public float getDimension(@StyleableRes int index, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ 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 + STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to dimension: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as an offset in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for you. An offset conversion involves simply
+ * truncating the base value to an integer.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelSize
+ */
+ public int getDimensionPixelOffset(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ 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 + STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to dimension: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as a size in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for use as a size. A size conversion involves
+ * rounding the base value, and ensuring that a non-zero base value
+ * is at least one pixel in size.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a dimension.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelOffset
+ */
+ public int getDimensionPixelSize(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ 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 + STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to dimension: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension or integer (enum).
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param name Textual name of attribute for error reporting.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a dimension or integer (enum).
+ */
+ public int getLayoutDimension(@StyleableRes int index, String name) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index + STYLE_DATA];
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException(getPositionDescription()
+ + ": You must supply a " + name + " attribute.");
+ }
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param defValue The default value to return if this attribute is not
+ * default or contains the wrong type of data.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public int getLayoutDimension(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index + STYLE_DATA];
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieves a fractional unit attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a fraction.
+ */
+ public float getFraction(@StyleableRes int index, int base, int pbase, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final int attrIndex = index;
+ index *= STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ 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 + STYLE_DATA], base, pbase);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
+ }
+
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to fraction: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieves the resource identifier for the attribute at
+ * <var>index</var>. Note that attribute resource as resolved when
+ * the overall {@link TypedArray} object is retrieved. As a
+ * result, this function will return the resource identifier of the
+ * final resource value that was found, <em>not</em> necessarily the
+ * original resource that was specified by the attribute.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ @AnyRes
+ public int getResourceId(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) {
+ final int resid = data[index + STYLE_RESOURCE_ID];
+ if (resid != 0) {
+ return resid;
+ }
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieves the theme attribute resource identifier for the attribute at
+ * <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or not a
+ * resource.
+ *
+ * @return Theme attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @hide
+ */
+ public int getThemeAttributeId(@StyleableRes int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+ return data[index + STYLE_DATA];
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieve the Drawable for the attribute at <var>index</var>.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a color or drawable resource.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Drawable for the attribute, or {@code null} if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a color or drawable resource.
+ */
+ @Nullable
+ public Drawable getDrawable(@StyleableRes int index) {
+ return getDrawableForDensity(index, 0);
+ }
+
+ /**
+ * Version of {@link #getDrawable(int)} that accepts an override density.
+ * @hide
+ */
+ @Nullable
+ public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final TypedValue value = mValue;
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
+ if (value.type == TypedValue.TYPE_ATTRIBUTE) {
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
+ }
+
+ if (density > 0) {
+ // If the density is overridden, the value in the TypedArray will not reflect this.
+ // Do a separate lookup of the resourceId with the density override.
+ mResources.getValueForDensity(value.resourceId, density, value, true);
+ }
+ return mResources.loadDrawable(value, value.resourceId, density, mTheme);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the Typeface for the attribute at <var>index</var>.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a font.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Typeface for the attribute, or {@code null} if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a font resource.
+ */
+ @Nullable
+ public Typeface getFont(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final TypedValue value = mValue;
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
+ if (value.type == TypedValue.TYPE_ATTRIBUTE) {
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
+ }
+ return mResources.getFont(value, value.resourceId);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the CharSequence[] for the attribute at <var>index</var>.
+ * This gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getTextArray Resources.getTextArray} of the owning
+ * Resources object to retrieve its String[].
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a text array resource.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence[] for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public CharSequence[] getTextArray(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final TypedValue value = mValue;
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
+ return mResources.getTextArray(value.resourceId);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param outValue TypedValue object in which to place the attribute's
+ * data.
+ *
+ * @return {@code true} if the value was retrieved and not @empty, {@code false} otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public boolean getValue(@StyleableRes int index, TypedValue outValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ return getValueAt(index * STYLE_NUM_ENTRIES, outValue);
+ }
+
+ /**
+ * Returns the type of attribute at the specified index.
+ *
+ * @param index Index of attribute whose type to retrieve.
+ *
+ * @return Attribute type.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public int getType(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ return mData[index + STYLE_TYPE];
+ }
+
+ /**
+ * Returns the resource ID of the style or layout against which the specified attribute was
+ * resolved, otherwise returns defValue.
+ *
+ * For example, if you we resolving two attributes {@code android:attribute1} and
+ * {@code android:attribute2} and you were inflating a {@link android.view.View} from
+ * {@code layout/my_layout.xml}:
+ * <pre>
+ * <View
+ * style="@style/viewStyle"
+ * android:layout_width="wrap_content"
+ * android:layout_height="wrap_content"
+ * android:attribute1="foo"/>
+ * </pre>
+ *
+ * and {@code @style/viewStyle} is:
+ * <pre>
+ * <style android:name="viewStyle">
+ * <item name="android:attribute2">bar<item/>
+ * <style/>
+ * </pre>
+ *
+ * then resolved {@link TypedArray} will have values that return source resource ID of
+ * {@code R.layout.my_layout} for {@code android:attribute1} and {@code R.style.viewStyle} for
+ * {@code android:attribute2}.
+ *
+ * @param index Index of attribute whose source style to retrieve.
+ * @param defaultValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Either a style resource ID, layout resource ID, or defaultValue if it was not
+ * resolved in a style or layout.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ @AnyRes
+ public int getSourceResourceId(@StyleableRes int index, @AnyRes int defaultValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int resid = mData[index + STYLE_SOURCE_RESOURCE_ID];
+ if (resid != 0) {
+ return resid;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Determines whether there is an attribute at <var>index</var>.
+ * <p>
+ * <strong>Note:</strong> If the attribute was set to {@code @empty} or
+ * {@code @undefined}, this method returns {@code false}.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return True if the attribute has a value, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public boolean hasValue(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ return type != TypedValue.TYPE_NULL;
+ }
+
+ /**
+ * Determines whether there is an attribute at <var>index</var>, returning
+ * {@code true} if the attribute was explicitly set to {@code @empty} and
+ * {@code false} only if the attribute was undefined.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return True if the attribute has a value or is empty, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public boolean hasValueOrEmpty(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ return type != TypedValue.TYPE_NULL
+ || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>
+ * and return a temporary object holding its data. This object is only
+ * valid until the next call on to {@link TypedArray}.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Returns a TypedValue object if the attribute is defined,
+ * containing its data; otherwise returns null. (You will not
+ * receive a TypedValue whose type is TYPE_NULL.)
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public TypedValue peekValue(@StyleableRes int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ final TypedValue value = mValue;
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
+ return value;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a message about the parser state suitable for printing error messages.
+ *
+ * @return Human-readable description of current parser state.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public String getPositionDescription() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ return mXml != null ? mXml.getPositionDescription() : "<internal>";
+ }
+
+ /**
+ * Recycles the TypedArray, to be re-used by a later caller. After calling
+ * this function you must not ever touch the typed array again.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public void recycle() {
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
+ }
+
+ mRecycled = true;
+
+ // These may have been set by the client.
+ mXml = null;
+ mTheme = null;
+ mAssets = null;
+
+ mResources.mTypedArrayPool.release(this);
+ }
+
+ /**
+ * Recycles the TypedArray, to be re-used by a later caller. After calling
+ * this function you must not ever touch the typed array again.
+ *
+ * @see #recycle()
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ */
+ public void close() {
+ recycle();
+ }
+
+ /**
+ * Extracts theme attributes from a typed array for later resolution using
+ * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
+ * Removes the entries from the typed array so that subsequent calls to typed
+ * getters will return the default value without crashing.
+ *
+ * @return an array of length {@link #getIndexCount()} populated with theme
+ * attributes, or null if there are no theme attributes in the typed
+ * array
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @hide
+ */
+ @Nullable
+ @UnsupportedAppUsage
+ public int[] extractThemeAttrs() {
+ return extractThemeAttrs(null);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @UnsupportedAppUsage
+ public int[] extractThemeAttrs(@Nullable int[] scrap) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ int[] attrs = null;
+
+ final int[] data = mData;
+ final int N = length();
+ for (int i = 0; i < N; i++) {
+ 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 + STYLE_TYPE] = TypedValue.TYPE_NULL;
+
+ final int attr = data[index + STYLE_DATA];
+ if (attr == 0) {
+ // Useless data, ignore.
+ continue;
+ }
+
+ // Ensure we have a usable attribute array.
+ if (attrs == null) {
+ if (scrap != null && scrap.length == N) {
+ attrs = scrap;
+ Arrays.fill(attrs, 0);
+ } else {
+ attrs = new int[N];
+ }
+ }
+
+ attrs[i] = attr;
+ }
+
+ return attrs;
+ }
+
+ /**
+ * Return a mask of the configuration parameters for which the values in
+ * this typed array may change.
+ *
+ * @return Returns a mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @see android.content.pm.ActivityInfo
+ */
+ public @Config int getChangingConfigurations() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ @Config int changingConfig = 0;
+
+ final int[] data = mData;
+ final int N = length();
+ for (int i = 0; i < N; i++) {
+ 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 + STYLE_CHANGING_CONFIGURATIONS]);
+ }
+ return changingConfig;
+ }
+
+ @UnsupportedAppUsage
+ private boolean getValueAt(int index, TypedValue outValue) {
+ final int[] data = mData;
+ final int type = data[index + STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return false;
+ }
+ outValue.type = type;
+ 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 + STYLE_CHANGING_CONFIGURATIONS]);
+ outValue.density = data[index + STYLE_DENSITY];
+ outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
+ outValue.sourceResourceId = data[index + STYLE_SOURCE_RESOURCE_ID];
+ return true;
+ }
+
+ @Nullable
+ private CharSequence loadStringValueAt(int index) {
+ final int[] data = mData;
+ final int cookie = data[index + STYLE_ASSET_COOKIE];
+ if (cookie < 0) {
+ if (mXml != null) {
+ return mXml.getPooledString(data[index + STYLE_DATA]);
+ }
+ return null;
+ }
+ return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
+ }
+
+ /** @hide */
+ protected TypedArray(Resources resources) {
+ mResources = resources;
+ mMetrics = mResources.getDisplayMetrics();
+ mAssets = mResources.getAssets();
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(mData);
+ }
+}
diff --git a/android/content/res/TypedArray_Delegate.java b/android/content/res/TypedArray_Delegate.java
new file mode 100644
index 0000000..faa8852
--- /dev/null
+++ b/android/content/res/TypedArray_Delegate.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.TypedValue;
+
+public class TypedArray_Delegate {
+
+ @LayoutlibDelegate
+ public static boolean getValueAt(TypedArray theTypedArray, int index, TypedValue outValue) {
+ // pass
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtain(Resources res, int len) {
+ return BridgeTypedArray.obtain(res, len);
+ }
+}
diff --git a/android/content/res/XmlBlock.java b/android/content/res/XmlBlock.java
new file mode 100644
index 0000000..53dfed3
--- /dev/null
+++ b/android/content/res/XmlBlock.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2006 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 static android.content.res.Resources.ID_NULL;
+
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.util.TypedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+
+import dalvik.annotation.optimization.FastNative;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Wrapper around a compiled XML file.
+ *
+ * {@hide}
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class XmlBlock implements AutoCloseable {
+ private static final boolean DEBUG=false;
+
+ @UnsupportedAppUsage
+ public XmlBlock(byte[] data) {
+ mAssets = null;
+ mNative = nativeCreate(data, 0, data.length);
+ mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+ }
+
+ public XmlBlock(byte[] data, int offset, int size) {
+ mAssets = null;
+ mNative = nativeCreate(data, offset, size);
+ mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+ }
+
+ @Override
+ public void close() {
+ synchronized (this) {
+ if (mOpen) {
+ mOpen = false;
+ decOpenCountLocked();
+ }
+ }
+ }
+
+ private void decOpenCountLocked() {
+ mOpenCount--;
+ if (mOpenCount == 0) {
+ nativeDestroy(mNative);
+ if (mAssets != null) {
+ mAssets.xmlBlockGone(hashCode());
+ }
+ }
+ }
+
+ @UnsupportedAppUsage
+ public XmlResourceParser newParser() {
+ return newParser(ID_NULL);
+ }
+
+ public XmlResourceParser newParser(@AnyRes int resId) {
+ synchronized (this) {
+ if (mNative != 0) {
+ return new Parser(nativeCreateParseState(mNative, resId), this);
+ }
+ return null;
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public final class Parser implements XmlResourceParser {
+ Parser(long parseState, XmlBlock block) {
+ mParseState = parseState;
+ mBlock = block;
+ block.mOpenCount++;
+ }
+
+ @AnyRes
+ public int getSourceResId() {
+ return nativeGetSourceResId(mParseState);
+ }
+
+ public void setFeature(String name, boolean state) throws XmlPullParserException {
+ if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
+ return;
+ }
+ if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
+ return;
+ }
+ throw new XmlPullParserException("Unsupported feature: " + name);
+ }
+ public boolean getFeature(String name) {
+ if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
+ return true;
+ }
+ if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
+ return true;
+ }
+ return false;
+ }
+ public void setProperty(String name, Object value) throws XmlPullParserException {
+ throw new XmlPullParserException("setProperty() not supported");
+ }
+ public Object getProperty(String name) {
+ return null;
+ }
+ public void setInput(Reader in) throws XmlPullParserException {
+ throw new XmlPullParserException("setInput() not supported");
+ }
+ public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
+ throw new XmlPullParserException("setInput() not supported");
+ }
+ public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
+ throw new XmlPullParserException("defineEntityReplacementText() not supported");
+ }
+ public String getNamespacePrefix(int pos) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespacePrefix() not supported");
+ }
+ public String getInputEncoding() {
+ return null;
+ }
+ public String getNamespace(String prefix) {
+ throw new RuntimeException("getNamespace() not supported");
+ }
+ public int getNamespaceCount(int depth) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespaceCount() not supported");
+ }
+ public String getPositionDescription() {
+ return "Binary XML file line #" + getLineNumber();
+ }
+ public String getNamespaceUri(int pos) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespaceUri() not supported");
+ }
+ public int getColumnNumber() {
+ return -1;
+ }
+ public int getDepth() {
+ return mDepth;
+ }
+ @Nullable
+ public String getText() {
+ int id = nativeGetText(mParseState);
+ return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
+ }
+ public int getLineNumber() {
+ return nativeGetLineNumber(mParseState);
+ }
+ public int getEventType() throws XmlPullParserException {
+ return mEventType;
+ }
+ public boolean isWhitespace() throws XmlPullParserException {
+ // whitespace was stripped by aapt.
+ return false;
+ }
+ public String getPrefix() {
+ throw new RuntimeException("getPrefix not supported");
+ }
+ public char[] getTextCharacters(int[] holderForStartAndLength) {
+ String txt = getText();
+ char[] chars = null;
+ if (txt != null) {
+ holderForStartAndLength[0] = 0;
+ holderForStartAndLength[1] = txt.length();
+ chars = new char[txt.length()];
+ txt.getChars(0, txt.length(), chars, 0);
+ }
+ return chars;
+ }
+ @Nullable
+ public String getNamespace() {
+ int id = nativeGetNamespace(mParseState);
+ return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : "";
+ }
+ @Nullable
+ public String getName() {
+ int id = nativeGetName(mParseState);
+ return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
+ }
+ @NonNull
+ public String getAttributeNamespace(int index) {
+ int id = nativeGetAttributeNamespace(mParseState, index);
+ if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id);
+ if (id >= 0) return getSequenceString(mStrings.getSequence(id));
+ else if (id == -1) return "";
+ throw new IndexOutOfBoundsException(String.valueOf(index));
+ }
+ @NonNull
+ public String getAttributeName(int index) {
+ int id = nativeGetAttributeName(mParseState, index);
+ if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id);
+ if (id >= 0) return getSequenceString(mStrings.getSequence(id));
+ throw new IndexOutOfBoundsException(String.valueOf(index));
+ }
+ public String getAttributePrefix(int index) {
+ throw new RuntimeException("getAttributePrefix not supported");
+ }
+ public boolean isEmptyElementTag() throws XmlPullParserException {
+ // XXX Need to detect this.
+ return false;
+ }
+ public int getAttributeCount() {
+ return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1;
+ }
+ @NonNull
+ public String getAttributeValue(int index) {
+ int id = nativeGetAttributeStringValue(mParseState, index);
+ if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id);
+ if (id >= 0) return getSequenceString(mStrings.getSequence(id));
+
+ // May be some other type... check and try to convert if so.
+ int t = nativeGetAttributeDataType(mParseState, index);
+ if (t == TypedValue.TYPE_NULL) {
+ throw new IndexOutOfBoundsException(String.valueOf(index));
+ }
+
+ int v = nativeGetAttributeData(mParseState, index);
+ return TypedValue.coerceToString(t, v);
+ }
+ public String getAttributeType(int index) {
+ return "CDATA";
+ }
+ public boolean isAttributeDefault(int index) {
+ return false;
+ }
+ public int nextToken() throws XmlPullParserException,IOException {
+ return next();
+ }
+ public String getAttributeValue(String namespace, String name) {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, name);
+ if (idx >= 0) {
+ if (DEBUG) System.out.println("getAttributeName of "
+ + namespace + ":" + name + " index = " + idx);
+ if (DEBUG) System.out.println(
+ "Namespace=" + getAttributeNamespace(idx)
+ + "Name=" + getAttributeName(idx)
+ + ", Value=" + getAttributeValue(idx));
+ return getAttributeValue(idx);
+ }
+ return null;
+ }
+ public int next() throws XmlPullParserException,IOException {
+ if (!mStarted) {
+ mStarted = true;
+ return START_DOCUMENT;
+ }
+ if (mParseState == 0) {
+ return END_DOCUMENT;
+ }
+ int ev = nativeNext(mParseState);
+ if (mDecNextDepth) {
+ mDepth--;
+ mDecNextDepth = false;
+ }
+ switch (ev) {
+ case START_TAG:
+ mDepth++;
+ break;
+ case END_TAG:
+ mDecNextDepth = true;
+ break;
+ }
+ mEventType = ev;
+ if (ev == END_DOCUMENT) {
+ // Automatically close the parse when we reach the end of
+ // a document, since the standard XmlPullParser interface
+ // doesn't have such an API so most clients will leave us
+ // dangling.
+ close();
+ }
+ return ev;
+ }
+ public void require(int type, String namespace, String name) throws XmlPullParserException,IOException {
+ if (type != getEventType()
+ || (namespace != null && !namespace.equals( getNamespace () ) )
+ || (name != null && !name.equals( getName() ) ) )
+ throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription());
+ }
+ public String nextText() throws XmlPullParserException,IOException {
+ if(getEventType() != START_TAG) {
+ throw new XmlPullParserException(
+ getPositionDescription()
+ + ": parser must be on START_TAG to read next text", this, null);
+ }
+ int eventType = next();
+ if(eventType == TEXT) {
+ String result = getText();
+ eventType = next();
+ if(eventType != END_TAG) {
+ throw new XmlPullParserException(
+ getPositionDescription()
+ + ": event TEXT it must be immediately followed by END_TAG", this, null);
+ }
+ return result;
+ } else if(eventType == END_TAG) {
+ return "";
+ } else {
+ throw new XmlPullParserException(
+ getPositionDescription()
+ + ": parser must be on START_TAG or TEXT to read text", this, null);
+ }
+ }
+ public int nextTag() throws XmlPullParserException,IOException {
+ int eventType = next();
+ if(eventType == TEXT && isWhitespace()) { // skip whitespace
+ eventType = next();
+ }
+ if (eventType != START_TAG && eventType != END_TAG) {
+ throw new XmlPullParserException(
+ getPositionDescription()
+ + ": expected start or end tag", this, null);
+ }
+ return eventType;
+ }
+
+ public int getAttributeNameResource(int index) {
+ return nativeGetAttributeResource(mParseState, index);
+ }
+
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+ if (idx >= 0) {
+ return getAttributeListValue(idx, options, defaultValue);
+ }
+ return defaultValue;
+ }
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+ if (idx >= 0) {
+ return getAttributeBooleanValue(idx, defaultValue);
+ }
+ return defaultValue;
+ }
+ public int getAttributeResourceValue(String namespace, String attribute,
+ int defaultValue) {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+ if (idx >= 0) {
+ return getAttributeResourceValue(idx, defaultValue);
+ }
+ return defaultValue;
+ }
+ public int getAttributeIntValue(String namespace, String attribute,
+ int defaultValue) {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+ if (idx >= 0) {
+ return getAttributeIntValue(idx, defaultValue);
+ }
+ return defaultValue;
+ }
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue)
+ {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+ if (idx >= 0) {
+ return getAttributeUnsignedIntValue(idx, defaultValue);
+ }
+ return defaultValue;
+ }
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
+ if (idx >= 0) {
+ return getAttributeFloatValue(idx, defaultValue);
+ }
+ return defaultValue;
+ }
+
+ public int getAttributeListValue(int idx,
+ String[] options, int defaultValue) {
+ int t = nativeGetAttributeDataType(mParseState, idx);
+ int v = nativeGetAttributeData(mParseState, idx);
+ if (t == TypedValue.TYPE_STRING) {
+ return XmlUtils.convertValueToList(
+ mStrings.getSequence(v), options, defaultValue);
+ }
+ return v;
+ }
+ public boolean getAttributeBooleanValue(int idx,
+ boolean defaultValue) {
+ int t = nativeGetAttributeDataType(mParseState, idx);
+ // Note: don't attempt to convert any other types, because
+ // we want to count on aapt doing the conversion for us.
+ if (t >= TypedValue.TYPE_FIRST_INT &&
+ t <= TypedValue.TYPE_LAST_INT) {
+ return nativeGetAttributeData(mParseState, idx) != 0;
+ }
+ return defaultValue;
+ }
+ public int getAttributeResourceValue(int idx, int defaultValue) {
+ int t = nativeGetAttributeDataType(mParseState, idx);
+ // Note: don't attempt to convert any other types, because
+ // we want to count on aapt doing the conversion for us.
+ if (t == TypedValue.TYPE_REFERENCE) {
+ return nativeGetAttributeData(mParseState, idx);
+ }
+ return defaultValue;
+ }
+ public int getAttributeIntValue(int idx, int defaultValue) {
+ int t = nativeGetAttributeDataType(mParseState, idx);
+ // Note: don't attempt to convert any other types, because
+ // we want to count on aapt doing the conversion for us.
+ if (t >= TypedValue.TYPE_FIRST_INT &&
+ t <= TypedValue.TYPE_LAST_INT) {
+ return nativeGetAttributeData(mParseState, idx);
+ }
+ return defaultValue;
+ }
+ public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
+ int t = nativeGetAttributeDataType(mParseState, idx);
+ // Note: don't attempt to convert any other types, because
+ // we want to count on aapt doing the conversion for us.
+ if (t >= TypedValue.TYPE_FIRST_INT &&
+ t <= TypedValue.TYPE_LAST_INT) {
+ return nativeGetAttributeData(mParseState, idx);
+ }
+ return defaultValue;
+ }
+ public float getAttributeFloatValue(int idx, float defaultValue) {
+ int t = nativeGetAttributeDataType(mParseState, idx);
+ // Note: don't attempt to convert any other types, because
+ // we want to count on aapt doing the conversion for us.
+ if (t == TypedValue.TYPE_FLOAT) {
+ return Float.intBitsToFloat(
+ nativeGetAttributeData(mParseState, idx));
+ }
+ throw new RuntimeException("not a float!");
+ }
+ @Nullable
+ public String getIdAttribute() {
+ int id = nativeGetIdAttribute(mParseState);
+ return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
+ }
+ @Nullable
+ public String getClassAttribute() {
+ int id = nativeGetClassAttribute(mParseState);
+ return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
+ }
+
+ public int getIdAttributeResourceValue(int defaultValue) {
+ //todo: create and use native method
+ return getAttributeResourceValue(null, "id", defaultValue);
+ }
+
+ public int getStyleAttribute() {
+ return nativeGetStyleAttribute(mParseState);
+ }
+
+ private String getSequenceString(@Nullable CharSequence str) {
+ if (str == null) {
+ // A value of null retrieved from a StringPool indicates that retrieval of the
+ // string failed due to incremental installation. The presence of all the XmlBlock
+ // data is verified when it is created, so this exception must not be possible.
+ throw new IllegalStateException("Retrieving a string from the StringPool of an"
+ + " XmlBlock should never fail");
+ }
+ return str.toString();
+ }
+
+ public void close() {
+ synchronized (mBlock) {
+ if (mParseState != 0) {
+ nativeDestroyParseState(mParseState);
+ mParseState = 0;
+ mBlock.decOpenCountLocked();
+ }
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ close();
+ }
+
+ @Nullable
+ /*package*/ final CharSequence getPooledString(int id) {
+ return mStrings.getSequence(id);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ /*package*/ long mParseState;
+ @UnsupportedAppUsage
+ private final XmlBlock mBlock;
+ private boolean mStarted = false;
+ private boolean mDecNextDepth = false;
+ private int mDepth = 0;
+ private int mEventType = START_DOCUMENT;
+ }
+
+ protected void finalize() throws Throwable {
+ close();
+ }
+
+ /**
+ * Create from an existing xml block native object. This is
+ * -extremely- dangerous -- only use it if you absolutely know what you
+ * are doing! The given native object must exist for the entire lifetime
+ * of this newly creating XmlBlock.
+ */
+ XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
+ mAssets = assets;
+ mNative = xmlBlock;
+ mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
+ }
+
+ private @Nullable final AssetManager mAssets;
+ private final long mNative;
+ /*package*/ final StringBlock mStrings;
+ private boolean mOpen = true;
+ private int mOpenCount = 1;
+
+ private static final native long nativeCreate(byte[] data,
+ int offset,
+ int size);
+ private static final native long nativeGetStringBlock(long obj);
+ private static final native long nativeCreateParseState(long obj, int resId);
+ private static final native void nativeDestroyParseState(long state);
+ private static final native void nativeDestroy(long obj);
+
+ // ----------- @FastNative ------------------
+
+ @FastNative
+ /*package*/ static final native int nativeNext(long state);
+ @FastNative
+ private static final native int nativeGetNamespace(long state);
+ @FastNative
+ /*package*/ static final native int nativeGetName(long state);
+ @FastNative
+ private static final native int nativeGetText(long state);
+ @FastNative
+ private static final native int nativeGetLineNumber(long state);
+ @FastNative
+ private static final native int nativeGetAttributeCount(long state);
+ @FastNative
+ private static final native int nativeGetAttributeNamespace(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeName(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeResource(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeDataType(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeData(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeStringValue(long state, int idx);
+ @FastNative
+ private static final native int nativeGetIdAttribute(long state);
+ @FastNative
+ private static final native int nativeGetClassAttribute(long state);
+ @FastNative
+ private static final native int nativeGetStyleAttribute(long state);
+ @FastNative
+ private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
+ @FastNative
+ private static final native int nativeGetSourceResId(long state);
+}
diff --git a/android/content/res/XmlResourceParser.java b/android/content/res/XmlResourceParser.java
new file mode 100644
index 0000000..86f4ba6
--- /dev/null
+++ b/android/content/res/XmlResourceParser.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006 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.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The XML parsing interface returned for an XML resource. This is a standard
+ * {@link XmlPullParser} interface but also extends {@link AttributeSet} and
+ * adds an additional {@link #close()} method for the client to indicate when
+ * 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.
+ */
+ public void close();
+}
+
diff --git a/android/content/res/loader/AssetsProvider.java b/android/content/res/loader/AssetsProvider.java
new file mode 100644
index 0000000..0f8f1d1
--- /dev/null
+++ b/android/content/res/loader/AssetsProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Provides callbacks that allow for the value of a file-based resources or assets of a
+ * {@link ResourcesProvider} to be specified or overridden.
+ */
+public interface AssetsProvider {
+
+ /**
+ * Callback that allows the value of a file-based resources or asset to be specified or
+ * overridden.
+ *
+ * <p>The system will take ownership of the file descriptor returned from this method, so
+ * {@link ParcelFileDescriptor#dup() dup} the file descriptor before returning if the system
+ * should not own it.
+ *
+ * <p>There are two situations in which this method will be called:
+ * <ul>
+ * <li>AssetManager is queried for an InputStream of an asset using APIs like
+ * {@link AssetManager#open} and {@link AssetManager#openXmlResourceParser}.
+ * <li>AssetManager is resolving the value of a file-based resource provided by the
+ * {@link ResourcesProvider} this instance is associated with.
+ * </ul>
+ *
+ * <p>If the value retrieved from this callback is null, AssetManager will attempt to find the
+ * file-based resource or asset within the APK provided by the ResourcesProvider this instance
+ * is associated with.
+ *
+ * @param path the asset path being loaded
+ * @param accessMode the {@link AssetManager} access mode
+ *
+ * @see AssetManager#open
+ */
+ @Nullable
+ default AssetFileDescriptor loadAssetFd(@NonNull String path, int accessMode) {
+ return null;
+ }
+}
diff --git a/android/content/res/loader/ResourcesLoader.java b/android/content/res/loader/ResourcesLoader.java
new file mode 100644
index 0000000..c308400
--- /dev/null
+++ b/android/content/res/loader/ResourcesLoader.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2019 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.loader;
+
+import android.annotation.NonNull;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A container for supplying {@link ResourcesProvider ResourcesProvider(s)} to {@link Resources}
+ * objects.
+ *
+ * <p>{@link ResourcesLoader ResourcesLoader(s)} are added to Resources objects to supply
+ * additional resources and assets or modify the values of existing resources and assets. Multiple
+ * Resources objects can share the same ResourcesLoaders and ResourcesProviders. Changes to the list
+ * of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
+ * objects that use the loader.
+ *
+ * <p>Loaders must be added to Resources objects in increasing precedence order. A loader will
+ * override the resources and assets of loaders added before itself.
+ *
+ * <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
+ * provider will override the resources and assets of providers listed before itself.
+ *
+ * <p>Modifying the list of providers a loader contains or the list of loaders a Resources object
+ * contains can cause lock contention with the UI thread. APIs that modify the lists of loaders or
+ * providers should only be used on the UI thread. Providers can be instantiated on any thread
+ * without causing lock contention.
+ */
+public class ResourcesLoader {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private ApkAssets[] mApkAssets;
+
+ @GuardedBy("mLock")
+ private ResourcesProvider[] mPreviousProviders;
+
+ @GuardedBy("mLock")
+ private ResourcesProvider[] mProviders;
+
+ @GuardedBy("mLock")
+ private ArrayMap<WeakReference<Object>, UpdateCallbacks> mChangeCallbacks = new ArrayMap<>();
+
+ /** @hide */
+ public interface UpdateCallbacks {
+
+ /**
+ * Invoked when a {@link ResourcesLoader} has a {@link ResourcesProvider} added, removed,
+ * or reordered.
+ *
+ * @param loader the loader that was updated
+ */
+ void onLoaderUpdated(@NonNull ResourcesLoader loader);
+ }
+
+ /**
+ * Retrieves the list of providers loaded into this instance. Providers are listed in increasing
+ * precedence order. A provider will override the values of providers listed before itself.
+ */
+ @NonNull
+ public List<ResourcesProvider> getProviders() {
+ synchronized (mLock) {
+ return mProviders == null ? Collections.emptyList() : Arrays.asList(mProviders);
+ }
+ }
+
+ /**
+ * Appends a provider to the end of the provider list. If the provider is already present in the
+ * loader list, the list will not be modified.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * provider changes.
+ *
+ * @param resourcesProvider the provider to add
+ */
+ public void addProvider(@NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mLock) {
+ mProviders = ArrayUtils.appendElement(ResourcesProvider.class, mProviders,
+ resourcesProvider);
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Removes a provider from the provider list. If the provider is not present in the provider
+ * list, the list will not be modified.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * provider changes.
+ *
+ * @param resourcesProvider the provider to remove
+ */
+ public void removeProvider(@NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mLock) {
+ mProviders = ArrayUtils.removeElement(ResourcesProvider.class, mProviders,
+ resourcesProvider);
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Sets the list of providers.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * provider changes.
+ *
+ * @param resourcesProviders the new providers
+ */
+ public void setProviders(@NonNull List<ResourcesProvider> resourcesProviders) {
+ synchronized (mLock) {
+ mProviders = resourcesProviders.toArray(new ResourcesProvider[0]);
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Removes all {@link ResourcesProvider ResourcesProvider(s)}.
+ *
+ * <p>This should only be called from the UI thread to avoid lock contention when propagating
+ * provider changes.
+ */
+ public void clearProviders() {
+ synchronized (mLock) {
+ mProviders = null;
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Retrieves the list of {@link ApkAssets} used by the providers.
+ *
+ * @hide
+ */
+ @NonNull
+ public List<ApkAssets> getApkAssets() {
+ synchronized (mLock) {
+ if (mApkAssets == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(mApkAssets);
+ }
+ }
+
+ /**
+ * Registers a callback to be invoked when {@link ResourcesProvider ResourcesProvider(s)}
+ * change.
+ * @param instance the instance tied to the callback
+ * @param callbacks the callback to invoke
+ *
+ * @hide
+ */
+ public void registerOnProvidersChangedCallback(@NonNull Object instance,
+ @NonNull UpdateCallbacks callbacks) {
+ synchronized (mLock) {
+ mChangeCallbacks.put(new WeakReference<>(instance), callbacks);
+ }
+ }
+
+ /**
+ * Removes a previously registered callback.
+ * @param instance the instance tied to the callback
+ *
+ * @hide
+ */
+ public void unregisterOnProvidersChangedCallback(@NonNull Object instance) {
+ synchronized (mLock) {
+ for (int i = 0, n = mChangeCallbacks.size(); i < n; i++) {
+ final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
+ if (instance == key.get()) {
+ mChangeCallbacks.removeAt(i);
+ return;
+ }
+ }
+ }
+ }
+
+ /** Returns whether the arrays contain the same provider instances in the same order. */
+ private static boolean arrayEquals(ResourcesProvider[] a1, ResourcesProvider[] a2) {
+ if (a1 == a2) {
+ return true;
+ }
+
+ if (a1 == null || a2 == null) {
+ return false;
+ }
+
+ if (a1.length != a2.length) {
+ return false;
+ }
+
+ // Check that the arrays contain the exact same instances in the same order. Providers do
+ // not have any form of equivalence checking of whether the contents of two providers have
+ // equivalent apk assets.
+ for (int i = 0, n = a1.length; i < n; i++) {
+ if (a1[i] != a2[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Invokes registered callbacks when the list of {@link ResourcesProvider} instances this loader
+ * uses changes.
+ */
+ private void notifyProvidersChangedLocked() {
+ final ArraySet<UpdateCallbacks> uniqueCallbacks = new ArraySet<>();
+ if (arrayEquals(mPreviousProviders, mProviders)) {
+ return;
+ }
+
+ if (mProviders == null || mProviders.length == 0) {
+ mApkAssets = null;
+ } else {
+ mApkAssets = new ApkAssets[mProviders.length];
+ for (int i = 0, n = mProviders.length; i < n; i++) {
+ mProviders[i].incrementRefCount();
+ mApkAssets[i] = mProviders[i].getApkAssets();
+ }
+ }
+
+ // Decrement the ref count after incrementing the new provider ref count so providers
+ // present before and after this method do not drop to zero references.
+ if (mPreviousProviders != null) {
+ for (ResourcesProvider provider : mPreviousProviders) {
+ provider.decrementRefCount();
+ }
+ }
+
+ mPreviousProviders = mProviders;
+
+ for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) {
+ final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
+ if (key.get() == null) {
+ mChangeCallbacks.removeAt(i);
+ } else {
+ uniqueCallbacks.add(mChangeCallbacks.valueAt(i));
+ }
+ }
+
+ for (int i = 0, n = uniqueCallbacks.size(); i < n; i++) {
+ uniqueCallbacks.valueAt(i).onLoaderUpdated(this);
+ }
+ }
+}
diff --git a/android/content/res/loader/ResourcesProvider.java b/android/content/res/loader/ResourcesProvider.java
new file mode 100644
index 0000000..463dcac
--- /dev/null
+++ b/android/content/res/loader/ResourcesProvider.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 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.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.ApkAssets;
+import android.content.res.AssetFileDescriptor;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
+ * (eg. {@code resources.arsc}) for use with {@link ResourcesLoader ResourcesLoader(s)}.
+ */
+public class ResourcesProvider implements AutoCloseable, Closeable {
+ private static final String TAG = "ResourcesProvider";
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mOpen = true;
+
+ @GuardedBy("mLock")
+ private int mOpenCount = 0;
+
+ @GuardedBy("mLock")
+ private final ApkAssets mApkAssets;
+
+ /**
+ * Creates an empty ResourcesProvider with no resource data. This is useful for loading
+ * file-based assets not associated with resource identifiers.
+ *
+ * @param assetsProvider the assets provider that implements the loading of file-based resources
+ */
+ @NonNull
+ public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
+ return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
+ }
+
+ /**
+ * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
+ *
+ * <p>The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * @param fileDescriptor the file descriptor of the APK to load
+ *
+ * @see ParcelFileDescriptor#open(File, int)
+ * @see android.system.Os#memfd_create(String, int)
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return loadFromApk(fileDescriptor, null /* assetsProvider */);
+ }
+
+ /**
+ * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
+ *
+ * <p>The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * <p>The assets provider can override the loading of files within the APK and can provide
+ * entirely new files that do not exist in the APK.
+ *
+ * @param fileDescriptor the file descriptor of the APK to load
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ *
+ * @see ParcelFileDescriptor#open(File, int)
+ * @see android.system.Os#memfd_create(String, int)
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
+ @Nullable AssetsProvider assetsProvider)
+ throws IOException {
+ return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
+ fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
+ }
+
+ /**
+ * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
+ *
+ * <p>The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * <p>The assets provider can override the loading of files within the APK and can provide
+ * entirely new files that do not exist in the APK.
+ *
+ * @param fileDescriptor the file descriptor of the APK to load
+ * @param offset The location within the file that the apk starts. This must be 0 if length is
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
+ * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
+ * if it extends to the end of the file.
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ *
+ * @see ParcelFileDescriptor#open(File, int)
+ * @see android.system.Os#memfd_create(String, int)
+ * @hide
+ */
+ @VisibleForTesting
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
+ long offset, long length, @Nullable AssetsProvider assetsProvider)
+ throws IOException {
+ return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
+ fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
+ }
+
+ /**
+ * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
+ *
+ * <p>The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * <p>The resources table format is not an archive format and therefore cannot asset files
+ * within itself. The assets provider can instead provide files that are potentially referenced
+ * by path in the resources table.
+ *
+ * @param fileDescriptor the file descriptor of the resources table to load
+ * @param assetsProvider the assets provider that implements the loading of file-based resources
+ *
+ * @see ParcelFileDescriptor#open(File, int)
+ * @see android.system.Os#memfd_create(String, int)
+ */
+ @NonNull
+ public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
+ @Nullable AssetsProvider assetsProvider)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
+ fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
+ }
+
+ /**
+ * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
+ *
+ * The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * <p>The resources table format is not an archive format and therefore cannot asset files
+ * within itself. The assets provider can instead provide files that are potentially referenced
+ * by path in the resources table.
+ *
+ * @param fileDescriptor the file descriptor of the resources table to load
+ * @param offset The location within the file that the table starts. This must be 0 if length is
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
+ * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
+ * if it extends to the end of the file.
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ *
+ * @see ParcelFileDescriptor#open(File, int)
+ * @see android.system.Os#memfd_create(String, int)
+ * @hide
+ */
+ @VisibleForTesting
+ @NonNull
+ public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
+ long offset, long length, @Nullable AssetsProvider assetsProvider)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
+ fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
+ }
+
+ /**
+ * Read from a split installed alongside the application, which may not have been
+ * loaded initially because the application requested isolated split loading.
+ *
+ * @param context a context of the package that contains the split
+ * @param splitName the name of the split to load
+ */
+ @NonNull
+ public static ResourcesProvider loadFromSplit(@NonNull Context context,
+ @NonNull String splitName) throws IOException {
+ ApplicationInfo appInfo = context.getApplicationInfo();
+ int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
+ if (splitIndex < 0) {
+ throw new IllegalArgumentException("Split " + splitName + " not found");
+ }
+
+ String splitPath = appInfo.getSplitCodePaths()[splitIndex];
+ return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER,
+ null /* assetsProvider */));
+ }
+
+ /**
+ * Creates a ResourcesProvider from a directory path.
+ *
+ * File-based resources will be resolved within the directory as if the directory is an APK.
+ *
+ * @param path the path of the directory to treat as an APK
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ */
+ @NonNull
+ public static ResourcesProvider loadFromDirectory(@NonNull String path,
+ @Nullable AssetsProvider assetsProvider) throws IOException {
+ return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
+ }
+
+
+ private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+ this.mApkAssets = apkAssets;
+ }
+
+ /** @hide */
+ @NonNull
+ public ApkAssets getApkAssets() {
+ return mApkAssets;
+ }
+
+ final void incrementRefCount() {
+ synchronized (mLock) {
+ if (!mOpen) {
+ throw new IllegalStateException("Operation failed: resources provider is closed");
+ }
+ mOpenCount++;
+ }
+ }
+
+ final void decrementRefCount() {
+ synchronized (mLock) {
+ mOpenCount--;
+ }
+ }
+
+ /**
+ * Frees internal data structures. Closed providers can no longer be added to
+ * {@link ResourcesLoader ResourcesLoader(s)}.
+ *
+ * @throws IllegalStateException if provider is currently used by a ResourcesLoader
+ */
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (!mOpen) {
+ return;
+ }
+
+ if (mOpenCount != 0) {
+ throw new IllegalStateException("Failed to close provider used by " + mOpenCount
+ + " ResourcesLoader instances");
+ }
+ mOpen = false;
+ }
+
+ try {
+ mApkAssets.close();
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ synchronized (mLock) {
+ if (mOpenCount != 0) {
+ Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: "
+ + mOpenCount);
+ }
+ }
+ }
+}
diff --git a/android/content/rollback/PackageRollbackInfo.java b/android/content/rollback/PackageRollbackInfo.java
new file mode 100644
index 0000000..0140280
--- /dev/null
+++ b/android/content/rollback/PackageRollbackInfo.java
@@ -0,0 +1,236 @@
+/*
+ * 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.rollback;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information about a rollback available for a particular package.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PackageRollbackInfo implements Parcelable {
+
+ private final VersionedPackage mVersionRolledBackFrom;
+ private final VersionedPackage mVersionRolledBackTo;
+
+ /**
+ * Encapsulates information required to restore a snapshot of an app's userdata.
+ *
+ * @hide
+ */
+ public static class RestoreInfo {
+ public final int userId;
+ public final int appId;
+ public final String seInfo;
+
+ public RestoreInfo(int userId, int appId, String seInfo) {
+ this.userId = userId;
+ this.appId = appId;
+ this.seInfo = seInfo;
+ }
+ }
+
+ /*
+ * The list of users for which we need to backup userdata for this package. Backups of
+ * credential encrypted data are listed as pending if the user hasn't unlocked their device
+ * with credentials yet.
+ */
+ // NOTE: Not a part of the Parcelable representation of this object.
+ private final List<Integer> mPendingBackups;
+
+ /**
+ * The list of users for which we need to restore userdata for this package. This field is
+ * non-null only after a rollback for this package has been committed.
+ */
+ // NOTE: Not a part of the Parcelable representation of this object.
+ private final ArrayList<RestoreInfo> mPendingRestores;
+
+ /**
+ * Whether this instance represents the PackageRollbackInfo for an APEX module.
+ */
+ private final boolean mIsApex;
+
+ /**
+ * Whether this instance represents the PackageRollbackInfo for an APK in APEX.
+ */
+ private final boolean mIsApkInApex;
+
+ /*
+ * The list of users for which snapshots have been saved.
+ */
+ // NOTE: Not a part of the Parcelable representation of this object.
+ private final List<Integer> mSnapshottedUsers;
+
+ /**
+ * The userdata policy to execute when a rollback for this package is committed.
+ */
+ private final int mRollbackDataPolicy;
+
+ /**
+ * Returns the name of the package to roll back from.
+ */
+ @NonNull
+ public String getPackageName() {
+ return mVersionRolledBackFrom.getPackageName();
+ }
+
+ /**
+ * Returns the version of the package rolled back from.
+ */
+ @NonNull
+ public VersionedPackage getVersionRolledBackFrom() {
+ return mVersionRolledBackFrom;
+ }
+
+ /**
+ * Returns the version of the package rolled back to.
+ */
+ @NonNull
+ public VersionedPackage getVersionRolledBackTo() {
+ return mVersionRolledBackTo;
+ }
+
+ /** @hide */
+ public void addPendingBackup(int userId) {
+ mPendingBackups.add(userId);
+ }
+
+ /** @hide */
+ public List<Integer> getPendingBackups() {
+ return mPendingBackups;
+ }
+
+ /** @hide */
+ public ArrayList<RestoreInfo> getPendingRestores() {
+ return mPendingRestores;
+ }
+
+ /** @hide */
+ public RestoreInfo getRestoreInfo(int userId) {
+ for (RestoreInfo ri : mPendingRestores) {
+ if (ri.userId == userId) {
+ return ri;
+ }
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ public void removeRestoreInfo(RestoreInfo ri) {
+ mPendingRestores.remove(ri);
+ }
+
+ /** @hide */
+ public boolean isApex() {
+ return mIsApex;
+ }
+
+ /** @hide */
+ public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
+ return mRollbackDataPolicy;
+ }
+ /** @hide */
+ public boolean isApkInApex() {
+ return mIsApkInApex;
+ }
+
+ /** @hide */
+ public List<Integer> getSnapshottedUsers() {
+ return mSnapshottedUsers;
+ }
+
+ /** @hide */
+ public void removePendingBackup(int userId) {
+ mPendingBackups.remove((Integer) userId);
+ }
+
+ /** @hide */
+ public void removePendingRestoreInfo(int userId) {
+ removeRestoreInfo(getRestoreInfo(userId));
+ }
+
+ /** @hide */
+ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+ VersionedPackage packageRolledBackTo,
+ @NonNull List<Integer> pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
+ boolean isApex, boolean isApkInApex, @NonNull List<Integer> snapshottedUsers) {
+ this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
+ isApkInApex, snapshottedUsers, PackageManager.RollbackDataPolicy.RESTORE);
+ }
+
+ /** @hide */
+ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+ VersionedPackage packageRolledBackTo,
+ @NonNull List<Integer> pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
+ boolean isApex, boolean isApkInApex, @NonNull List<Integer> snapshottedUsers,
+ @PackageManager.RollbackDataPolicy int rollbackDataPolicy) {
+ this.mVersionRolledBackFrom = packageRolledBackFrom;
+ this.mVersionRolledBackTo = packageRolledBackTo;
+ this.mPendingBackups = pendingBackups;
+ this.mPendingRestores = pendingRestores;
+ this.mIsApex = isApex;
+ this.mRollbackDataPolicy = rollbackDataPolicy;
+ this.mIsApkInApex = isApkInApex;
+ this.mSnapshottedUsers = snapshottedUsers;
+ }
+
+ private PackageRollbackInfo(Parcel in) {
+ this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
+ this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
+ this.mIsApex = in.readBoolean();
+ this.mIsApkInApex = in.readBoolean();
+ this.mPendingRestores = null;
+ this.mPendingBackups = null;
+ this.mSnapshottedUsers = null;
+ this.mRollbackDataPolicy = PackageManager.RollbackDataPolicy.RESTORE;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ mVersionRolledBackFrom.writeToParcel(out, flags);
+ mVersionRolledBackTo.writeToParcel(out, flags);
+ out.writeBoolean(mIsApex);
+ out.writeBoolean(mIsApkInApex);
+ }
+
+ public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR =
+ new Parcelable.Creator<PackageRollbackInfo>() {
+ public PackageRollbackInfo createFromParcel(Parcel in) {
+ return new PackageRollbackInfo(in);
+ }
+
+ public PackageRollbackInfo[] newArray(int size) {
+ return new PackageRollbackInfo[size];
+ }
+ };
+}
diff --git a/android/content/rollback/RollbackInfo.java b/android/content/rollback/RollbackInfo.java
new file mode 100644
index 0000000..a363718
--- /dev/null
+++ b/android/content/rollback/RollbackInfo.java
@@ -0,0 +1,139 @@
+/*
+ * 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.rollback;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.VersionedPackage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Information about a set of packages that can be, or already have been
+ * rolled back together.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RollbackInfo implements Parcelable {
+
+ /**
+ * A unique identifier for the rollback.
+ */
+ private final int mRollbackId;
+
+ private final List<PackageRollbackInfo> mPackages;
+
+ private final List<VersionedPackage> mCausePackages;
+
+ private final boolean mIsStaged;
+ private int mCommittedSessionId;
+
+ /** @hide */
+ public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged,
+ List<VersionedPackage> causePackages, int committedSessionId) {
+ this.mRollbackId = rollbackId;
+ this.mPackages = packages;
+ this.mIsStaged = isStaged;
+ this.mCausePackages = causePackages;
+ this.mCommittedSessionId = committedSessionId;
+ }
+
+ private RollbackInfo(Parcel in) {
+ mRollbackId = in.readInt();
+ mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
+ mIsStaged = in.readBoolean();
+ mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR);
+ mCommittedSessionId = in.readInt();
+ }
+
+ /**
+ * Returns a unique identifier for this rollback.
+ */
+ public int getRollbackId() {
+ return mRollbackId;
+ }
+
+ /**
+ * Returns the list of package that are rolled back.
+ */
+ @NonNull
+ public List<PackageRollbackInfo> getPackages() {
+ return mPackages;
+ }
+
+ /**
+ * Returns true if this rollback requires reboot to take effect after
+ * being committed.
+ */
+ public boolean isStaged() {
+ return mIsStaged;
+ }
+
+ /**
+ * Returns the session ID for the committed rollback for staged rollbacks.
+ * Only applicable for rollbacks that have been committed.
+ */
+ public int getCommittedSessionId() {
+ return mCommittedSessionId;
+ }
+
+ /**
+ * Sets the session ID for the committed rollback for staged rollbacks.
+ * @hide
+ */
+ public void setCommittedSessionId(int sessionId) {
+ mCommittedSessionId = sessionId;
+ }
+
+ /**
+ * Gets the list of package versions that motivated this rollback.
+ * As provided to {@link #commitRollback} when the rollback was committed.
+ * This is only applicable for rollbacks that have been committed.
+ */
+ @NonNull
+ public List<VersionedPackage> getCausePackages() {
+ return mCausePackages;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mRollbackId);
+ out.writeTypedList(mPackages);
+ out.writeBoolean(mIsStaged);
+ out.writeTypedList(mCausePackages);
+ out.writeInt(mCommittedSessionId);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<RollbackInfo> CREATOR =
+ new Parcelable.Creator<RollbackInfo>() {
+ public RollbackInfo createFromParcel(Parcel in) {
+ return new RollbackInfo(in);
+ }
+
+ public RollbackInfo[] newArray(int size) {
+ return new RollbackInfo[size];
+ }
+ };
+}
diff --git a/android/content/rollback/RollbackManager.java b/android/content/rollback/RollbackManager.java
new file mode 100644
index 0000000..3636222
--- /dev/null
+++ b/android/content/rollback/RollbackManager.java
@@ -0,0 +1,277 @@
+/*
+ * 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.rollback;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.VersionedPackage;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Offers the ability to rollback packages after upgrade.
+ * <p>
+ * For packages installed with rollbacks enabled, the RollbackManager can be
+ * used to initiate rollback of those packages for a limited time period after
+ * upgrade.
+ *
+ * @see PackageInstaller.SessionParams#setEnableRollback(boolean)
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.ROLLBACK_SERVICE)
+public final class RollbackManager {
+ private final String mCallerPackageName;
+ private final IRollbackManager mBinder;
+
+ /**
+ * Lifetime duration of rollback packages in millis. A rollback will be available for
+ * at most that duration of time after a package is installed with
+ * {@link PackageInstaller.SessionParams#setEnableRollback(boolean)}.
+ *
+ * <p>If flag value is negative, the default value will be assigned.
+ *
+ * @see RollbackManager
+ *
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_ROLLBACK_BOOT
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String PROPERTY_ROLLBACK_LIFETIME_MILLIS =
+ "rollback_lifetime_in_millis";
+
+ /** {@hide} */
+ public RollbackManager(Context context, IRollbackManager binder) {
+ mCallerPackageName = context.getPackageName();
+ mBinder = binder;
+ }
+
+ /**
+ * Returns a list of all currently available rollbacks.
+ *
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_ROLLBACKS,
+ android.Manifest.permission.TEST_MANAGE_ROLLBACKS
+ })
+ @NonNull
+ public List<RollbackInfo> getAvailableRollbacks() {
+ try {
+ return mBinder.getAvailableRollbacks().getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the list of all recently committed rollbacks.
+ * This is for the purposes of preventing re-install of a bad version of a
+ * package and monitoring the status of a staged rollback.
+ * <p>
+ * Returns an empty list if there are no recently committed rollbacks.
+ * <p>
+ * To avoid having to keep around complete rollback history forever on a
+ * device, the returned list of rollbacks is only guaranteed to include
+ * rollbacks that are still relevant. A rollback is no longer considered
+ * relevant if the package is subsequently uninstalled or upgraded
+ * (without the possibility of rollback) to a higher version code than was
+ * rolled back from.
+ *
+ * @return the recently committed rollbacks
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_ROLLBACKS,
+ android.Manifest.permission.TEST_MANAGE_ROLLBACKS
+ })
+ public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
+ try {
+ return mBinder.getRecentlyCommittedRollbacks().getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Status of a rollback commit. Will be one of
+ * {@link #STATUS_SUCCESS}, {@link #STATUS_FAILURE},
+ * {@link #STATUS_FAILURE_ROLLBACK_UNAVAILABLE}, {@link #STATUS_FAILURE_INSTALL}
+ *
+ * @see Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_STATUS = "android.content.rollback.extra.STATUS";
+
+ /**
+ * Detailed string representation of the status, including raw details that
+ * are useful for debugging.
+ *
+ * @see Intent#getStringExtra(String)
+ */
+ public static final String EXTRA_STATUS_MESSAGE =
+ "android.content.rollback.extra.STATUS_MESSAGE";
+
+ /**
+ * Status result of committing a rollback.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESS,
+ STATUS_FAILURE,
+ STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+ STATUS_FAILURE_INSTALL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {};
+
+ /**
+ * The rollback was successfully committed.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The rollback could not be committed due to some generic failure.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE = 1;
+
+ /**
+ * The rollback could not be committed because it was no longer available.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_ROLLBACK_UNAVAILABLE = 2;
+
+ /**
+ * The rollback failed to install successfully.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_INSTALL = 3;
+
+ /**
+ * Commit the rollback with given id, rolling back all versions of the
+ * packages to the last good versions previously installed on the device
+ * as specified in the corresponding RollbackInfo object. The
+ * rollback will fail if any of the installed packages or available
+ * rollbacks are inconsistent with the versions specified in the given
+ * rollback object, which can happen if a package has been updated or a
+ * rollback expired since the rollback object was retrieved from
+ * {@link #getAvailableRollbacks()}.
+ *
+ * @param rollbackId ID of the rollback to commit
+ * @param causePackages package versions to record as the motivation for this
+ * rollback.
+ * @param statusReceiver where to deliver the results. Intents sent to
+ * this receiver contain {@link #EXTRA_STATUS}
+ * and {@link #EXTRA_STATUS_MESSAGE}.
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_ROLLBACKS,
+ android.Manifest.permission.TEST_MANAGE_ROLLBACKS
+ })
+ public void commitRollback(int rollbackId, @NonNull List<VersionedPackage> causePackages,
+ @NonNull IntentSender statusReceiver) {
+ try {
+ mBinder.commitRollback(rollbackId, new ParceledListSlice(causePackages),
+ mCallerPackageName, statusReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reload all persisted rollback data from device storage.
+ * This API is meant to test that rollback state is properly preserved
+ * across device reboot, by simulating what happens on reboot without
+ * actually rebooting the device.
+ *
+ * Note rollbacks in the process of enabling will be lost after calling
+ * this method since they are not persisted yet. Don't call this method
+ * in the middle of the install process.
+ *
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS)
+ @TestApi
+ public void reloadPersistedData() {
+ try {
+ mBinder.reloadPersistedData();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Expire the rollback data for a given package.
+ * This API is meant to facilitate testing of rollback logic for
+ * expiring rollback data. Removes rollback data for available and
+ * recently committed rollbacks that contain the given package.
+ *
+ * @param packageName the name of the package to expire data for.
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS)
+ @TestApi
+ public void expireRollbackForPackage(@NonNull String packageName) {
+ try {
+ mBinder.expireRollbackForPackage(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Block the RollbackManager for a specified amount of time.
+ * This API is meant to facilitate testing of race conditions in
+ * RollbackManager. Blocks RollbackManager from processing anything for
+ * the given number of milliseconds.
+ *
+ * @param millis number of milliseconds to block the RollbackManager for
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS)
+ @TestApi
+ public void blockRollbackManager(long millis) {
+ try {
+ mBinder.blockRollbackManager(millis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/content/rollback/RollbackManagerFrameworkInitializer.java b/android/content/rollback/RollbackManagerFrameworkInitializer.java
new file mode 100644
index 0000000..24617a0
--- /dev/null
+++ b/android/content/rollback/RollbackManagerFrameworkInitializer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.rollback;
+
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class holding initialization code for the RollbackManager module.
+ *
+ * @hide
+ */
+public class RollbackManagerFrameworkInitializer {
+ private RollbackManagerFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers RollbackManager
+ * to {@link Context}, so that {@link Context#getSystemService} can return it.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides
+ * {@link SystemServiceRegistry}
+ */
+ public static void initialize() {
+ SystemServiceRegistry.registerContextAwareService(
+ Context.ROLLBACK_SERVICE, RollbackManager.class,
+ (context, b) -> new RollbackManager(context, IRollbackManager.Stub.asInterface(b)));
+ }
+}
diff --git a/android/content/type/DefaultMimeMapFactory.java b/android/content/type/DefaultMimeMapFactory.java
new file mode 100644
index 0000000..bcd0eb0
--- /dev/null
+++ b/android/content/type/DefaultMimeMapFactory.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 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.type;
+
+import libcore.content.type.MimeMap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Creates the framework default {@link MimeMap}, a bidirectional mapping
+ * between MIME types and file extensions.
+ *
+ * This default mapping is loaded from data files that start with some mappings
+ * recognized by IANA plus some custom extensions and overrides.
+ *
+ * @hide
+ */
+public class DefaultMimeMapFactory {
+
+ private DefaultMimeMapFactory() {
+ }
+
+ /**
+ * Creates and returns a new {@link MimeMap} instance that implements.
+ * Android's default mapping between MIME types and extensions.
+ */
+ public static MimeMap create() {
+ Class c = DefaultMimeMapFactory.class;
+ // The resources are placed into the res/ path by the "mimemap-res.jar" genrule.
+ return create(resourceName -> c.getResourceAsStream("/res/" + resourceName));
+ }
+
+ /**
+ * Creates a {@link MimeMap} instance whose resources are loaded from the
+ * InputStreams looked up in {@code resourceSupplier}.
+ *
+ * @hide
+ */
+ public static MimeMap create(Function<String, InputStream> resourceSupplier) {
+ MimeMap.Builder builder = MimeMap.builder();
+ // The files loaded here must be in minimized format with lines of the
+ // form "mime/type ext1 ext2 ext3", i.e. no comments, no empty lines, no
+ // leading/trailing whitespace and with a single space between entries on
+ // each line. See http://b/142267887
+ //
+ // Note: the order here matters - later entries can overwrite earlier ones
+ // (except that vendor.mime.types entries are prefixed with '?' which makes
+ // them never overwrite).
+ parseTypes(builder, resourceSupplier, "debian.mime.types");
+ parseTypes(builder, resourceSupplier, "android.mime.types");
+ parseTypes(builder, resourceSupplier, "vendor.mime.types");
+ return builder.build();
+ }
+
+ private static void parseTypes(MimeMap.Builder builder,
+ Function<String, InputStream> resourceSupplier, String resourceName) {
+ try (InputStream inputStream = Objects.requireNonNull(resourceSupplier.apply(resourceName));
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ List<String> specs = new ArrayList<>(10); // re-use for each line
+ while ((line = reader.readLine()) != null) {
+ specs.clear();
+ // Lines are of the form "mimeSpec extSpec extSpec[...]" with a single space
+ // separating them and no leading/trailing spaces and no empty lines.
+ int startIdx = 0;
+ do {
+ int endIdx = line.indexOf(' ', startIdx);
+ if (endIdx < 0) {
+ endIdx = line.length();
+ }
+ String spec = line.substring(startIdx, endIdx);
+ if (spec.isEmpty()) {
+ throw new IllegalArgumentException("Malformed line: " + line);
+ }
+ specs.add(spec);
+ startIdx = endIdx + 1; // skip over the space
+ } while (startIdx < line.length());
+ builder.addMimeMapping(specs.get(0), specs.subList(1, specs.size()));
+ }
+ } catch (IOException | RuntimeException e) {
+ throw new RuntimeException("Failed to parse " + resourceName, e);
+ }
+ }
+
+}