Automatic sources dropoff on 2020-06-10 18:32:38.095721
The change is generated with prebuilt drop tool.
Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
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/AutofillOptions.java b/android/content/AutofillOptions.java
new file mode 100644
index 0000000..97b33b7
--- /dev/null
+++ b/android/content/AutofillOptions.java
@@ -0,0 +1,220 @@
+/*
+ * 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.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 whitelisted for augmented autofill.
+ */
+ public boolean augmentedAutofillEnabled;
+
+ /**
+ * List of whitelisted activities.
+ */
+ @Nullable
+ 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.
+ */
+ @Nullable
+ public ArrayMap<String, Long> disabledActivities;
+
+ public AutofillOptions(int loggingLevel, boolean compatModeEnabled) {
+ this.loggingLevel = loggingLevel;
+ this.compatModeEnabled = compatModeEnabled;
+ }
+
+ /**
+ * Returns whether activity is whitelisted 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..2342165
--- /dev/null
+++ b/android/content/ClipData.java
@@ -0,0 +1,1178 @@
+/**
+ * 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.compat.annotation.UnsupportedAppUsage;
+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 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;
+
+ /**
+ * 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;
+
+ /** @hide */
+ public Item(Item other) {
+ mText = other.mText;
+ mHtmlText = other.mHtmlText;
+ mIntent = other.mIntent;
+ mUri = other.mUri;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /** @hide */
+ public void toShortString(StringBuilder b) {
+ if (mHtmlText != null) {
+ b.append("H:");
+ b.append(mHtmlText);
+ } else if (mText != null) {
+ b.append("T:");
+ b.append(mText);
+ } else if (mUri != null) {
+ b.append("U:");
+ b.append(mUri);
+ } else if (mIntent != null) {
+ b.append("I:");
+ mIntent.toShortString(b, true, true, true, true);
+ } else {
+ b.append("NULL");
+ }
+ }
+
+ /** @hide */
+ public void toShortSummaryString(StringBuilder b) {
+ if (mHtmlText != null) {
+ b.append("HTML");
+ } else if (mText != null) {
+ b.append("TEXT");
+ } else if (mUri != null) {
+ b.append("U:");
+ b.append(mUri);
+ } else if (mIntent != null) {
+ b.append("I:");
+ mIntent.toShortString(b, true, true, true, true);
+ } else {
+ b.append("NULL");
+ }
+ }
+
+ /** @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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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() {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ item.mIntent.prepareToEnterProcess();
+ }
+ }
+ }
+
+ /** @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);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("ClipData { ");
+ toShortString(b);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /** @hide */
+ public void toShortString(StringBuilder b) {
+ boolean first;
+ if (mClipDescription != null) {
+ first = !mClipDescription.toShortString(b);
+ } 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());
+ }
+ for (int i=0; i<mItems.size(); i++) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append('{');
+ mItems.get(i).toShortString(b);
+ b.append('}');
+ }
+ }
+
+ /** @hide */
+ public void toShortStringShortItems(StringBuilder b, boolean first) {
+ if (mItems.size() > 0) {
+ if (!first) {
+ b.append(' ');
+ }
+ for (int i=0; i<mItems.size(); i++) {
+ 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);
+ if (item.mIntent != null) {
+ dest.writeInt(1);
+ item.mIntent.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (item.mUri != null) {
+ dest.writeInt(1);
+ item.mUri.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+ }
+
+ ClipData(Parcel in) {
+ mClipDescription = new ClipDescription(in);
+ if (in.readInt() != 0) {
+ mIcon = Bitmap.CREATOR.createFromParcel(in);
+ } else {
+ mIcon = null;
+ }
+ mItems = new ArrayList<Item>();
+ 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.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
+ Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
+ mItems.add(new Item(text, htmlText, intent, uri));
+ }
+ }
+
+ 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..6739138
--- /dev/null
+++ b/android/content/ClipDescription.java
@@ -0,0 +1,404 @@
+/**
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * 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 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 name of the extra used to define a component name when copying/dragging
+ * an app icon from Launcher.
+ * <p>
+ * Type: String
+ * </p>
+ * <p>
+ * Use {@link ComponentName#unflattenFromString(String)}
+ * and {@link ComponentName#flattenToString()} to convert the extra value
+ * to/from {@link ComponentName}.
+ * </p>
+ * @hide
+ */
+ public static final String EXTRA_TARGET_COMPONENT_NAME =
+ "android.content.extra.TARGET_COMPONENT_NAME";
+
+ /**
+ * The name of the extra used to define a user serial number when copying/dragging
+ * an app icon from Launcher.
+ * <p>
+ * Type: long
+ * </p>
+ * @hide
+ */
+ public static final String EXTRA_USER_SERIAL_NUMBER =
+ "android.content.extra.USER_SERIAL_NUMBER";
+
+
+ final CharSequence mLabel;
+ private final ArrayList<String> mMimeTypes;
+ private PersistableBundle mExtras;
+ private long mTimeStamp;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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");
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("ClipDescription { ");
+ toShortString(b);
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ /** @hide */
+ public boolean toShortString(StringBuilder b) {
+ boolean first = !toShortStringTypesOnly(b);
+ if (mLabel != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append('"');
+ b.append(mLabel);
+ b.append('"');
+ }
+ if (mExtras != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ 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);
+ }
+
+ ClipDescription(Parcel in) {
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mMimeTypes = in.createStringArrayList();
+ mExtras = in.readPersistableBundle();
+ mTimeStamp = in.readLong();
+ }
+
+ 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/ComponentName.java b/android/content/ComponentName.java
new file mode 100644
index 0000000..1967a01
--- /dev/null
+++ b/android/content/ComponentName.java
@@ -0,0 +1,431 @@
+/*
+ * 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.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
+ public static void appendShortString(StringBuilder sb, String packageName, String className) {
+ sb.append(packageName).append('/');
+ appendShortClassName(sb, packageName, className);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ 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(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..cb2142c
--- /dev/null
+++ b/android/content/ContentCaptureOptions.java
@@ -0,0 +1,238 @@
+/*
+ * 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.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 whitelisted for content capture (or {@code null} if whitelisted
+ * for all acitivites in the package).
+ */
+ @Nullable
+ 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,
+ @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)) {
+ 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 whitelisted
+ 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..60ecf64
--- /dev/null
+++ b/android/content/ContentProvider.java
@@ -0,0 +1,2625 @@
+/*
+ * 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.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+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.IBinder;
+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.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+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
+ 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<Pair<String, String>> mCallingPackage;
+
+ 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(String callingPkg, @Nullable String attributionTag, Uri uri,
+ @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable ICancellationSignal cancellationSignal) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ // 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 Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ cursor = mInterface.query(
+ uri, projection, queryArgs,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ }
+ if (cursor == null) {
+ return null;
+ }
+
+ // Return an empty cursor for all columns.
+ return new MatrixCursor(cursor.getColumnNames(), 0);
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "query");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.query(
+ uri, projection, queryArgs,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(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);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "getType");
+ 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(String callingPkg, @Nullable String attributionTag, Uri uri,
+ ContentValues initialValues, Bundle extras) {
+ uri = validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return rejectInsert(uri, initialValues);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "insert");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int bulkInsert(String callingPkg, @Nullable String attributionTag, Uri uri,
+ ContentValues[] initialValues) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "bulkInsert");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.bulkInsert(uri, initialValues);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ @Nullable String attributionTag, 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);
+ }
+ if (operation.isReadOperation()) {
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
+ }
+ if (operation.isWriteOperation()) {
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
+ }
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ 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 {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int delete(String callingPkg, @Nullable String attributionTag, Uri uri,
+ Bundle extras) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "delete");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.delete(uri, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int update(String callingPkg, @Nullable String attributionTag, Uri uri,
+ ContentValues values, Bundle extras) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "update");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.update(uri, values, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag,
+ Uri uri, String mode, ICancellationSignal cancellationSignal, IBinder callerToken)
+ throws FileNotFoundException {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(callingPkg, attributionTag, uri, mode, callerToken);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "openFile");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.openFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag,
+ Uri uri, String mode, ICancellationSignal cancellationSignal)
+ throws FileNotFoundException {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(callingPkg, attributionTag, uri, mode, null);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.openAssetFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public Bundle call(String callingPkg, @Nullable String attributionTag, String authority,
+ String method, @Nullable String arg, @Nullable Bundle extras) {
+ validateIncomingAuthority(authority);
+ Bundle.setDefusable(extras, true);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "call");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.call(authority, method, arg, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(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);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "getStreamTypes");
+ try {
+ return mInterface.getStreamTypes(uri, mimeTypeFilter);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg,
+ @Nullable String attributionTag, Uri uri, String mimeType, Bundle opts,
+ ICancellationSignal cancellationSignal) throws FileNotFoundException {
+ Bundle.setDefusable(opts, true);
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(callingPkg, attributionTag, uri, "r", null);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.openTypedAssetFile(
+ uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public ICancellationSignal createCancellationSignal() {
+ return CancellationSignal.createTransport();
+ }
+
+ @Override
+ public Uri canonicalize(String callingPkg, @Nullable String attributionTag, Uri uri) {
+ uri = validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "canonicalize");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return maybeAddUserId(mInterface.canonicalize(uri), userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public void canonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri,
+ RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
+ canonicalize(callingPkg, attributionTag, uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, String attributionTag, Uri uri) {
+ uri = validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "uncanonicalize");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return maybeAddUserId(mInterface.uncanonicalize(uri), userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public boolean refresh(String callingPkg, String attributionTag, Uri uri, Bundle extras,
+ ICancellationSignal cancellationSignal) throws RemoteException {
+ uri = validateIncomingUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, attributionTag, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ Trace.traceBegin(TRACE_TAG_DATABASE, "refresh");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.refresh(uri, extras,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ @Override
+ public int checkUriPermission(String callingPkg, @Nullable String attributionTag, Uri uri,
+ int uid, int modeFlags) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "checkUriPermission");
+ final Pair<String, String> original = setCallingPackage(
+ new Pair<>(callingPkg, attributionTag));
+ try {
+ return mInterface.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
+ private void enforceFilePermission(String callingPkg, @Nullable String attributionTag,
+ Uri uri, String mode, IBinder callerToken)
+ throws FileNotFoundException, SecurityException {
+ if (mode != null && mode.indexOf('w') != -1) {
+ if (enforceWritePermission(callingPkg, attributionTag, uri, callerToken)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ } else {
+ if (enforceReadPermission(callingPkg, attributionTag, uri, callerToken)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ }
+ }
+
+ private int enforceReadPermission(String callingPkg, @Nullable String attributionTag,
+ Uri uri, IBinder callerToken)
+ throws SecurityException {
+ final int mode = enforceReadPermissionInner(uri, callingPkg, attributionTag,
+ callerToken);
+ if (mode != MODE_ALLOWED) {
+ return mode;
+ }
+
+ return noteProxyOp(callingPkg, attributionTag, mReadOp);
+ }
+
+ private int enforceWritePermission(String callingPkg, String attributionTag, Uri uri,
+ IBinder callerToken)
+ throws SecurityException {
+ final int mode = enforceWritePermissionInner(uri, callingPkg, attributionTag,
+ callerToken);
+ if (mode != MODE_ALLOWED) {
+ return mode;
+ }
+
+ return noteProxyOp(callingPkg, attributionTag, mWriteOp);
+ }
+
+ private int noteProxyOp(String callingPkg, String attributionTag, int op) {
+ if (op != AppOpsManager.OP_NONE) {
+ int mode = mAppOpsManager.noteProxyOp(op, callingPkg, Binder.getCallingUid(),
+ attributionTag, null);
+ return mode == MODE_DEFAULT ? MODE_IGNORED : mode;
+ }
+
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ 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) == PERMISSION_GRANTED
+ || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid)
+ == PERMISSION_GRANTED;
+ }
+
+ /**
+ * Verify that calling app holds both the given permission and any app-op
+ * associated with that permission.
+ */
+ private int checkPermissionAndAppOp(String permission, String callingPkg,
+ @Nullable String attributionTag, IBinder callerToken) {
+ if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(),
+ callerToken) != PERMISSION_GRANTED) {
+ return MODE_ERRORED;
+ }
+
+ return mTransport.noteProxyOp(callingPkg, attributionTag,
+ AppOpsManager.permissionToOpCode(permission));
+ }
+
+ /** {@hide} */
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+ @Nullable String attributionTag, IBinder callerToken) throws SecurityException {
+ final Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ String missingPerm = null;
+ int strongestMode = MODE_ALLOWED;
+
+ if (UserHandle.isSameApp(uid, mMyUid)) {
+ return MODE_ALLOWED;
+ }
+
+ if (mExported && checkUser(pid, uid, context)) {
+ final String componentPerm = getReadPermission();
+ if (componentPerm != null) {
+ final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, attributionTag,
+ callerToken);
+ if (mode == MODE_ALLOWED) {
+ return MODE_ALLOWED;
+ } else {
+ missingPerm = componentPerm;
+ strongestMode = Math.max(strongestMode, mode);
+ }
+ }
+
+ // 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 mode = checkPermissionAndAppOp(pathPerm, callingPkg,
+ attributionTag, callerToken);
+ if (mode == MODE_ALLOWED) {
+ return MODE_ALLOWED;
+ } else {
+ // any denied <path-permission> means we lose
+ // default <provider> access.
+ allowDefaultRead = false;
+ missingPerm = pathPerm;
+ strongestMode = Math.max(strongestMode, mode);
+ }
+ }
+ }
+ }
+
+ // if we passed <path-permission> checks above, and no default
+ // <provider> permission, then allow access.
+ if (allowDefaultRead) return MODE_ALLOWED;
+ }
+
+ // 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,
+ callerToken) == PERMISSION_GRANTED) {
+ return MODE_ALLOWED;
+ }
+
+ // 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 (strongestMode == MODE_IGNORED) {
+ return MODE_IGNORED;
+ }
+
+ 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} */
+ protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+ @Nullable String attributionTag, IBinder callerToken) throws SecurityException {
+ final Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ String missingPerm = null;
+ int strongestMode = MODE_ALLOWED;
+
+ if (UserHandle.isSameApp(uid, mMyUid)) {
+ return MODE_ALLOWED;
+ }
+
+ if (mExported && checkUser(pid, uid, context)) {
+ final String componentPerm = getWritePermission();
+ if (componentPerm != null) {
+ final int mode = checkPermissionAndAppOp(componentPerm, callingPkg,
+ attributionTag, callerToken);
+ if (mode == MODE_ALLOWED) {
+ return MODE_ALLOWED;
+ } else {
+ missingPerm = componentPerm;
+ strongestMode = Math.max(strongestMode, 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 = checkPermissionAndAppOp(pathPerm, callingPkg,
+ attributionTag, callerToken);
+ if (mode == MODE_ALLOWED) {
+ return MODE_ALLOWED;
+ } else {
+ // any denied <path-permission> means we lose
+ // default <provider> access.
+ allowDefaultWrite = false;
+ missingPerm = pathPerm;
+ strongestMode = Math.max(strongestMode, mode);
+ }
+ }
+ }
+ }
+
+ // if we passed <path-permission> checks above, and no default
+ // <provider> permission, then allow access.
+ if (allowDefaultWrite) return MODE_ALLOWED;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ callerToken) == PERMISSION_GRANTED) {
+ return MODE_ALLOWED;
+ }
+
+ // 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 (strongestMode == MODE_IGNORED) {
+ return MODE_IGNORED;
+ }
+
+ 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 Pair<String, String> setCallingPackage(Pair<String, String> callingPackage) {
+ final Pair<String, String> original = mCallingPackage.get();
+ mCallingPackage.set(callingPackage);
+ 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 Pair<String, String> pkg = mCallingPackage.get();
+ if (pkg != null) {
+ mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg.first);
+ return pkg.first;
+ }
+
+ return null;
+ }
+
+ /**
+ * 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 Pair<String, String> pkg = mCallingPackage.get();
+ if (pkg != null) {
+ return pkg.second;
+ }
+
+ 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 Pair<String, String> pkg = mCallingPackage.get();
+ if (pkg != null) {
+ return pkg.first;
+ }
+
+ 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 Pair<String, String> callingPackage;
+
+ /** {@hide} */
+ public CallingIdentity(long binderToken, Pair<String, String> callingPackage) {
+ this.binderToken = binderToken;
+ this.callingPackage = callingPackage;
+ }
+ }
+
+ /**
+ * 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}.
+ */
+ public final @NonNull CallingIdentity clearCallingIdentity() {
+ return new CallingIdentity(Binder.clearCallingIdentity(), setCallingPackage(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);
+ mCallingPackage.set(identity.callingPackage);
+ }
+
+ /**
+ * 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 dummy 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 dummy '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 Access mode for the file. May be "r" for read-only access,
+ * "rw" for read and write access, or "rwt" for read and write access
+ * that truncates any existing file.
+ *
+ * @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 Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access, "rw" for read and write access, or
+ * "rwt" for read and write access that truncates any existing
+ * file.
+ * @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 Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ *
+ * @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 Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ * @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 file mode. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ *
+ * @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
+ 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;
+ mCallingPackage = 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());
+ }
+
+ /** @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;
+ }
+}
diff --git a/android/content/ContentProviderClient.java b/android/content/ContentProviderClient.java
new file mode 100644
index 0000000..d0f5ec4
--- /dev/null
+++ b/android/content/ContentProviderClient.java
@@ -0,0 +1,722 @@
+/*
+ * 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.annotation.TestApi;
+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 @Nullable String mAttributionTag;
+ 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;
+ mAttributionTag = contentResolver.mAttributionTag;
+
+ 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
+ @TestApi
+ @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(
+ mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, url, mode,
+ remoteSignal, null);
+ } 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(mPackageName, mAttributionTag, 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(
+ mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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..7bc5901
--- /dev/null
+++ b/android/content/ContentProviderNative.java
@@ -0,0 +1,928 @@
+/*
+ * 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.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);
+
+ String callingPkg = data.readString();
+ String callingFeatureId = data.readString();
+ 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(callingPkg, callingFeatureId, 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);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+
+ Uri out = insert(callingPkg, featureId, url, values, extras);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case BULK_INSERT_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
+
+ int count = bulkInsert(callingPkg, featureId, url, values);
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case APPLY_BATCH_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ 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(callingPkg, featureId,
+ authority, operations);
+ reply.writeNoException();
+ reply.writeTypedArray(results, 0);
+ return true;
+ }
+
+ case DELETE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+
+ int count = delete(callingPkg, featureId, url, extras);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case UPDATE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+
+ int count = update(callingPkg, featureId, url, values, extras);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case OPEN_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+ IBinder callerToken = data.readStrongBinder();
+
+ ParcelFileDescriptor fd;
+ fd = openFile(callingPkg, featureId, url, mode, signal, callerToken);
+ 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);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(callingPkg, featureId, 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);
+
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ String authority = data.readString();
+ String method = data.readString();
+ String stringArg = data.readString();
+ Bundle extras = data.readBundle();
+
+ Bundle responseBundle = call(callingPkg, featureId, 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);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ 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(callingPkg, featureId, 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);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = canonicalize(callingPkg, featureId, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case CANONICALIZE_ASYNC_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
+ canonicalizeAsync(callingPkg, featureId, uri, callback);
+ return true;
+ }
+
+ case UNCANONICALIZE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = uncanonicalize(callingPkg, featureId, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case REFRESH_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ Bundle extras = data.readBundle();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ boolean out = refresh(callingPkg, featureId, url, extras, signal);
+ reply.writeNoException();
+ reply.writeInt(out ? 0 : -1);
+ return true;
+ }
+
+ case CHECK_URI_PERMISSION_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ int uid = data.readInt();
+ int modeFlags = data.readInt();
+
+ int out = checkUriPermission(callingPkg, featureId, 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(String callingPkg, @Nullable String featureId, 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);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url,
+ ContentValues values, Bundle extras) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url,
+ ContentValues[] values) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId,
+ String authority, ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url, Bundle extras)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url,
+ ContentValues values, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url,
+ String mode, ICancellationSignal signal, IBinder token)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+ data.writeStrongBinder(token);
+
+ 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(String callingPkg, @Nullable String featureId,
+ Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, String authority,
+ String method, String request, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId,
+ Uri url, String mimeType, Bundle opts, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId,
+ Uri uri, RemoteCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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
+ public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle extras,
+ ICancellationSignal signal) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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(String callingPkg, @Nullable String featureId, Uri url, int uid,
+ int modeFlags) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ 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..1fb426e
--- /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=" + typeToString(mType) + " ");
+ if (mUri != null) {
+ sb.append("uri=" + mUri + " ");
+ }
+ if (mValues != null) {
+ sb.append("values=" + mValues + " ");
+ }
+ if (mSelection != null) {
+ sb.append("selection=" + mSelection + " ");
+ }
+ if (mSelectionArgs != null) {
+ sb.append("selectionArgs=" + mSelectionArgs + " ");
+ }
+ if (mExpectedCount != null) {
+ sb.append("expectedCount=" + mExpectedCount + " ");
+ }
+ 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..4fb1ddb
--- /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=" + uri + " ");
+ }
+ if (count != null) {
+ sb.append("count=" + count + " ");
+ }
+ if (extras != null) {
+ sb.append("extras=" + extras + " ");
+ }
+ if (exception != null) {
+ sb.append("exception=" + exception + " ");
+ }
+ 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..0a4627d
--- /dev/null
+++ b/android/content/ContentResolver.java
@@ -0,0 +1,4129 @@
+/*
+ * 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.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.os.storage.StorageManager;
+import android.system.Int32Ref;
+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 = StorageManager.hasIsolatedStorage();
+
+ /**
+ * 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 and without any delay
+ */
+ 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";
+
+ /**
+ * @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
+ 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
+ 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 whitelist 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;
+ 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;
+
+ /**
+ * 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;
+
+ // Timeout given a ContentProvider that has already been started and connected to.
+ private static final int CONTENT_PROVIDER_TIMEOUT_MILLIS = 3 * 1000;
+
+ // 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;
+
+ 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();
+ mAttributionTag = mContext.getAttributionTag();
+ 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 */
+ @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 */
+ @UnsupportedAppUsage
+ public abstract boolean releaseProvider(IContentProvider icp);
+ /** @hide */
+ @UnsupportedAppUsage
+ protected abstract IContentProvider acquireUnstableProvider(Context c, String name);
+ /** @hide */
+ @UnsupportedAppUsage
+ public abstract boolean releaseUnstableProvider(IContentProvider icp);
+ /** @hide */
+ @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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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 {
+ return provider.uncanonicalize(mPackageName, mAttributionTag, url);
+ } 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(mPackageName, mAttributionTag, 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
+ * @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")}.
+ * @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 May be "w", "wa", "rw", or "rwt".
+ * @return OutputStream
+ * @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 file mode to use, as per {@link ContentProvider#openFile
+ * ContentProvider.openFile}.
+ * @return Returns a new ParcelFileDescriptor pointing to the file. 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 file mode to use, as per {@link ContentProvider#openFile
+ * ContentProvider.openFile}.
+ * @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. 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 file mode to use, as per {@link ContentProvider#openAssetFile
+ * ContentProvider.openAssetFile}.
+ * @return Returns a new ParcelFileDescriptor pointing to the file. 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 file mode to use, as per {@link ContentProvider#openAssetFile
+ * ContentProvider.openAssetFile}.
+ * @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. 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(
+ mPackageName, mAttributionTag, 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(
+ mPackageName, mAttributionTag, 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. 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. 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(
+ mPackageName, mAttributionTag, 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(
+ mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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(mPackageName, mAttributionTag, 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();
+ }
+ }
+
+ /**
+ * 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} 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) {
+ if (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)) {
+ return true;
+ }
+ return 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 master 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 master 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 master 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 master 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
+ 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 mPackageName;
+ }
+
+ /** @hide */
+ public @Nullable String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ @UnsupportedAppUsage
+ private static volatile IContentService sContentService;
+ @UnsupportedAppUsage
+ private final Context mContext;
+
+ @UnsupportedAppUsage
+ final String mPackageName;
+ final @Nullable String mAttributionTag;
+ 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, Point.convert(size));
+ final Int32Ref orientation = new Int32Ref(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
+ @TestApi
+ // 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
+ @TestApi
+ // 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..f9f4c5d
--- /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(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..8472144
--- /dev/null
+++ b/android/content/Context.java
@@ -0,0 +1,6122 @@
+/*
+ * 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.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.UserIdInt;
+import android.app.ActivityManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.app.VrManager;
+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.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 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.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.
+ */
+ 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 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
+ */
+ 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.
+ *
+ * @param callback The interface to call. This can be either a
+ * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ */
+ 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).
+ */
+ @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;
+ }
+
+ // TODO moltmann: Remove
+ /**
+ * @removed
+ */
+ @Deprecated
+ public @Nullable String getFeatureId() {
+ return getAttributionTag();
+ }
+
+ /** 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
+ */
+ 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 */
+ 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
+ */
+ 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
+ */
+ @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
+ @TestApi
+ 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
+ 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.");
+ }
+
+ /**
+ * 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
+ */
+ 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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage
+ 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
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ @UnsupportedAppUsage
+ 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
+ */
+ @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>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.
+ */
+ @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
+ */
+ @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.
+ *
+ * <p class="note"><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>
+ *
+ * @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 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 #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.
+ *
+ * @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.
+ *
+ * @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.
+ */
+ @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.
+ */
+ @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.
+ */
+ @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}, 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 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
+ */
+ 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,
+ 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_SERVICE,
+ //@hide: STATUS_BAR_SERVICE,
+ CONNECTIVITY_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,
+ 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,
+ 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,
+ })
+ @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_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.
+ * </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_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
+ */
+ 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}.
+ * </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.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
+ */
+ 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
+ */
+ 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)
+ */
+ public static final String WALLPAPER_SERVICE = "wallpaper";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.os.Vibrator} for interacting with the vibration hardware.
+ *
+ * @see #getSystemService(String)
+ * @see android.os.Vibrator
+ */
+ 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
+ @TestApi
+ @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.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 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
+ @TestApi
+ 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";
+
+ /**
+ * 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} 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 com.android.server.attention.AttentionManagerService} for attention services.
+ *
+ * @see #getSystemService(String)
+ * @see android.server.attention.AttentionManagerService
+ * @hide
+ */
+ public static final String ATTENTION_SERVICE = "attention";
+
+ /**
+ * 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 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";
+
+ /**
+ * 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";
+
+ /**
+ * 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";
+
+ /**
+ * Official published name of the (internal) permission service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final String PERMISSION_SERVICE = "permission";
+
+ /**
+ * Official published name of the (internal) permission controller service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
+
+ /**
+ * 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 @TestApi
+ public static final String ROLLBACK_SERVICE = "rollback";
+
+ /**
+ * 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
+ @SuppressLint("ServiceName") // TODO: This should be renamed to POWER_WHITELIST_SERVICE
+ public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
+
+ /**
+ * 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";
+
+ /**
+ * Official published name of the (internal) role controller service.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.role.RoleControllerService
+ *
+ * @hide
+ */
+ public static final String ROLE_CONTROLLER_SERVICE = "role_controller";
+
+ /**
+ * 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.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
+ * @hide
+ */
+ @SystemApi @TestApi
+ 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";
+
+ /**
+ * 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.content.integrity.AppIntegrityManager}.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ 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.app.DreamManager} for controlling Dream states.
+ *
+ * @see #getSystemService(String)
+
+ * @hide
+ */
+ @TestApi
+ public static final String DREAM_SERVICE = "dream";
+
+ /**
+ * 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 */
+ @PackageManager.PermissionResult
+ @UnsupportedAppUsage
+ 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);
+
+ /** @hide */
+ @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 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);
+
+ /**
+ * 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
+ @TestApi
+ @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
+ @TestApi
+ @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
+ */
+ @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
+ * @hide
+ */
+ @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}.
+ *
+ * @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.
+ */
+ 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
+ * ...
+ * mParams.type = TYPE_APPLICATION_OVERLAY;
+ * ...
+ *
+ * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+ * </pre>
+ *
+ * <p>
+ * This context's configuration and resources are adjusted to a display area 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>
+ *
+ * @param type Window type in {@link WindowManager.LayoutParams}
+ * @param options Bundle used to pass window-related options.
+ * @return A {@link Context} that can be used to create windows.
+ * @throws UnsupportedOperationException if this is called on a non-UI context, such as
+ * {@link android.app.Application Application} or {@link android.app.Service Service}.
+ *
+ * @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 or
+ * the current number of window contexts without adding any view by
+ * {@link WindowManager#addView} <b>exceeds five</b>.
+ */
+ public @NonNull Context createWindowContext(@WindowType int type, @Nullable Bundle options) {
+ 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 featureId) {
+ return createAttributionContext(featureId);
+ }
+
+ /**
+ * 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
+ */
+ @SystemApi
+ public abstract Context createCredentialProtectedStorageContext();
+
+ /**
+ * 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
+ */
+ 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
+ */
+ @TestApi
+ public abstract int getDisplayId();
+
+ /**
+ * @hide
+ */
+ 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
+ */
+ @SystemApi
+ public abstract boolean isCredentialProtectedStorage();
+
+ /**
+ * Returns true if the context can load unsafe resources, e.g. fonts.
+ * @hide
+ */
+ public abstract boolean canLoadUnsafeResources();
+
+ /**
+ * @hide
+ */
+ public IBinder getActivityToken() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * @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 whitelisted.
+ *
+ * @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()");
+ }
+ }
+
+ /**
+ * Indicates if this context is a visual context such as {@link android.app.Activity} or
+ * a context created from {@link #createWindowContext(int, Bundle)}.
+ * @hide
+ */
+ public boolean isUiContext() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+}
diff --git a/android/content/ContextWrapper.java b/android/content/ContextWrapper.java
new file mode 100644
index 0000000..5dc41e4
--- /dev/null
+++ b/android/content/ContextWrapper.java
@@ -0,0 +1,1156 @@
+/*
+ * 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.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.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.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 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 File getExternalFilesDir(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 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 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,
+ 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, Bundle options) {
+ mBase.startActivityForResult(who, intent, requestCode, options);
+ }
+
+ /** @hide **/
+ public boolean canStartActivityForResult() {
+ return mBase.canStartActivityForResult();
+ }
+
+ @Override
+ public void startActivity(Intent intent, Bundle options) {
+ mBase.startActivity(intent, options);
+ }
+
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ mBase.startActivityAsUser(intent, options, user);
+ }
+
+ @Override
+ public void startActivities(Intent[] intents) {
+ mBase.startActivities(intents);
+ }
+
+ @Override
+ public void startActivities(Intent[] intents, Bundle options) {
+ mBase.startActivities(intents, options);
+ }
+
+ /** @hide */
+ @Override
+ public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ return mBase.startActivitiesAsUser(intents, options, userHandle);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ mBase.startIntentSender(intent, fillInIntent, flagsMask,
+ flagsValues, extraFlags);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ 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, 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 sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+ String[] receiverPermissions) {
+ mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions);
+ }
+
+ /** @hide */
+ @SystemApi
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) {
+ mBase.sendBroadcast(intent, receiverPermission, options);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ mBase.sendBroadcast(intent, receiverPermission, appOp);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent,
+ String receiverPermission) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission,
+ resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
+ /** @hide */
+ @SystemApi
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, String receiverPermission, Bundle options,
+ BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission,
+ options, resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ 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,
+ String receiverPermission, Bundle options) {
+ mBase.sendBroadcastAsUser(intent, user, receiverPermission, options);
+ }
+
+ /** @hide */
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp) {
+ mBase.sendBroadcastAsUser(intent, user, receiverPermission, appOp);
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, appOp, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData, 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);
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyOrderedBroadcast(
+ Intent intent, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ 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, Bundle options) {
+ mBase.sendStickyBroadcastAsUser(intent, user, options);
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ 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(
+ BroadcastReceiver receiver, IntentFilter filter) {
+ return mBase.registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public Intent registerReceiver(
+ BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ return mBase.registerReceiver(receiver, filter, flags);
+ }
+
+ @Override
+ public Intent registerReceiver(
+ BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return mBase.registerReceiver(receiver, filter, broadcastPermission,
+ scheduler);
+ }
+
+ @Override
+ public Intent registerReceiver(
+ BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, 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(
+ BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return mBase.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBase.unregisterReceiver(receiver);
+ }
+
+ @Override
+ public ComponentName startService(Intent service) {
+ return mBase.startService(service);
+ }
+
+ @Override
+ public ComponentName startForegroundService(Intent service) {
+ return mBase.startForegroundService(service);
+ }
+
+ @Override
+ public boolean stopService(Intent name) {
+ return mBase.stopService(name);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage
+ public ComponentName startServiceAsUser(Intent service, UserHandle user) {
+ return mBase.startServiceAsUser(service, user);
+ }
+
+ /** @hide */
+ @Override
+ @UnsupportedAppUsage
+ public 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,
+ String profileFile, Bundle arguments) {
+ return mBase.startInstrumentation(className, profileFile, arguments);
+ }
+
+ @Override
+ public 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, String message) {
+ mBase.enforcePermission(permission, pid, uid, message);
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, String message) {
+ mBase.enforceCallingPermission(permission, message);
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(
+ String permission, 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);
+ }
+
+ /** @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);
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+ return mBase.checkCallingOrSelfUriPermission(uri, modeFlags);
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, String readPermission,
+ 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(
+ Uri uri, String readPermission, String writePermission,
+ int pid, int uid, int modeFlags, 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
+ 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
+ public @NonNull Context createAttributionContext(@Nullable String attributionTag) {
+ return mBase.createAttributionContext(attributionTag);
+ }
+
+ @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();
+ }
+
+ @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 IBinder getActivityToken() {
+ return mBase.getActivityToken();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public 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 ContentCaptureOptions getContentCaptureOptions() {
+ return mBase == null ? null : mBase.getContentCaptureOptions();
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @Override
+ public void setContentCaptureOptions(ContentCaptureOptions options) {
+ if (mBase != null) {
+ mBase.setContentCaptureOptions(options);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isUiContext() {
+ return mBase.isUiContext();
+ }
+}
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..4ff5cca
--- /dev/null
+++ b/android/content/CursorLoader.java
@@ -0,0 +1,250 @@
+/*
+ * 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.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
+ final ForceLoadContentObserver mObserver;
+
+ Uri mUri;
+ String[] mProjection;
+ String mSelection;
+ String[] mSelectionArgs;
+ String mSortOrder;
+
+ Cursor mCursor;
+ @UnsupportedAppUsage
+ 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..84b0f0e
--- /dev/null
+++ b/android/content/IContentProvider.java
@@ -0,0 +1,175 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+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 {
+ public Cursor query(String callingPkg, @Nullable String attributionTag, Uri url,
+ @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
+ throws RemoteException;
+ public 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")
+ public default Uri insert(String callingPkg, Uri url, ContentValues initialValues)
+ throws RemoteException {
+ return insert(callingPkg, null, url, initialValues, null);
+ }
+ public Uri insert(String callingPkg, String attributionTag, 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")
+ public default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
+ throws RemoteException {
+ return bulkInsert(callingPkg, null, url, initialValues);
+ }
+ public int bulkInsert(String callingPkg, String attributionTag, 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")
+ public default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+ throws RemoteException {
+ return delete(callingPkg, null, url,
+ ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ }
+ public int delete(String callingPkg, String attributionTag, 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")
+ public default int update(String callingPkg, Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException {
+ return update(callingPkg, null, url, values,
+ ContentResolver.createSqlQueryBundle(selection, selectionArgs));
+ }
+ public int update(String callingPkg, String attributionTag, Uri url, ContentValues values,
+ Bundle extras) throws RemoteException;
+
+ public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag,
+ Uri url, String mode, ICancellationSignal signal, IBinder callerToken)
+ throws RemoteException, FileNotFoundException;
+
+ public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag,
+ Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException;
+
+ public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String attributionTag,
+ 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(callingPkg, null, "unknown", method, arg, extras);
+ }
+
+ public Bundle call(String callingPkg, @Nullable String attributionTag, String authority,
+ String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException;
+
+ public int checkUriPermission(String callingPkg, @Nullable String attributionTag, Uri uri,
+ int uid, int modeFlags) throws RemoteException;
+
+ public ICancellationSignal createCancellationSignal() throws RemoteException;
+
+ public Uri canonicalize(String callingPkg, @Nullable String attributionTag, 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(String callingPkg, @Nullable String attributionTag, Uri uri,
+ RemoteCallback callback) throws RemoteException;
+
+ public Uri uncanonicalize(String callingPkg, @Nullable String attributionTag, Uri uri)
+ throws RemoteException;
+
+ public boolean refresh(String callingPkg, @Nullable String attributionTag, Uri url,
+ @Nullable Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException;
+
+ // Data interchange.
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg,
+ @Nullable String attributionTag, 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
+ 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;
+}
diff --git a/android/content/Intent.java b/android/content/Intent.java
new file mode 100644
index 0000000..baaf8f7
--- /dev/null
+++ b/android/content/Intent.java
@@ -0,0 +1,11414 @@
+/*
+ * 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.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.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.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}
+ * </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";
+
+ /**
+ * 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
+ @TestApi
+ 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.
+ * <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 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. This action would be handled by apps that
+ * want to show details about how and why given permission 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: {@code android.intent.extra.PERMISSION_NAME} specifies the permission
+ * 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 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
+ @TestApi
+ 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
+ @TestApi
+ 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>
+ *
+ * @hide
+ */
+ @SystemApi
+ 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 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";
+ /**
+ * Clear DNS Cache Action: This is broadcast when networks have changed and old
+ * DNS entries should be tossed.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CLEAR_DNS_CACHE = "android.intent.action.CLEAR_DNS_CACHE";
+ /**
+ * 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
+ 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.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ 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.
+ * </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 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 @TestApi
+ @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
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_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 crazy, 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
+ * 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
+ * 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
+ * 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
+ * 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
+ */
+ 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
+ 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 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 master 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";
+
+ /**
+ * 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";
+
+ /**
+ * 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} intents
+ * to tell the application being invoked how many pending alarms are being
+ * delievered 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.
+ */
+ 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 astring 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";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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.
+ */
+ 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.
+ */
+ 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
+ * (longpress home key).
+ */
+ 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".
+ *
+ * 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 in 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;
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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;
+ 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);
+ }
+
+ 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 {
+ 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 {
+ return getIntentOld(uri, 0);
+ }
+
+ 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
+ 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
+ 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
+ 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);
+ }
+ }
+ 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 (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(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
+ 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 toInsecureStringWithClip() {
+ StringBuilder b = new StringBuilder(128);
+
+ b.append("Intent { ");
+ toShortString(b, false, true, true, true);
+ 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={");
+ if (clip) {
+ mClipData.toShortString(b);
+ } else {
+ if (mClipData.getDescription() != null) {
+ first = !mClipData.getDescription().toShortStringTypesOnly(b);
+ } else {
+ first = true;
+ }
+ mClipData.toShortStringShortItems(b, first);
+ }
+ 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();
+ if (clip) {
+ mClipData.toShortString(b);
+ } else {
+ mClipData.toShortStringShortItems(b, false);
+ }
+ 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) {
+ 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
+ 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);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void prepareToEnterProcess() {
+ // We just entered destination process, so we should be able to read all
+ // parcelables inside.
+ setDefusable(true);
+
+ if (mSelector != null) {
+ mSelector.prepareToEnterProcess();
+ }
+ if (mClipData != null) {
+ mClipData.prepareToEnterProcess();
+ }
+
+ if (mContentUserHint != UserHandle.USER_CURRENT) {
+ if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+ fixUris(mContentUserHint);
+ mContentUserHint = UserHandle.USER_CURRENT;
+ }
+ }
+ }
+
+ /** @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() {
+ // 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();
+ }
+ } 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();
+ }
+ }
+ }
+ } 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()) {
+ final Uri output;
+ try {
+ output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ if (output != null) {
+ setClipData(ClipData.newRawUri("", output));
+ addFlags(FLAG_GRANT_WRITE_URI_PERMISSION|FLAG_GRANT_READ_URI_PERMISSION);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 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..745add1
--- /dev/null
+++ b/android/content/IntentFilter.java
@@ -0,0 +1,2466 @@
+/*
+ * 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 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";
+
+ /**
+ * 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
+ 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
+ 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(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) {
+ 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 (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}, 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
+ 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
+ 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}, 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
+ 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 ArrayList<String> types = mDataTypes;
+ final ArrayList<String> schemes = mDataSchemes;
+
+ int match = MATCH_CATEGORY_EMPTY;
+
+ if (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 (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;
+ }
+ 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;
+ }
+ 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 (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 {
+ 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..f40dc29
--- /dev/null
+++ b/android/content/IntentSender.java
@@ -0,0 +1,387 @@
+/*
+ * 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.app.ActivityManager;
+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;
+
+ /**
+ * 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() {
+ try {
+ return ActivityManager.getService()
+ .getPackageForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
+ * 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() {
+ try {
+ return ActivityManager.getService()
+ .getPackageForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
+ * 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() {
+ try {
+ return ActivityManager.getService()
+ .getUidForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return -1;
+ }
+ }
+
+ /**
+ * 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() {
+ try {
+ int uid = ActivityManager.getService()
+ .getUidForIntentSender(mTarget);
+ return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null;
+ } catch (RemoteException e) {
+ // Should never happen.
+ return 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(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);
+ }
+}
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..283cea0
--- /dev/null
+++ b/android/content/LocusId.java
@@ -0,0 +1,149 @@
+/*
+ * 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.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 activiy 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(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..a075148
--- /dev/null
+++ b/android/content/PeriodicSync.java
@@ -0,0 +1,165 @@
+/*
+ * 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.os.Parcelable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.accounts.Account;
+
+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(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..b434072
--- /dev/null
+++ b/android/content/PermissionChecker.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Binder;
+import android.os.Process;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class provides permission check APIs that verify both the
+ * permission and the associated app op for this permission if
+ * such is defined.
+ * <p>
+ * In the new permission model permissions with protection level
+ * dangerous are runtime permissions. For apps targeting {@link android.os.Build.VERSION_CODES#M}
+ * and above the user may not grant such permissions or revoke
+ * them at any time. For apps targeting API lower than {@link android.os.Build.VERSION_CODES#M}
+ * these permissions are always granted as such apps do not expect
+ * permission revocations and would crash. Therefore, when the
+ * user disables a permission for a legacy app in the UI the
+ * platform disables the APIs guarded by this permission making
+ * them a no-op which is doing nothing or returning an empty
+ * result or default error.
+ * </p>
+ * <p>
+ * It is important that when you perform an operation on behalf of
+ * another app you use these APIs to check for permissions as the
+ * app may be a legacy app that does not participate in the new
+ * permission model for which the user had disabled the "permission"
+ * which is achieved by disallowing the corresponding app op.
+ * </p>
+ * <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. */
+ public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED;
+
+ /** Returned when:
+ * <ul>
+ * <li>For non app op permissions, returned when the permission is denied.</li>
+ * <li>For app op permissions, returned when the app op is denied or app op is
+ * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li>
+ * </ul>
+ *
+ */
+ public static final int PERMISSION_HARD_DENIED = PackageManager.PERMISSION_DENIED;
+
+ /** Only for runtime permissions, its returned when the runtime permission
+ * is granted, but the corresponding app op is denied. */
+ public static final int PERMISSION_SOFT_DENIED = PackageManager.PERMISSION_DENIED - 1;
+
+ /** Constant when the PID for which we check permissions is unknown. */
+ public static final int PID_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({PERMISSION_GRANTED,
+ PERMISSION_SOFT_DENIED,
+ PERMISSION_HARD_DENIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionResult {}
+
+ private PermissionChecker() {
+ /* do nothing */
+ }
+
+ /**
+ * Checks whether a given package in a UID and PID has a given permission
+ * and whether the app op that corresponds to this permission is allowed.
+ *
+ * <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 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 checkPermissionCommon(context, permission, pid, uid, packageName, attributionTag,
+ message, true /*forDataDelivery*/);
+ }
+
+ /**
+ * 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)} 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.
+ * @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)
+ */
+ @PermissionResult
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, int pid, int uid, @Nullable String packageName) {
+ return checkPermissionCommon(context, permission, pid, uid, packageName,
+ null /*attributionTag*/, null /*message*/, false /*forDataDelivery*/);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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.
+ *
+ * @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.
+ * @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 #checkCallingPermissionForPreflight(Context, String, String)
+ */
+ @PermissionResult
+ public static int checkCallingPermissionForDataDelivery(@NonNull Context context,
+ @NonNull String permission, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return PERMISSION_HARD_DENIED;
+ }
+ return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
+ Binder.getCallingUid(), packageName, attributionTag, message);
+ }
+
+ /**
+ * 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)} 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 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)
+ */
+ @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.
+ *
+ * @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 attributionTag 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 attributionTag, @Nullable String message) {
+ String packageName = (Binder.getCallingPid() == Process.myPid())
+ ? context.getPackageName() : null;
+ attributionTag = (Binder.getCallingPid() == Process.myPid())
+ ? context.getAttributionTag() : attributionTag;
+ return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
+ Binder.getCallingUid(), packageName, attributionTag, message);
+ }
+
+ /**
+ * 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)} 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)
+ */
+ @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);
+ }
+
+ static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
+ int pid, int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @Nullable String message, boolean forDataDelivery) {
+ final PermissionInfo permissionInfo;
+ try {
+ // TODO(b/147869157): Cache platform defined app op and runtime permissions to avoid
+ // calling into the package manager every time.
+ permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ if (packageName == null) {
+ String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
+ if (packageNames != null && packageNames.length > 0) {
+ packageName = packageNames[0];
+ }
+ }
+
+ if (permissionInfo.isAppOp()) {
+ return checkAppOpPermission(context, permission, pid, uid, packageName, attributionTag,
+ message, forDataDelivery);
+ }
+ if (permissionInfo.isRuntime()) {
+ return checkRuntimePermission(context, permission, pid, uid, packageName,
+ attributionTag, message, forDataDelivery);
+ }
+ return context.checkPermission(permission, pid, uid);
+ }
+
+ private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
+ int pid, int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @Nullable String message, boolean forDataDelivery) {
+ final String op = AppOpsManager.permissionToOp(permission);
+ if (op == null || packageName == null) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final int opMode = (forDataDelivery)
+ ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message)
+ : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ALLOWED:
+ case AppOpsManager.MODE_FOREGROUND: {
+ return PERMISSION_GRANTED;
+ }
+ case AppOpsManager.MODE_DEFAULT: {
+ return context.checkPermission(permission, pid, uid)
+ == PackageManager.PERMISSION_GRANTED
+ ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+ default: {
+ return PERMISSION_HARD_DENIED;
+ }
+ }
+ }
+
+ private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
+ int pid, int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @Nullable String message, boolean forDataDelivery) {
+ if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
+ return PERMISSION_HARD_DENIED;
+ }
+
+ final String op = AppOpsManager.permissionToOp(permission);
+ if (op == null || packageName == null) {
+ return PERMISSION_GRANTED;
+ }
+
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final int opMode = (forDataDelivery)
+ ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message)
+ : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ALLOWED:
+ case AppOpsManager.MODE_FOREGROUND:
+ return PERMISSION_GRANTED;
+ default:
+ return PERMISSION_SOFT_DENIED;
+ }
+ }
+}
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..010992c
--- /dev/null
+++ b/android/content/RestrictionEntry.java
@@ -0,0 +1,558 @@
+/*
+ * 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.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 white-list.
+ * Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ * 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(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..7bcdbfd
--- /dev/null
+++ b/android/content/SyncAdapterType.java
@@ -0,0 +1,253 @@
+/*
+ * 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.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
+ private final boolean isAlwaysSyncable;
+ @UnsupportedAppUsage
+ private final boolean allowParallelSyncs;
+ @UnsupportedAppUsage
+ 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
+ */
+ public @Nullable String getPackageName() {
+ return packageName;
+ }
+
+ public static SyncAdapterType newKey(String authority, String accountType) {
+ return new SyncAdapterType(authority, accountType);
+ }
+
+ public boolean equals(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..58445a7
--- /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 com.android.internal.annotations.GuardedBy;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+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, XmlSerializer out) throws IOException {
+ out.attribute(null, "authority", item.authority);
+ out.attribute(null, "accountType", item.accountType);
+ }
+
+ public SyncAdapterType createFromXml(XmlPullParser 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..9e568a4
--- /dev/null
+++ b/android/content/SyncRequest.java
@@ -0,0 +1,554 @@
+/*
+ * 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.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;
+
+ /**
+ * {@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 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.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);
+ 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;
+ 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;
+
+ 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}
+ * 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;
+ }
+
+ /**
+ * 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() {
+ // Validate the extras bundle
+ ContentResolver.validateSyncExtrasBundle(mCustomExtras);
+ if (mCustomExtras == null) {
+ mCustomExtras = new Bundle();
+ }
+ // 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 (mIsManual) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ if (mSyncType == SYNC_TYPE_PERIODIC) {
+ // If this is a periodic sync ensure than invalid extras were not set.
+ if (ContentResolver.invalidPeriodicExtras(mCustomExtras) ||
+ ContentResolver.invalidPeriodicExtras(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..8280f8e
--- /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() {
+ StringBuffer sb = new StringBuffer();
+
+ 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..b72eb04
--- /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) { // Sanity 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..ed9cd86
--- /dev/null
+++ b/android/content/UndoManager.java
@@ -0,0 +1,956 @@
+/*
+ * 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.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
+ public UndoManager() {
+ }
+
+ @UnsupportedAppUsage
+ 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
+ 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
+ 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
+ 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
+ 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
+ public boolean isInUndo() {
+ return mInUndo;
+ }
+
+ @UnsupportedAppUsage
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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..235d721
--- /dev/null
+++ b/android/content/UndoOperation.java
@@ -0,0 +1,115 @@
+/*
+ * 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.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
+ public UndoOperation(UndoOwner owner) {
+ mOwner = owner;
+ }
+
+ /**
+ * Construct from a Parcel.
+ */
+ @UnsupportedAppUsage
+ 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..2869abb
--- /dev/null
+++ b/android/content/integrity/AppIntegrityManager.java
@@ -0,0 +1,143 @@
+/*
+ * 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 whitelisted as an integrity rule provider. Otherwise a {@link SecurityException} will be
+ * thrown.
+ *
+ * @hide
+ */
+@TestApi
+@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 whitelisted 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..f363a54
--- /dev/null
+++ b/android/content/integrity/AtomicFormula.java
@@ -0,0 +1,695 @@
+/*
+ * 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.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), String.format("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,
+ String.format(
+ "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,
+ String.format(
+ "Key %s cannot be used with LongAtomicFormula", keyToString(key)));
+ checkArgument(
+ isValidOperator(operator), String.format("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(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,
+ String.format(
+ "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,
+ String.format(
+ "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,
+ String.format(
+ "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(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(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..14b1197
--- /dev/null
+++ b/android/content/integrity/CompoundFormula.java
@@ -0,0 +1,228 @@
+/*
+ * 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.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), String.format("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 " + 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(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,
+ String.format(
+ "Connector %s must have at least 2 formulas",
+ connectorToString(connector)));
+ break;
+ case NOT:
+ checkArgument(
+ formulas.size() == 1,
+ String.format(
+ "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..fc17772
--- /dev/null
+++ b/android/content/integrity/IntegrityFormula.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.integrity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+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
+@TestApi
+@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..c3f7624
--- /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 " + hexDigest + ": must have even length");
+
+ 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..d29e6df
--- /dev/null
+++ b/android/content/integrity/Rule.java
@@ -0,0 +1,148 @@
+/*
+ * 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.SystemApi;
+import android.annotation.TestApi;
+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
+ */
+@TestApi
+@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), String.format("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(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..e121ff8
--- /dev/null
+++ b/android/content/integrity/RuleSet.java
@@ -0,0 +1,93 @@
+/*
+ * 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.TestApi;
+
+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
+ */
+@TestApi
+@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/OverlayInfo.java b/android/content/om/OverlayInfo.java
new file mode 100644
index 0000000..62815dd
--- /dev/null
+++ b/android/content/om/OverlayInfo.java
@@ -0,0 +1,464 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+
+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 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;
+
+ /**
+ * 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
+ 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;
+
+ /**
+ * 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.targetPackageName, source.targetOverlayableName,
+ source.category, source.baseCodePath, state, source.userId, source.priority,
+ source.isMutable);
+ }
+
+ /** @hide */
+ 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 = packageName;
+ this.targetPackageName = targetPackageName;
+ this.targetOverlayableName = targetOverlayableName;
+ this.category = category;
+ this.baseCodePath = baseCodePath;
+ this.state = state;
+ this.userId = userId;
+ this.priority = priority;
+ this.isMutable = isMutable;
+ ensureValidState();
+ }
+
+ /** @hide */
+ public OverlayInfo(Parcel source) {
+ packageName = 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();
+ ensureValidState();
+ }
+
+ /**
+ * Returns package name of the current overlay.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * Returns the target package name of the current overlay.
+ * @hide
+ */
+ @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;
+ }
+
+ /**
+ * Returns name of the target overlayable declaration.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getTargetOverlayableName() {
+ return targetOverlayableName;
+ }
+
+ @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(targetPackageName);
+ dest.writeString(targetOverlayableName);
+ dest.writeString(category);
+ dest.writeString(baseCodePath);
+ dest.writeInt(state);
+ dest.writeInt(userId);
+ dest.writeInt(priority);
+ dest.writeBoolean(isMutable);
+ }
+
+ 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 + ((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 (!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 { overlay=" + packageName + ", targetPackage=" + targetPackageName
+ + ((targetOverlayableName == null) ? ""
+ : ", 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..2bdca7d
--- /dev/null
+++ b/android/content/om/OverlayManager.java
@@ -0,0 +1,282 @@
+/*
+ * 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.annotation.TestApi;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+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
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ 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 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
+ */
+ @TestApi
+ @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();
+ }
+ }
+
+ /**
+ * 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/OverlayableInfo.java b/android/content/om/OverlayableInfo.java
new file mode 100644
index 0000000..5923907
--- /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(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..b1f8869
--- /dev/null
+++ b/android/content/pm/ActivityInfo.java
@@ -0,0 +1,1482 @@
+/*
+ * 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.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.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
+ * 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;
+ /**
+ * The launch mode style requested by the activity. From the
+ * {@link android.R.attr#launchMode} attribute, one of
+ * {@link #LAUNCH_MULTIPLE},
+ * {@link #LAUNCH_SINGLE_TOP}, {@link #LAUNCH_SINGLE_TASK}, or
+ * {@link #LAUNCH_SINGLE_INSTANCE}.
+ */
+ 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
+ */
+ public float maxAspectRatio;
+
+ /**
+ * Value indicating the minimum aspect ratio the activity supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#minAspectRatio}.
+ * @hide
+ */
+ public float minAspectRatio;
+
+ /**
+ * 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
+ 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.
+ */
+ @UnsupportedAppUsage
+ 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;
+
+ /**
+ * Options that have been set in the activity declaration in the manifest.
+ * These include:
+ * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}.
+ * @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;
+
+ /** @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
+ };
+
+ /**
+ * Convert Java change bits to native.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ 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_WHITELISTED = 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_WHITELISTED:
+ return "LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED";
+ 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;
+ maxAspectRatio = orig.maxAspectRatio;
+ minAspectRatio = orig.minAspectRatio;
+ }
+
+ /**
+ * 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 maxAspectRatio != 0 || minAspectRatio != 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 true if the activity supports picture-in-picture.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean supportsPictureInPicture() {
+ return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
+ }
+
+ /** @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;
+ }
+ }
+
+ 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 (maxAspectRatio != 0) {
+ pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
+ }
+ if (minAspectRatio != 0) {
+ pw.println(prefix + "minAspectRatio=" + minAspectRatio);
+ }
+ 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(maxAspectRatio);
+ dest.writeFloat(minAspectRatio);
+ }
+
+ /**
+ * 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();
+ maxAspectRatio = source.readFloat();
+ minAspectRatio = source.readFloat();
+ }
+
+ /**
+ * 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 = width;
+ this.widthFraction = widthFraction;
+ this.height = height;
+ this.heightFraction = heightFraction;
+ this.gravity = gravity;
+ this.minWidth = minWidth;
+ this.minHeight = minHeight;
+ }
+
+ /** @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.
+ * @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..1cbbdca
--- /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;
+
+/**
+ * Dummy 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/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
new file mode 100644
index 0000000..043953d
--- /dev/null
+++ b/android/content/pm/ApplicationInfo.java
@@ -0,0 +1,2243 @@
+/*
+ * 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.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 {
+ /**
+ * 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
+ public int fullBackupContent = 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.
+ */
+ 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 {}
+
+ /**
+ * 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;
+
+ /**
+ * @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;
+
+ /**
+ * 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}.
+ *
+ * @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;
+
+ /**
+ * 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
+ })
+ @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;
+
+ /**
+ * 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);
+ 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;
+
+ /**
+ * 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 ((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"));
+ }
+ 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);
+ }
+ }
+ 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);
+ }
+ }
+ 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);
+ }
+ 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;
+ 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;
+ 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;
+ 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;
+ }
+
+ 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(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.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.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);
+ }
+
+ 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();
+ 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();
+ 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();
+ 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();
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 whitelist.
+ *
+ * @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;
+ }
+
+ /** @hide */
+ 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;
+ }
+
+ /** @hide */
+ public boolean isVendor() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0;
+ }
+
+ /** @hide */
+ 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 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;
+ }
+
+ /**
+ * @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 };
+ 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} */
+ @UnsupportedAppUsage
+ 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; }
+}
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/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..bd847bf
--- /dev/null
+++ b/android/content/pm/BaseParceledListSlice.java
@@ -0,0 +1,214 @@
+/*
+ * 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.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;
+ }
+
+ final T parcelable = readCreator(creator, p, loader);
+ if (listElementClass == null) {
+ listElementClass = parcelable.getClass();
+ } else {
+ verifySameType(listElementClass, parcelable.getClass());
+ }
+
+ mList.add(parcelable);
+
+ 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) {
+ final T parcelable = readCreator(creator, reply, loader);
+ verifySameType(listElementClass, parcelable.getClass());
+
+ mList.add(parcelable);
+
+ if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ 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
+ 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
+ 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/ComponentInfo.java b/android/content/pm/ComponentInfo.java
new file mode 100644
index 0000000..628bcd7
--- /dev/null
+++ b/android/content/pm/ComponentInfo.java
@@ -0,0 +1,248 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.graphics.drawable.Drawable;
+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;
+
+ /**
+ * 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;
+ 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
+ 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);
+ }
+ 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.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();
+ 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/CrossProfileApps.java b/android/content/pm/CrossProfileApps.java
new file mode 100644
index 0000000..99e6d91
--- /dev/null
+++ b/android/content/pm/CrossProfileApps.java
@@ -0,0 +1,522 @@
+/*
+ * 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.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
+ * deciding which task the new activity should belong to. If {@code null}, the activity
+ * will always be started in a new task.
+ */
+ @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
+ * deciding which task the new activity should belong to. If {@code null}, the activity
+ * will always be started in a new task.
+ * @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>
+ * </ul>
+ *
+ * <p>Note that in order for the user to be able to grant the consent, the requesting package
+ * must be whitelisted 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 whitelisted by default by the OEM or has been
+ * explicitly whitelisted 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
+ */
+ 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 whitelisted or not installed in the other profile.
+ *
+ * <p>Note that platform-signed apps that are automatically granted the permission and are not
+ * whitelisted 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..16a749f
--- /dev/null
+++ b/android/content/pm/CrossProfileAppsInternal.java
@@ -0,0 +1,65 @@
+/*
+ * 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.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);
+}
diff --git a/android/content/pm/DataLoaderManager.java b/android/content/pm/DataLoaderManager.java
new file mode 100644
index 0000000..e8fb241
--- /dev/null
+++ b/android/content/pm/DataLoaderManager.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.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 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,
+ @NonNull IDataLoaderStatusListener listener) {
+ try {
+ return mService.bindToDataLoader(dataLoaderId, params, 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..a791026
--- /dev/null
+++ b/android/content/pm/DataLoaderParams.java
@@ -0,0 +1,98 @@
+/*
+ * 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 Data Loader.
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ * @hide
+ */
+@SystemApi
+public class DataLoaderParams {
+ @NonNull
+ private final DataLoaderParamsParcel mData;
+
+ /**
+ * Creates and populates set of Data Loader parameters for Streaming installation.
+ *
+ * @param componentName Data Loader component supporting Streaming installation.
+ * @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 Data Loader component supporting Incremental installation.
+ * @param arguments free form installation arguments
+ */
+ 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/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..de761ad
--- /dev/null
+++ b/android/content/pm/InstallationFile.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ *
+ * @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..84f5021
--- /dev/null
+++ b/android/content/pm/InstantAppRequest.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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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 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, 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..67bda2c
--- /dev/null
+++ b/android/content/pm/IntentFilterVerificationInfo.java
@@ -0,0 +1,258 @@
+/*
+ * 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_UNDEFINED;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+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_NEVER;
+
+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 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.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 mMainStatus;
+
+ /** @hide */
+ public IntentFilterVerificationInfo() {
+ mPackageName = null;
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ /** @hide */
+ public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) {
+ mPackageName = packageName;
+ mDomains = domains;
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ /** @hide */
+ public IntentFilterVerificationInfo(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ readFromXml(parser);
+ }
+
+ /** @hide */
+ public IntentFilterVerificationInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getStatus() {
+ return mMainStatus;
+ }
+
+ /** @hide */
+ public void setStatus(int s) {
+ if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
+ s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ mMainStatus = 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(XmlPullParser 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(XmlPullParser parser, String attribute, int defaultValue) {
+ String value = parser.getAttributeValue(null, attribute);
+ if (TextUtils.isEmpty(value)) {
+ String msg = "Missing element under " + TAG +": " + attribute + " at " +
+ parser.getPositionDescription();
+ Log.w(TAG, msg);
+ return defaultValue;
+ } else {
+ return Integer.parseInt(value);
+ }
+ }
+
+ /** @hide */
+ public void readFromXml(XmlPullParser 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);
+ }
+ mMainStatus = 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(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
+ serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
+ 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)mMainStatus) << 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();
+ mMainStatus = 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(mMainStatus);
+ 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..5c1d35e
--- /dev/null
+++ b/android/content/pm/KeySet.java
@@ -0,0 +1,109 @@
+/*
+ * 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.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(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..67deb82
--- /dev/null
+++ b/android/content/pm/LauncherActivityInfo.java
@@ -0,0 +1,178 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+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 static final String TAG = "LauncherActivityInfo";
+
+ private final PackageManager mPm;
+
+ @UnsupportedAppUsage
+ private ActivityInfo mActivityInfo;
+ private ComponentName mComponentName;
+ private UserHandle mUser;
+
+ /**
+ * Create a launchable activity object for a given ResolveInfo and user.
+ *
+ * @param context The context for fetching resources.
+ * @param info ResolveInfo from which to create the LauncherActivityInfo.
+ * @param user The UserHandle of the profile to which this activity belongs.
+ */
+ LauncherActivityInfo(Context context, ActivityInfo info, UserHandle user) {
+ this(context);
+ mActivityInfo = info;
+ mComponentName = new ComponentName(info.packageName, info.name);
+ mUser = user;
+ }
+
+ LauncherActivityInfo(Context context) {
+ mPm = context.getPackageManager();
+ }
+
+ /**
+ * Returns the component name of this activity.
+ *
+ * @return ComponentName of the activity
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * 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 mActivityInfo.loadLabel(mPm);
+ }
+
+ /**
+ * 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 = mActivityInfo.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(mActivityInfo.applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (NameNotFoundException | Resources.NotFoundException exc) {
+ }
+ }
+ // Get the default density icon
+ if (icon == null) {
+ icon = mActivityInfo.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 mActivityInfo.applicationInfo.flags;
+ }
+
+ /**
+ * Returns the application info for the appliction this activity belongs to.
+ * @return
+ */
+ public ApplicationInfo getApplicationInfo() {
+ return mActivityInfo.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(mActivityInfo.packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the name for the acitivty from android:name in the manifest.
+ * @return the name from android:name for the acitivity.
+ */
+ public String getName() {
+ return mActivityInfo.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/LauncherApps.java b/android/content/pm/LauncherApps.java
new file mode 100644
index 0000000..bbcac56
--- /dev/null
+++ b/android/content/pm/LauncherApps.java
@@ -0,0 +1,2141 @@
+/*
+ * 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";
+
+ 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) {
+ }
+ }
+
+ /**
+ * 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;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_MATCH_DYNAMIC,
+ FLAG_MATCH_PINNED,
+ FLAG_MATCH_MANIFEST,
+ FLAG_MATCH_CACHED,
+ FLAG_GET_KEY_FIELDS_ONLY,
+ })
+ @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 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 {
+ ActivityInfo ai = mService.resolveActivity(mContext.getPackageName(),
+ intent.getComponent(), user);
+ if (ai != null) {
+ LauncherActivityInfo info = new LauncherActivityInfo(mContext, ai, user);
+ return info;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return null;
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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<ResolveInfo> activities, UserHandle user) {
+ if (activities == null) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
+ for (ResolveInfo ri : activities.getList()) {
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri.activityInfo, user);
+ 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.
+ * @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) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.cacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ } 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.
+ * @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) {
+ logErrorForInvalidProfileAccess(user);
+ try {
+ mService.uncacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ } 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 = null;
+ try {
+ uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId,
+ userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * 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.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);
+ }
+ }
+ }
+ };
+
+ 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 LauncherApps.Callback mCallback;
+
+ private static class CallbackInfo {
+ String[] packageNames;
+ String packageName;
+ Bundle launcherExtras;
+ boolean replacing;
+ UserHandle user;
+ List<ShortcutInfo> shortcuts;
+ }
+
+ 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;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <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.
+ *
+ * <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..19b681e
--- /dev/null
+++ b/android/content/pm/LimitedLengthInputStream.java
@@ -0,0 +1,95 @@
+package android.content.pm;
+
+import libcore.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..a6db662
--- /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(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..d7abb68
--- /dev/null
+++ b/android/content/pm/PackageInfo.java
@@ -0,0 +1,557 @@
+/*
+ * 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;
+
+ /**
+ * 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 = 1<<0;
+
+ /**
+ * Flag for {@link #requestedPermissionsFlags}: the requested permission
+ * is currently granted to the application.
+ */
+ public static final int REQUESTED_PERMISSION_GRANTED = 1<<1;
+
+ /**
+ * 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.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);
+ 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..85a3986
--- /dev/null
+++ b/android/content/pm/PackageInstaller.java
@@ -0,0 +1,2750 @@
+/*
+ * 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.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.util.ArraySet;
+import android.util.ExceptionUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+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 ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>();
+
+ /** {@hide} */
+ public PackageInstaller(IPackageInstaller installer,
+ String installerPackageName, int userId) {
+ mInstaller = installer;
+ mInstallerPackageName = installerPackageName;
+ 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, 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) whitelist
+ * 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();
+ }
+ }
+
+
+ /** {@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();
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ * {@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.
+ *
+ * @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-callback session
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ * {@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-callback 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();
+ }
+ }
+
+ /**
+ * 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 SecurityException if the session does not update the original installer
+ * @throws SecurityException if streams opened through
+ * {@link #openWrite(String, long, long) are still open.
+ */
+ public void transfer(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ Objects.requireNonNull(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
+ * opening the session and calling {@link Session#abandon()}.
+ */
+ 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.
+ *
+ * @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;
+
+ /** {@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} */
+ @UnsupportedAppUsage
+ public long sizeBytes = -1;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public String appPackageName;
+ /** {@hide} */
+ @UnsupportedAppUsage
+ 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
+ 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;
+
+ /**
+ * 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();
+ 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();
+ }
+
+ /** {@hide} */
+ public SessionParams copy() {
+ SessionParams ret = new SessionParams(mode);
+ ret.installFlags = installFlags;
+ ret.installLocation = installLocation;
+ ret.installReason = installReason;
+ 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;
+ 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.
+ */
+ 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
+ */
+ @TestApi
+ @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 whitelisted for the app. Whitelisting
+ * is not granting the permissions, rather it allows the app to hold permissions
+ * which are otherwise restricted. Whitelisting 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. Whitelisting a hard restricted permission
+ * allows the app to hold that permission and whitelisting 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 whitelist
+ * 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 whitelisted but you can change
+ * which ones are whitelisted by calling this method or the corresponding ones
+ * on the {@link PackageManager}.
+ *
+ * @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.
+ */
+ 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 @TestApi
+ 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 @TestApi
+ 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 @TestApi
+ 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;
+ }
+ }
+
+ /**
+ * 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 @TestApi
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public void setStaged() {
+ this.isStaged = true;
+ }
+
+ /**
+ * Set this session to be installing an APEX package.
+ *
+ * {@hide}
+ */
+ @SystemApi @TestApi
+ @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 provider mode and disallow direct writes into
+ * staging folder.
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ * {@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;
+ }
+
+ /** {@hide} */
+ public void dump(IndentingPrintWriter pw) {
+ pw.printPair("mode", mode);
+ pw.printHexPair("installFlags", installFlags);
+ pw.printPair("installLocation", installLocation);
+ 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("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.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);
+ }
+
+ 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})
+ @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;
+
+ /** {@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} */
+ @UnsupportedAppUsage
+ public String resolvedBaseCodePath;
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public float progress;
+ /** {@hide} */
+ @UnsupportedAppUsage
+ 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} */
+ @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} */
+ @UnsupportedAppUsage
+ public SessionInfo() {
+ }
+
+ /** {@hide} */
+ public SessionInfo(Parcel source) {
+ sessionId = source.readInt();
+ userId = source.readInt();
+ installerPackageName = source.readString();
+ resolvedBaseCodePath = source.readString();
+ progress = source.readFloat();
+ sealed = source.readInt() != 0;
+ active = source.readInt() != 0;
+
+ mode = source.readInt();
+ installReason = 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();
+ }
+
+ /**
+ * 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 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 whitelisted this method returns {@link
+ * SessionParams#RESTRICTED_PERMISSIONS_ALL}.
+ *
+ * @hide
+ */
+ @TestApi
+ @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
+ */
+ @TestApi
+ @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;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code true},
+ * return true. If it was called with {@code false} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsFullApp
+ */
+ @SystemApi
+ public boolean getInstallAsInstantApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code false},
+ * return true. If it was called with {@code true} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsInstantApp
+ */
+ @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 @TestApi
+ @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;
+ }
+
+ @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(resolvedBaseCodePath);
+ dest.writeFloat(progress);
+ dest.writeInt(sealed ? 1 : 0);
+ dest.writeInt(active ? 1 : 0);
+
+ dest.writeInt(mode);
+ dest.writeInt(installReason);
+ 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);
+ }
+
+ 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..d41ace5
--- /dev/null
+++ b/android/content/pm/PackageItemInfo.java
@@ -0,0 +1,496 @@
+/*
+ * 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.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 */
+ private static final int MAX_SAFE_LABEL_LENGTH = 50000;
+
+ /** @hide */
+ public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
+
+ /**
+ * 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) {
+ return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
+ | SAFE_STRING_FLAG_FIRST_LINE);
+ } else {
+ return loadUnsafeLabel(pm);
+ }
+ }
+
+ /** {@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..f92d095
--- /dev/null
+++ b/android/content/pm/PackageManager.java
@@ -0,0 +1,8206 @@
+/*
+ * 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.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.EnabledAfter;
+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.PackageParser.PackageParserException;
+import android.content.pm.dex.ArtManager;
+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.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.incremental.IncrementalManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.permission.PermissionManager;
+import android.util.AndroidException;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+
+import dalvik.system.VMRuntime;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 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}.
+ */
+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);
+ }
+ }
+
+ /**
+ * Listener for changes in permissions granted to a UID.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public interface OnPermissionsChangedListener {
+
+ /**
+ * Called when the permissions for a UID change.
+ * @param uid The UID with a change.
+ */
+ public void onPermissionsChanged(int uid);
+ }
+
+ /**
+ * 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,
+ })
+ @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.
+ */
+ 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.
+ */
+ 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
+ @TestApi
+ 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;
+
+ /** @hide */
+ @Deprecated
+ public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO;
+
+ /**
+ * Internal {@link PackageInfo} flag used to indicate that a package is a hidden system app.
+ * @hide
+ */
+ 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
+ @TestApi
+ 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,
+ INSTALL_DRY_RUN,
+ })
+ @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.
+ *
+ * @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 package should only be verified
+ * but not installed.
+ *
+ * @hide
+ */
+ public static final int INSTALL_DRY_RUN = 0x00800000;
+
+ /** @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 = { "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}
+ * if the new package uses a shared library that is not available.
+ *
+ * @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: instant app installs are incompatible with some
+ * other installation flags supplied for the operation; or other circumstances such
+ * as trying to upgrade a system app via an instant app install.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_INSTANT_APP_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;
+
+ /** @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;
+
+ /**
+ * 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
+ 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
+ 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.
+ *
+ * @hide
+ */
+ @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.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
+
+ /**
+ * Internal status code to indicate that an IntentFilter verification result is not specified.
+ *
+ * @hide
+ */
+ @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).
+ *
+ * @hide
+ */
+ @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.
+ *
+ * @hide
+ */
+ @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.
+ *
+ * @hide
+ */
+ @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.
+ *
+ * @hide
+ */
+ @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()}
+ */
+ @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}: 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 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}: 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 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
+ */
+ @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}:
+ * The device has a StrongBox hardware-backed Keystore.
+ */
+ @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.
+ *
+ * @see IncrementalManager#isEnabled
+ * @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";
+
+ /** @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}
+ *
+ * @hide
+ */
+ 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"
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ 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
+ */
+ @TestApi
+ @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
+ */
+ @TestApi
+ @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
+ */
+ @TestApi
+ @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
+ */
+ @TestApi
+ @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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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 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 or on upgrade.
+ */
+ 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 or on upgrade.
+ */
+ 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 or the system.
+ */
+ 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
+ @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
+ @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
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ 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 noting system app state as hidden before installation
+ * @hide
+ */
+ public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0;
+
+ /**
+ * Constant for noting system app state as visible before installation
+ * @hide
+ */
+ public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1;
+
+ /**
+ * Constant for noting system app state as installed
+ * @hide
+ */
+ public static final int SYSTEM_APP_STATE_INSTALLED = 2;
+
+ /**
+ * Constant for noting system app state as not installed
+ * @hide
+ */
+ public static final int SYSTEM_APP_STATE_UNINSTALLED = 3;
+
+ /** {@hide} */
+ public int getUserId() {
+ return UserHandle.myUserId();
+ }
+
+ /**
+ * 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
+ */
+ @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
+ */
+ 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
+ */
+ @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
+ */
+ @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.
+ */
+ 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 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 package with the given name cannot be
+ * found on the system.
+ */
+ @NonNull
+ public abstract List<PermissionInfo> queryPermissionsByGroup(@NonNull 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
+ */
+ @TestApi @SystemApi
+ public abstract boolean arePermissionsIndividuallyControlled();
+
+ /**
+ * Returns true if wireless consent mode is enabled
+ *
+ * @hide
+ */
+ public abstract boolean isWirelessConsentModeEnabled();
+
+ /**
+ * Retrieve all of the information we know about a particular group of
+ * permissions.
+ *
+ * @param permName 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.
+ */
+ @NonNull
+ public abstract PermissionGroupInfo getPermissionGroupInfo(@NonNull String permName,
+ @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.
+ */
+ @NonNull
+ public abstract List<PermissionGroupInfo> getAllPermissionGroups(
+ @PermissionGroupInfoFlags int flags);
+
+ /**
+ * 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} */
+ @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());
+ }
+
+ /**
+ * 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 a package with the given name 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
+ */
+ @NonNull
+ @TestApi
+ @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
+ public abstract boolean isPermissionRevokedByPolicy(@NonNull String permName,
+ @NonNull String packageName);
+
+ /**
+ * Gets the package name of the component controlling runtime permissions.
+ *
+ * @return The package name.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ @TestApi
+ public abstract String getPermissionControllerPackageName();
+
+ /**
+ * 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)
+ */
+ 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.
+ */
+ 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)
+ */
+ 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
+ */
+ @TestApi
+ @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
+ */
+ @TestApi
+ @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
+ */
+ @TestApi
+ @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
+ */
+ @SystemApi
+ @TestApi
+ @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
+ */
+ @SystemApi
+ @TestApi
+ @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 three 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 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.
+ *
+ * @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.
+ */
+ @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 three 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.
+ *
+ * <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.
+ *
+ * @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.
+ */
+ @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 three 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.
+ *
+ * <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.
+ *
+ * @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.
+ */
+ @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, or a holder of
+ * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} 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).
+ *
+ * @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.
+ */
+ @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.
+ * @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.
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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 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;
+
+ /** @hide */
+ @NonNull
+ @UnsupportedAppUsage
+ 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) {
+ final PackageParser parser = new PackageParser();
+ parser.setCallback(new PackageParser.CallbackImpl(this));
+ final File apkFile = new File(archiveFilePath);
+ try {
+ if ((flags & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
+ // Caller expressed an explicit opinion about what encryption
+ // aware/unaware components they want to see, so fall through and
+ // give them what they want
+ } else {
+ // Caller expressed no opinion, so match everything
+ flags |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
+ PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
+ if ((flags & GET_SIGNATURES) != 0) {
+ PackageParser.collectCertificates(pkg, false /* skipVerify */);
+ }
+ PackageUserState state = new PackageUserState();
+ return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
+ } catch (PackageParserException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @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.
+ */
+ @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.
+ */
+ @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.
+ *
+ * @hide
+ */
+ @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}
+ *
+ * @hide
+ */
+ @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.
+ *
+ * @hide
+ */
+ @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.
+ *
+ * @hide
+ */
+ @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
+ */
+ @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
+ */
+ @Nullable
+ @TestApi
+ @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
+ */
+ @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 */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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} */
+ @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} */
+ @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
+ */
+ @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}.
+ */
+ @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);
+
+ /**
+ * 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
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @UnsupportedAppUsage
+ public abstract boolean getApplicationHiddenSettingAsUser(@NonNull String packageName,
+ @NonNull UserHandle userHandle);
+
+ /**
+ * Sets system app state
+ * @param packageName Package name of the app.
+ * @param state State of the app.
+ * @hide
+ */
+ 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
+ */
+ @SystemApi
+ @TestApi
+ @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
+ */
+ @SystemApi
+ @TestApi
+ @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
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias);
+
+ /** Return the signing {@link KeySet} for this application.
+ * @hide
+ */
+ @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
+ */
+ @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
+ */
+ @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
+ */
+ @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.
+ */
+ 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} */
+ @UnsupportedAppUsage
+ public abstract int getMoveStatus(int moveId);
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public abstract void registerMoveCallback(@NonNull MoveCallback callback,
+ @NonNull Handler handler);
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public abstract void unregisterMoveCallback(@NonNull MoveCallback callback);
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public abstract int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol);
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public abstract @Nullable VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app);
+ /** {@hide} */
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract List<VolumeInfo> getPackageCandidateVolumes(
+ @NonNull ApplicationInfo app);
+
+ /** {@hide} */
+ public abstract int movePrimaryStorage(@NonNull VolumeInfo vol);
+ /** {@hide} */
+ public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume();
+ /** {@hide} */
+ 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
+ */
+ @NonNull
+ public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
+
+ /**
+ * Returns true if the device is upgrading, such as first boot after OTA.
+ *
+ * @hide
+ */
+ @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
+ */
+ @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
+ */
+ @UnsupportedAppUsage
+ public abstract void clearCrossProfileIntentFilters(@UserIdInt int sourceUserId);
+
+ /**
+ * @hide
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo,
+ @Nullable ApplicationInfo appInfo);
+
+ /**
+ * @hide
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ public abstract Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo,
+ @Nullable ApplicationInfo appInfo);
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public abstract boolean isPackageAvailable(@NonNull String packageName);
+
+ /** {@hide} */
+ @NonNull
+ @UnsupportedAppUsage
+ 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";
+ 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";
+ 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;
+ 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
+ */
+ @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
+ */
+ @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
+ */
+ @Nullable
+ @SystemApi
+ public abstract ComponentName getInstantAppInstallerComponent();
+
+ /**
+ * Return the Android Id for a given Instant App.
+ *
+ * @see {@link android.provider.Settings.Secure#ANDROID_ID}
+ * @hide
+ */
+ @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
+ */
+ @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");
+ }
+
+ /**
+ * @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 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
+ @TestApi
+ @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");
+ }
+
+ /**
+ * @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");
+ }
+
+ // 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(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) {
+ @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(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>(
+ 16, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
+ @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);
+ }
+}
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
new file mode 100644
index 0000000..81de29c
--- /dev/null
+++ b/android/content/pm/PackageManagerInternal.java
@@ -0,0 +1,991 @@
+/*
+ * 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.parsing.component.ParsedMainComponent;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+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.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Package manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class PackageManagerInternal {
+ 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_WIFI = 13;
+ public static final int PACKAGE_COMPANION = 14;
+ public static final int PACKAGE_RETAIL_DEMO = 15;
+
+ @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;
+
+ @IntDef(value = {
+ PACKAGE_SYSTEM,
+ PACKAGE_SETUP_WIZARD,
+ PACKAGE_INSTALLER,
+ PACKAGE_VERIFIER,
+ PACKAGE_BROWSER,
+ PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+ PACKAGE_PERMISSION_CONTROLLER,
+ PACKAGE_WELLBEING,
+ PACKAGE_DOCUMENTER,
+ PACKAGE_CONFIGURATOR,
+ PACKAGE_INCIDENT_REPORT_APPROVER,
+ PACKAGE_APP_PREDICTOR,
+ PACKAGE_WIFI,
+ PACKAGE_COMPANION,
+ PACKAGE_RETAIL_DEMO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface KnownPackage {}
+
+ /** 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. This enforces
+ * app visibility rules and permissions. Call {@link #getPackageUidInternal} for the internal
+ * implementation.
+ * @deprecated Use {@link PackageManager#getPackageUid(String, int)}
+ * @return The app's uid, or < 0 if the package was not found in that user
+ */
+ @Deprecated
+ public abstract int getPackageUid(String packageName,
+ @PackageInfoFlags int flags, 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
+ * TODO(b/148235092): rename this to getPackageUid
+ */
+ public abstract int getPackageUidInternal(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 DevicePolicyManagerService to set the package names protected by the device
+ * owner.
+ */
+ public abstract void setDeviceOwnerProtectedPackages(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 overlayPackageNames The complete list of overlay packages that should be enabled for
+ * the target. Previously enabled overlays not specified in the list
+ * will be disabled. Pass in null or an empty list 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,
+ List<String> overlayPackageNames, Collection<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);
+
+ /**
+ * 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.
+ *
+ * @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.
+ *
+ * @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);
+
+ /**
+ * 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);
+
+ /** Sets the enforcement of reading external storage */
+ public abstract void setReadExternalStorageEnforced(boolean enforced);
+
+ /**
+ * 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);
+}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
new file mode 100644
index 0000000..3b3521f
--- /dev/null
+++ b/android/content/pm/PackageParser.java
@@ -0,0 +1,8332 @@
+/*
+ * 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.FEATURE_WATCH;
+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.annotation.TestApi;
+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.permission.SplitPermissionInfoParcelable;
+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.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.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.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.Display;
+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.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 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>
+ *
+ * @hide
+ */
+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;
+ public static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO = 1.333f;
+ public static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH = 1f;
+
+ 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 METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
+ 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;
+
+ /**
+ * Total number of packages that were read from the cache. We use it only for logging.
+ */
+ public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger();
+
+ // 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
+ 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;
+
+ public PackageLite(String codePath, 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;
+ this.codePath = codePath;
+ this.baseCodePath = baseApk.codePath;
+ 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;
+ }
+
+ public List<String> getAllCodePaths() {
+ ArrayList<String> paths = new ArrayList<>();
+ paths.add(baseCodePath);
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ Collections.addAll(paths, splitCodePaths);
+ }
+ return paths;
+ }
+ }
+
+ /**
+ * 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;
+ 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 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 multiArch, boolean use32bitAbi,
+ boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits,
+ String targetPackageName, boolean overlayIsStatic, int overlayPriority,
+ int minSdkVersion, int targetSdkVersion) {
+ 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.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;
+ }
+
+ 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 sanity 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, 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, 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 sanity 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 sanity 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
+ 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
+ verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
+ apkPath, minSignatureScheme);
+ } 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";
+ }
+
+ 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 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;
+
+ 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);
+ }
+ }
+ } 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,
+ multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits,
+ targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion);
+ }
+
+ /**
+ * 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());
+ }
+
+ List<SplitPermissionInfoParcelable> splitPermissions;
+
+ try {
+ splitPermissions = ActivityThread.getPermissionManager().getSplitPermissions();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ final int listSize = splitPermissions.size();
+ for (int is = 0; is < listSize; is++) {
+ final SplitPermissionInfoParcelable 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.
+ */
+ @TestApi
+ 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.
+ */
+ @TestApi
+ 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;
+ // Debuggable implies profileable
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL;
+ }
+
+ 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);
+
+ 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) {
+ final float minAspectRatio;
+ if (owner.applicationInfo.minAspectRatio != 0) {
+ // Use the application max aspect ration as default if set.
+ minAspectRatio = owner.applicationInfo.minAspectRatio;
+ } else {
+ // Default to (1.33) 4:3 aspect ratio for pre-Q apps and unset for Q and greater.
+ // NOTE: 4:3 was the min aspect ratio Android devices can support pre-Q per the CDD,
+ // except for watches which always supported 1:1.
+ minAspectRatio = owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q
+ ? 0
+ : (mCallback != null && mCallback.hasFeature(FEATURE_WATCH))
+ ? DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH
+ : DEFAULT_PRE_Q_MIN_ASPECT_RATIO;
+ }
+
+ for (Activity activity : owner.activities) {
+ if (activity.hasMinAspectRatio()) {
+ continue;
+ }
+ activity.setMinAspectRatio(minAspectRatio);
+ }
+ }
+
+ /**
+ * @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.
+ */
+ @TestApi
+ 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.maxAspectRatio = target.info.maxAspectRatio;
+ info.minAspectRatio = target.info.minAspectRatio;
+ 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;
+ }
+
+ EncodedKeySpec keySpec;
+ try {
+ final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT);
+ keySpec = new X509EncodedKeySpec(encoded);
+ } 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;
+ }
+ }
+
+ /** 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;
+ }
+
+ /**
+ * 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(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;
+ }
+
+ 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
+ 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
+ public ArraySet<String> mUpgradeKeySets;
+ @UnsupportedAppUsage
+ 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);
+ ai.resourceDirs = state.getAllOverlayPaths();
+ 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.maxAspectRatio = 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.minAspectRatio = 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.sharedLibraryFiles,
+ Display.DEFAULT_DISPLAY,
+ 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;
+ }
+ }
+}
diff --git a/android/content/pm/PackageParserCacheHelper.java b/android/content/pm/PackageParserCacheHelper.java
new file mode 100644
index 0000000..8212224
--- /dev/null
+++ b/android/content/pm/PackageParserCacheHelper.java
@@ -0,0 +1,175 @@
+/*
+ * 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();
+ 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..9b8396e
--- /dev/null
+++ b/android/content/pm/PackagePartitions.java
@@ -0,0 +1,213 @@
+/*
+ * 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(flag = true, 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;
+ }
+
+ /** Represents a partition that contains application packages. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static class SystemPartition {
+ @NonNull
+ public final File folder;
+
+ @PartitionType
+ public final int type;
+
+ @Nullable
+ private final DeferredCanonicalFile mAppFolder;
+
+ @Nullable
+ private final DeferredCanonicalFile mPrivAppFolder;
+
+ @Nullable
+ private final DeferredCanonicalFile mOverlayFolder;
+
+ private SystemPartition(@NonNull File folder, @PartitionType int type,
+ boolean containsPrivApp, boolean containsOverlay) {
+ this.folder = folder;
+ this.type = type;
+ this.mAppFolder = new DeferredCanonicalFile(folder, "app");
+ this.mPrivAppFolder = containsPrivApp ?
+ new DeferredCanonicalFile(folder, "priv-app") : null;
+ this.mOverlayFolder = containsOverlay ?
+ new DeferredCanonicalFile(folder, "overlay") : null;
+ }
+
+ public SystemPartition(@NonNull SystemPartition original) {
+ this.folder = original.folder;
+ this.type = original.type;
+ this.mAppFolder = original.mAppFolder;
+ this.mPrivAppFolder = original.mPrivAppFolder;
+ this.mOverlayFolder = original.mOverlayFolder;
+ }
+
+ /**
+ * 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 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 in its priv-app folder. */
+ public boolean containsPrivApp(@NonNull File scanFile) {
+ return FileUtils.contains(mPrivAppFolder.getFile(), scanFile);
+ }
+
+ /** Returns whether the partition contains the specified file in its app folder. */
+ public boolean containsApp(@NonNull File scanFile) {
+ return FileUtils.contains(mAppFolder.getFile(), scanFile);
+ }
+
+ /** Returns whether the partition contains the specified file in its overlay folder. */
+ public boolean containsOverlay(@NonNull File scanFile) {
+ return FileUtils.contains(mOverlayFolder.getFile(), scanFile);
+ }
+
+ /** Returns whether the partition contains the specified file. */
+ public boolean containsPath(@NonNull String path) {
+ return path.startsWith(folder.getPath() + "/");
+ }
+
+ /** Returns whether the partition contains the specified file in its priv-app folder. */
+ public boolean containsPrivPath(@NonNull String path) {
+ return mPrivAppFolder != null
+ && path.startsWith(mPrivAppFolder.getFile().getPath() + "/");
+ }
+ }
+
+ /**
+ * 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;
+ private File mFile;
+ private DeferredCanonicalFile(File dir, String fileName) {
+ mFile = new File(dir, fileName);
+ mIsCanonical = false;
+ }
+
+ private File getFile() {
+ if (mIsCanonical) {
+ return mFile;
+ }
+ mIsCanonical = true;
+ try {
+ mFile = mFile.getCanonicalFile();
+ } catch (IOException ignore) {
+ // failed to look up canonical path, continue with original one
+ }
+ return mFile;
+ }
+ }
+}
diff --git a/android/content/pm/PackageStats.java b/android/content/pm/PackageStats.java
new file mode 100644
index 0000000..7c12527
--- /dev/null
+++ b/android/content/pm/PackageStats.java
@@ -0,0 +1,214 @@
+/*
+ * 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.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(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..327d1b8
--- /dev/null
+++ b/android/content/pm/PackageUserState.java
@@ -0,0 +1,636 @@
+/*
+ * 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.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 com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+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 domainVerificationStatus;
+ public int appLinkGeneration;
+ public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
+ public int installReason;
+ public @PackageManager.UninstallReason int uninstallReason;
+ public String harmfulAppWarning;
+
+ public ArraySet<String> disabledComponents;
+ public ArraySet<String> enabledComponents;
+
+ private String[] overlayPaths;
+ private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+ private String[] cachedOverlayPaths;
+
+ @Nullable
+ private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap;
+
+ @UnsupportedAppUsage
+ public PackageUserState() {
+ installed = true;
+ hidden = false;
+ suspended = false;
+ enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+ domainVerificationStatus =
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ 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;
+ domainVerificationStatus = o.domainVerificationStatus;
+ appLinkGeneration = o.appLinkGeneration;
+ categoryHint = o.categoryHint;
+ installReason = o.installReason;
+ uninstallReason = o.uninstallReason;
+ disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
+ enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
+ overlayPaths =
+ o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ if (o.sharedLibraryOverlayPaths != null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+ }
+ harmfulAppWarning = o.harmfulAppWarning;
+ if (o.componentLabelIconOverrideMap != null) {
+ this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
+ }
+ }
+
+ public String[] getOverlayPaths() {
+ return overlayPaths;
+ }
+
+ public void setOverlayPaths(String[] paths) {
+ overlayPaths = paths;
+ cachedOverlayPaths = null;
+ }
+
+ public Map<String, String[]> getSharedLibraryOverlayPaths() {
+ return sharedLibraryOverlayPaths;
+ }
+
+ public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+ if (sharedLibraryOverlayPaths == null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>();
+ }
+ sharedLibraryOverlayPaths.put(library, paths);
+ cachedOverlayPaths = null;
+ }
+
+ /**
+ * 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 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 String[] getAllOverlayPaths() {
+ if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+ return null;
+ }
+
+ if (cachedOverlayPaths != null) {
+ return cachedOverlayPaths;
+ }
+
+ final LinkedHashSet<String> paths = new LinkedHashSet<>();
+ if (overlayPaths != null) {
+ final int N = overlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(overlayPaths[i]);
+ }
+ }
+
+ if (sharedLibraryOverlayPaths != null) {
+ for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+ if (libOverlayPaths != null) {
+ final int N = libOverlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(libOverlayPaths[i]);
+ }
+ }
+ }
+ }
+
+ cachedOverlayPaths = paths.toArray(new String[0]);
+ return cachedOverlayPaths;
+ }
+
+ @Override
+ final public boolean equals(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 (domainVerificationStatus != oldState.domainVerificationStatus) {
+ return false;
+ }
+ if (appLinkGeneration != oldState.appLinkGeneration) {
+ 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;
+ }
+ 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 + domainVerificationStatus;
+ hashCode = 31 * hashCode + appLinkGeneration;
+ 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);
+ 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(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(XmlSerializer 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(XmlPullParser 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..73119e0
--- /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
+ 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..5f6befd
--- /dev/null
+++ b/android/content/pm/PermissionInfo.java
@@ -0,0 +1,687 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information you can retrieve about a particular security permission
+ * known to the system. This corresponds to information collected from the
+ * 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;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "PROTECTION_" }, value = {
+ PROTECTION_NORMAL,
+ PROTECTION_DANGEROUS,
+ PROTECTION_SIGNATURE,
+ PROTECTION_SIGNATURE_OR_SYSTEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Protection {}
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>privileged</code> value of
+ * {@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
+ @TestApi
+ 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
+ @TestApi
+ 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}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ 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
+ @TestApi
+ public static final int PROTECTION_FLAG_RETAIL_DEMO = 0x1000000;
+
+ /** @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_WELLBEING,
+ PROTECTION_FLAG_DOCUMENTER,
+ PROTECTION_FLAG_CONFIGURATOR,
+ PROTECTION_FLAG_INCIDENT_REPORT_APPROVER,
+ PROTECTION_FLAG_APP_PREDICTOR,
+ PROTECTION_FLAG_COMPANION,
+ PROTECTION_FLAG_RETAIL_DEMO,
+ })
+ @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}
+ * 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
+ */
+ @TestApi
+ @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 whitelist 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
+ @TestApi
+ 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;
+
+ /** @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
+ public static @NonNull String protectionToString(int level) {
+ String protLevel = "????";
+ switch (level & PROTECTION_MASK_BASE) {
+ case PermissionInfo.PROTECTION_DANGEROUS:
+ protLevel = "dangerous";
+ break;
+ case PermissionInfo.PROTECTION_NORMAL:
+ protLevel = "normal";
+ break;
+ case PermissionInfo.PROTECTION_SIGNATURE:
+ protLevel = "signature";
+ break;
+ case PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM:
+ protLevel = "signatureOrSystem";
+ break;
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+ protLevel += "|privileged";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ protLevel += "|development";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+ protLevel += "|appop";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_PRE23) != 0) {
+ protLevel += "|pre23";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0) {
+ protLevel += "|installer";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0) {
+ protLevel += "|verifier";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
+ protLevel += "|preinstalled";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
+ protLevel += "|setup";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
+ protLevel += "|instant";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
+ protLevel += "|runtime";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_OEM) != 0) {
+ protLevel += "|oem";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0) {
+ protLevel += "|vendorPrivileged";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0) {
+ protLevel += "|textClassifier";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 0) {
+ protLevel += "|wellbeing";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0) {
+ protLevel += "|documenter";
+ }
+ if ((level & PROTECTION_FLAG_CONFIGURATOR) != 0) {
+ protLevel += "|configurator";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) != 0) {
+ protLevel += "|incidentReportApprover";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) {
+ protLevel += "|appPredictor";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0) {
+ protLevel += "|retailDemo";
+ }
+ return protLevel;
+ }
+
+ /**
+ * @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);
+ }
+
+ /** @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);
+ }
+}
diff --git a/android/content/pm/ProcessInfo.java b/android/content/pm/ProcessInfo.java
new file mode 100644
index 0000000..d45ff98
--- /dev/null
+++ b/android/content/pm/ProcessInfo.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+
+ @Deprecated
+ public ProcessInfo(@NonNull ProcessInfo orig) {
+ this.name = orig.name;
+ this.deniedPermissions = orig.deniedPermissions;
+ this.gwpAsanMode = orig.gwpAsanMode;
+ }
+
+
+
+ // 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/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.
+ */
+ @DataClass.Generated.Member
+ public ProcessInfo(
+ @NonNull String name,
+ @Nullable ArraySet<String> deniedPermissions,
+ @ApplicationInfo.GwpAsanMode int gwpAsanMode) {
+ 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);
+
+ // 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);
+ }
+
+ @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();
+
+ 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);
+
+ // 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 = 1584555730519L,
+ codegenVersion = "1.0.15",
+ 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\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..bd909c7
--- /dev/null
+++ b/android/content/pm/RegisteredServicesCache.java
@@ -0,0 +1,827 @@
+/*
+ * 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.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+
+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 org.xmlpull.v1.XmlSerializer;
+
+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.nio.charset.StandardCharsets;
+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");
+ mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null);
+
+ // 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);
+
+ // Register for user-related events
+ IntentFilter userFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter);
+ }
+
+ 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 = new Handler(mContext.getMainLooper());
+ }
+ 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 {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(is, StandardCharsets.UTF_8.name());
+ 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;
+ }
+ String uidString = parser.getAttributeValue(null, "uid");
+ final int uid = Integer.parseInt(uidString);
+ 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();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ 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.attribute(null, "uid", Integer.toString(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).getUsers(true);
+ }
+
+ @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..4f2bf65
--- /dev/null
+++ b/android/content/pm/ResolveInfo.java
@@ -0,0 +1,529 @@
+/*
+ * 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.IntentFilter;
+import android.graphics.drawable.Drawable;
+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;
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ 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);
+ }
+
+ public ResolveInfo() {
+ targetUserId = UserHandle.USER_CURRENT;
+ }
+
+ 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;
+ 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(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;
+ 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..d3f9e24
--- /dev/null
+++ b/android/content/pm/ServiceInfo.java
@@ -0,0 +1,268 @@
+/*
+ * 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 phone call or video conference.
+ */
+ 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..da2a3d8
--- /dev/null
+++ b/android/content/pm/SharedLibraryInfo.java
@@ -0,0 +1,352 @@
+/*
+ * 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.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;
+
+/**
+ * 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 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.
+ *
+ * @hide
+ */
+ public SharedLibraryInfo(String path, String packageName, List<String> codePaths,
+ String name, long version, int type,
+ VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages,
+ List<SharedLibraryInfo> dependencies) {
+ mPath = path;
+ mPackageName = packageName;
+ mCodePaths = codePaths;
+ mName = name;
+ mVersion = version;
+ mType = type;
+ mDeclaringPackage = declaringPackage;
+ mDependentPackages = dependentPackages;
+ mDependencies = dependencies;
+ }
+
+ 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);
+ }
+
+ /**
+ * Gets the type of this library.
+ *
+ * @return The library type.
+ */
+ public @Type int getType() {
+ return mType;
+ }
+
+ /**
+ * 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
+ */
+ public List<String> getAllCodePaths() {
+ if (getPath() != null) {
+ // Builtin library.
+ ArrayList<String> list = new ArrayList<>();
+ list.add(getPath());
+ return list;
+ } else {
+ // Static or dynamic library.
+ return 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);
+ }
+
+ 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..85bf11c
--- /dev/null
+++ b/android/content/pm/ShortcutInfo.java
@@ -0,0 +1,2427 @@
+/*
+ * 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;
+
+ private 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;
+
+ /** @hide */
+ public static final int FLAG_CACHED = 1 << 14;
+
+ /** @hide */
+ public static final int FLAG_HAS_ICON_URI = 1 << 15;
+
+ /** @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_CACHED,
+ FLAG_HAS_ICON_URI,
+ })
+ @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;
+
+ 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;
+
+ 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;
+ }
+ }
+
+ /**
+ * 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#getResourcesForApplicationAsUser}.
+ *
+ * @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#getResourcesForApplicationAsUser}.
+ *
+ * @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#getResourcesForApplicationAsUser}.
+ *
+ * @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;
+ }
+ }
+
+ /**
+ * @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;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * @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. Launcher apps may use this information to
+ * categorize shortcuts.
+ *
+ * @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 londLived) {
+ mIsLongLived = londLived;
+ 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;
+ }
+
+ /** @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;
+ }
+
+ /** @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() {
+ 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() {
+ addFlags(FLAG_CACHED);
+ }
+
+ /** Return whether a shortcut is cached. */
+ public boolean isCached() {
+ return hasFlags(FLAG_CACHED);
+ }
+
+ /** 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)
+ || hasFlags(FLAG_CACHED);
+ }
+
+ /** @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();
+ }
+
+ @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);
+ }
+
+ public static final @android.annotation.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));
+
+ 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) {
+ 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;
+ }
+}
diff --git a/android/content/pm/ShortcutManager.java b/android/content/pm/ShortcutManager.java
new file mode 100644
index 0000000..35c99a1
--- /dev/null
+++ b/android/content/pm/ShortcutManager.java
@@ -0,0 +1,774 @@
+/*
+ * 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.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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * <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.
+ */
+ public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+ try {
+ return 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.
+ */
+ @NonNull
+ public List<ShortcutInfo> getDynamicShortcuts() {
+ try {
+ return 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.
+ */
+ @NonNull
+ public List<ShortcutInfo> getManifestShortcuts() {
+ try {
+ return 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.
+ */
+ @NonNull
+ public List<ShortcutInfo> getShortcuts(@ShortcutMatchFlags int matchFlags) {
+ try {
+ return 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.
+ */
+ public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+ try {
+ return 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 {
+ 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 {
+ 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 {
+ 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.
+ */
+ @NonNull
+ public List<ShortcutInfo> getPinnedShortcuts() {
+ try {
+ return 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.
+ */
+ public boolean updateShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
+ try {
+ return 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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.
+ */
+ public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut,
+ @Nullable IntentSender resultIntent) {
+ try {
+ return 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.
+ */
+ public Intent createShortcutResultIntent(@NonNull ShortcutInfo shortcut) {
+ try {
+ return 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 {
+ 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
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_APP_PREDICTIONS)
+ public List<ShareShortcutInfo> getShareTargets(@NonNull IntentFilter filter) {
+ try {
+ return 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 {
+ mService.pushDynamicShortcut(mContext.getPackageName(), shortcut, injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
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..eee91ce
--- /dev/null
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -0,0 +1,116 @@
+/*
+ * 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);
+
+ 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);
+ public abstract void uncacheShortcuts(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull List<String> shortcutIds, int userId);
+
+ /**
+ * 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..3b84ae7
--- /dev/null
+++ b/android/content/pm/Signature.java
@@ -0,0 +1,361 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.ByteArrayInputStream;
+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;
+ }
+
+ /**
+ * 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(Object obj) {
+ try {
+ if (obj != null) {
+ Signature other = (Signature)obj;
+ return this == other || Arrays.equals(mSignature, other.mSignature);
+ }
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mHaveHashCode) {
+ return mHashCode;
+ }
+ 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];
+ }
+ };
+
+ 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..851a081
--- /dev/null
+++ b/android/content/pm/SuspendDialogInfo.java
@@ -0,0 +1,456 @@
+/*
+ * 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 com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+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_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_ACTION = "buttonAction";
+
+ private final int mIconResId;
+ private final int mTitleResId;
+ private final int mDialogMessageResId;
+ private final String mDialogMessage;
+ private final int mNeutralButtonTextResId;
+ 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 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
+ * @hide
+ */
+ @StringRes
+ public int getNeutralButtonTextResId() {
+ return mNeutralButtonTextResId;
+ }
+
+ /**
+ * @return The {@link ButtonAction} that happens on tapping this button
+ * @hide
+ */
+ @ButtonAction
+ public int getNeutralButtonAction() {
+ return mNeutralButtonAction;
+ }
+
+ /**
+ * @hide
+ */
+ public void saveToXml(XmlSerializer out) throws IOException {
+ if (mIconResId != ID_NULL) {
+ XmlUtils.writeIntAttribute(out, XML_ATTR_ICON_RES_ID, mIconResId);
+ }
+ if (mTitleResId != ID_NULL) {
+ XmlUtils.writeIntAttribute(out, XML_ATTR_TITLE_RES_ID, mTitleResId);
+ }
+ if (mDialogMessageResId != ID_NULL) {
+ XmlUtils.writeIntAttribute(out, XML_ATTR_DIALOG_MESSAGE_RES_ID, mDialogMessageResId);
+ } else {
+ XmlUtils.writeStringAttribute(out, XML_ATTR_DIALOG_MESSAGE, mDialogMessage);
+ }
+ if (mNeutralButtonTextResId != ID_NULL) {
+ XmlUtils.writeIntAttribute(out, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId);
+ }
+ XmlUtils.writeIntAttribute(out, XML_ATTR_BUTTON_ACTION, mNeutralButtonAction);
+ }
+
+ /**
+ * @hide
+ */
+ public static SuspendDialogInfo restoreFromXml(XmlPullParser in) {
+ final SuspendDialogInfo.Builder dialogInfoBuilder = new SuspendDialogInfo.Builder();
+ try {
+ final int iconId = XmlUtils.readIntAttribute(in, XML_ATTR_ICON_RES_ID, ID_NULL);
+ final int titleId = XmlUtils.readIntAttribute(in, XML_ATTR_TITLE_RES_ID, ID_NULL);
+ final int buttonTextId = XmlUtils.readIntAttribute(in, XML_ATTR_BUTTON_TEXT_RES_ID,
+ ID_NULL);
+ final int buttonAction = XmlUtils.readIntAttribute(in, XML_ATTR_BUTTON_ACTION,
+ BUTTON_ACTION_MORE_DETAILS);
+ final int dialogMessageResId = XmlUtils.readIntAttribute(
+ in, 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);
+ }
+ if (buttonTextId != ID_NULL) {
+ dialogInfoBuilder.setNeutralButtonText(buttonTextId);
+ }
+ 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 + mNeutralButtonTextResId;
+ 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
+ && mDialogMessageResId == otherDialogInfo.mDialogMessageResId
+ && mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId
+ && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction
+ && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage);
+ }
+
+ @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(" ");
+ }
+ if (mNeutralButtonTextResId != ID_NULL) {
+ builder.append("mNeutralButtonTextResId = 0x");
+ builder.append(Integer.toHexString(mNeutralButtonTextResId));
+ 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.writeInt(mDialogMessageResId);
+ dest.writeString(mDialogMessage);
+ dest.writeInt(mNeutralButtonTextResId);
+ dest.writeInt(mNeutralButtonAction);
+ }
+
+ private SuspendDialogInfo(Parcel source) {
+ mIconResId = source.readInt();
+ mTitleResId = source.readInt();
+ mDialogMessageResId = source.readInt();
+ mDialogMessage = source.readString();
+ mNeutralButtonTextResId = source.readInt();
+ mNeutralButtonAction = source.readInt();
+ }
+
+ SuspendDialogInfo(Builder b) {
+ mIconResId = b.mIconResId;
+ mTitleResId = b.mTitleResId;
+ mDialogMessageResId = b.mDialogMessageResId;
+ mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null;
+ mNeutralButtonTextResId = b.mNeutralButtonTextResId;
+ 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 int mIconResId = ID_NULL;
+ private int mNeutralButtonTextResId = ID_NULL;
+ 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 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 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/UserInfo.java b/android/content/pm/UserInfo.java
new file mode 100644
index 0000000..aca5b45
--- /dev/null
+++ b/android/content/pm/UserInfo.java
@@ -0,0 +1,504 @@
+/*
+ * 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.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
+ */
+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;
+
+ /**
+ * 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);
+ }
+
+ @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 (isEphemeral() && !isEnabled()) {
+ // Don't support switching to an ephemeral user 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;
+ 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)" : "")
+ + (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..f29aaec
--- /dev/null
+++ b/android/content/pm/VerifierDeviceIdentity.java
@@ -0,0 +1,243 @@
+/*
+ * 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.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(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..5f88555
--- /dev/null
+++ b/android/content/pm/VerifierInfo.java
@@ -0,0 +1,85 @@
+/*
+ * 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.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
+ 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..2987557
--- /dev/null
+++ b/android/content/pm/VersionedPackage.java
@@ -0,0 +1,134 @@
+/*
+ * 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.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(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..5dce839
--- /dev/null
+++ b/android/content/pm/XmlSerializerAndParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/** @hide */
+public interface XmlSerializerAndParser<T> {
+ @UnsupportedAppUsage
+ void writeAsXml(T item, XmlSerializer out) throws IOException;
+ @UnsupportedAppUsage
+ T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException;
+}
diff --git a/android/content/pm/dex/ArtManager.java b/android/content/pm/dex/ArtManager.java
new file mode 100644
index 0000000..b0970f4
--- /dev/null
+++ b/android/content/pm/dex/ArtManager.java
@@ -0,0 +1,215 @@
+/**
+ * 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 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..62ab9e0
--- /dev/null
+++ b/android/content/pm/dex/ArtManagerInternal.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dex;
+
+import android.content.pm.ApplicationInfo;
+
+/**
+ * Art manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class ArtManagerInternal {
+
+ /**
+ * Return optimization information about the application {@code info} when
+ * in executes using the specified {@code abi}.
+ */
+ public abstract PackageOptimizationInfo getPackageOptimizationInfo(
+ ApplicationInfo info, String abi);
+}
diff --git a/android/content/pm/dex/DexMetadataHelper.java b/android/content/pm/dex/DexMetadataHelper.java
new file mode 100644
index 0000000..cdb1d22
--- /dev/null
+++ b/android/content/pm/dex/DexMetadataHelper.java
@@ -0,0 +1,207 @@
+/**
+ * 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.PackageParser.APK_FILE_EXTENSION;
+
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.PackageParserException;
+import android.util.ArrayMap;
+import android.util.jar.StrictJarFile;
+
+import java.io.File;
+import java.io.IOException;
+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;
+
+/**
+ * Helper class used to compute and validate the location of dex metadata files.
+ *
+ * @hide
+ */
+public class DexMetadataHelper {
+ 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);
+ }
+
+ /**
+ * 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.getAllCodePaths());
+ }
+
+ /**
+ * 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 (!PackageParser.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 PackageParser.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 sanity validation that the file is a zip archive.
+ *
+ * @throws PackageParserException if the file is not a .dm file.
+ */
+ public static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
+ StrictJarFile jarFile = null;
+ try {
+ jarFile = new StrictJarFile(dmaPath, false, false);
+ } 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) {
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 sanity 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 (PackageParser.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/parsing/ApkLiteParseUtils.java b/android/content/pm/parsing/ApkLiteParseUtils.java
new file mode 100644
index 0000000..2f416a2
--- /dev/null
+++ b/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -0,0 +1,465 @@
+/*
+ * 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.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.VerifierInfo;
+import android.content.res.ApkAssets;
+import android.content.res.XmlResourceParser;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.R;
+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.List;
+
+/** @hide */
+public class ApkLiteParseUtils {
+
+ private static final String TAG = ParsingPackageUtils.TAG;
+
+ // TODO(b/135203078): Consolidate constants
+ private static final int DEFAULT_MIN_SDK_VERSION = 1;
+ private static final int DEFAULT_TARGET_SDK_VERSION = 0;
+
+ private static final int PARSE_DEFAULT_INSTALL_LOCATION =
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+
+ /**
+ * 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 sanity 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 PackageParser.PackageLite parsePackageLite(File packageFile, int flags)
+ throws PackageParser.PackageParserException {
+ if (packageFile.isDirectory()) {
+ return parseClusterPackageLite(packageFile, flags);
+ } else {
+ return parseMonolithicPackageLite(packageFile, flags);
+ }
+ }
+
+ public static PackageParser.PackageLite parseMonolithicPackageLite(File packageFile, int flags)
+ throws PackageParser.PackageParserException {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ final PackageParser.ApkLite baseApk = parseApkLite(packageFile, flags);
+ final String packagePath = packageFile.getAbsolutePath();
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ return new PackageParser.PackageLite(packagePath, baseApk, null, null, null, null,
+ null, null);
+ }
+
+ public static PackageParser.PackageLite parseClusterPackageLite(File packageDir, int flags)
+ throws PackageParser.PackageParserException {
+ final File[] files = packageDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new PackageParser.PackageParserException(
+ 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(files[0], flags);
+ }
+
+ String packageName = null;
+ int versionCode = 0;
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ final ArrayMap<String, PackageParser.ApkLite> apks = new ArrayMap<>();
+ for (File file : files) {
+ if (PackageParser.isApkFile(file)) {
+ final PackageParser.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 PackageParser.PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent package " + lite.packageName + " in " + file
+ + "; expected " + packageName);
+ }
+ if (versionCode != lite.versionCode) {
+ throw new PackageParser.PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent version " + lite.versionCode + " in " + file
+ + "; expected " + versionCode);
+ }
+ }
+
+ // Assert that each split is defined only oncuses-static-libe
+ if (apks.put(lite.splitName, lite) != null) {
+ throw new PackageParser.PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Split name " + lite.splitName
+ + " defined more than once; most recent was " + file);
+ }
+ }
+ }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+ final PackageParser.ApkLite baseApk = apks.remove(null);
+ if (baseApk == null) {
+ throw new PackageParser.PackageParserException(
+ PackageManager.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;
+ 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, PackageParser.sSplitNameComparator);
+
+ for (int i = 0; i < size; i++) {
+ final PackageParser.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 PackageParser.PackageLite(codePath, baseApk, splitNames, isFeatureSplits,
+ usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes);
+ }
+
+ /**
+ * 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 PackageParser#PARSE_COLLECT_CERTIFICATES}
+ */
+ public static PackageParser.ApkLite parseApkLite(File apkFile, int flags)
+ throws PackageParser.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 PackageParser#PARSE_COLLECT_CERTIFICATES}
+ */
+ public static PackageParser.ApkLite parseApkLite(FileDescriptor fd, String debugPathName,
+ int flags) throws PackageParser.PackageParserException {
+ return parseApkLiteInner(null, fd, debugPathName, flags);
+ }
+
+ private static PackageParser.ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd,
+ String debugPathName, int flags) throws PackageParser.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 PackageParser.PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+ "Failed to parse " + apkPath, e);
+ }
+
+ parser = apkAssets.openXml(PackageParser.ANDROID_MANIFEST_FILENAME);
+
+ final PackageParser.SigningDetails signingDetails;
+ if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
+ final boolean skipVerify = (flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ signingDetails = ParsingPackageUtils.collectCertificates(apkFile.getAbsolutePath(),
+ skipVerify, false, PackageParser.SigningDetails.UNKNOWN,
+ DEFAULT_TARGET_SDK_VERSION);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ } else {
+ signingDetails = PackageParser.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 PackageParser.PackageParserException(
+ 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 PackageParser.ApkLite parseApkLite(
+ String codePath, XmlPullParser parser, AttributeSet attrs,
+ PackageParser.SigningDetails signingDetails)
+ throws IOException, XmlPullParserException, PackageParser.PackageParserException {
+ final Pair<String, String> packageSplit = PackageParser.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 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;
+
+ String requiredSystemPropertyName = null;
+ String requiredSystemPropertyValue = null;
+
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ final String attr = attrs.getAttributeName(i);
+ switch (attr) {
+ case "installLocation":
+ installLocation = attrs.getAttributeIntValue(i,
+ PARSE_DEFAULT_INSTALL_LOCATION);
+ break;
+ case "versionCode":
+ versionCode = attrs.getAttributeIntValue(i, 0);
+ break;
+ case "versionCodeMajor":
+ versionCodeMajor = attrs.getAttributeIntValue(i, 0);
+ break;
+ case "revisionCode":
+ revisionCode = attrs.getAttributeIntValue(i, 0);
+ break;
+ case "coreApp":
+ coreApp = attrs.getAttributeBooleanValue(i, false);
+ break;
+ case "isolatedSplits":
+ isolatedSplits = attrs.getAttributeBooleanValue(i, false);
+ break;
+ case "configForSplit":
+ configForSplit = attrs.getAttributeValue(i);
+ break;
+ case "isFeatureSplit":
+ isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
+ break;
+ case "isSplitRequired":
+ isSplitRequired = attrs.getAttributeBooleanValue(i, false);
+ break;
+ }
+ }
+
+ // 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 (PackageParser.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+ final VerifierInfo verifier = parseVerifier(attrs);
+ if (verifier != null) {
+ verifiers.add(verifier);
+ }
+ } else if (PackageParser.TAG_APPLICATION.equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ switch (attr) {
+ case "debuggable":
+ debuggable = attrs.getAttributeBooleanValue(i, false);
+ break;
+ case "multiArch":
+ multiArch = attrs.getAttributeBooleanValue(i, false);
+ break;
+ case "use32bitAbi":
+ use32bitAbi = attrs.getAttributeBooleanValue(i, false);
+ break;
+ case "extractNativeLibs":
+ extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
+ break;
+ case "useEmbeddedDex":
+ useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
+ break;
+ }
+ }
+ } 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 (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) {
+ if (usesSplitName != null) {
+ Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
+ continue;
+ }
+
+ usesSplitName = attrs.getAttributeValue(PackageParser.ANDROID_RESOURCES, "name");
+ if (usesSplitName == null) {
+ throw new PackageParser.PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<uses-split> tag requires 'android:name' attribute");
+ }
+ } else if (PackageParser.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 (!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 new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second,
+ isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode,
+ versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
+ coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs,
+ isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion,
+ targetSdkVersion);
+ }
+
+ public 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 R.attr.name:
+ packageName = attrs.getAttributeValue(i);
+ break;
+
+ case 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 = PackageParser.parsePublicKey(encodedPublicKey);
+ if (publicKey == null) {
+ Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
+ return null;
+ }
+
+ return new VerifierInfo(packageName, publicKey);
+ }
+}
diff --git a/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
new file mode 100644
index 0000000..216b3bb
--- /dev/null
+++ b/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -0,0 +1,772 @@
+/*
+ * 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.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.parsing.component.ComponentParseUtils;
+import android.content.pm.parsing.component.ParsedActivity;
+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.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.Set;
+
+/** @hide **/
+public class PackageInfoWithoutStateUtils {
+
+ @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);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ size = pkg.getRequestedPermissions().size();
+ if (size > 0) {
+ pi.requestedPermissions = new String[size];
+ pi.requestedPermissionsFlags = new int[size];
+ for (int i = 0; i < size; i++) {
+ final String perm = pkg.getRequestedPermissions().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;
+ }
+
+ 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);
+ }
+
+ /**
+ * This bypasses critical checks that are necessary for usage with data passed outside of
+ * system server.
+ *
+ * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}.
+ */
+ @NonNull
+ public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg,
+ @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId) {
+ // Make shallow copy so we can store the metadata/libraries safely
+ ApplicationInfo ai = pkg.toAppInfoWithoutState();
+ // Init handles data directories
+ // TODO(b/135203078): Consolidate the data directory logic, remove initForUser
+ ai.initForUser(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 (!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);
+ ai.resourceDirs = state.getAllOverlayPaths();
+
+ 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, 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,
+ @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.maxAspectRatio = maxAspectRatio != null ? maxAspectRatio : 0f;
+ Float minAspectRatio = a.getMinAspectRatio();
+ ai.minAspectRatio = minAspectRatio != null ? minAspectRatio : 0f;
+ ai.requestedVrComponent = a.getRequestedVrComponent();
+ ai.rotationAnimation = a.getRotationAnimation();
+ ai.colorMode = a.getColorMode();
+ ai.windowLayout = a.getWindowLayout();
+ 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, 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,
+ @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.metaData = s.getMetaData();
+ si.permission = s.getPermission();
+ si.processName = s.getProcessName();
+ si.mForegroundServiceType = s.getForegroundServiceType();
+ si.applicationInfo = applicationInfo;
+ 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();
+ pi.metaData = p.getMetaData();
+ if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+ pi.uriPermissionPatterns = null;
+ }
+ 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);
+ }
+
+ @Nullable
+ public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
+ ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId) {
+ 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.getBaseCodePath();
+ ii.publicSourceDir = pkg.getBaseCodePath();
+ ii.splitNames = pkg.getSplitNames();
+ ii.splitSourceDirs = pkg.getSplitCodePaths();
+ ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
+ ii.splitDependencies = pkg.getSplitDependencies();
+ ii.dataDir = getDataDir(pkg, userId).getAbsolutePath();
+ ii.deviceProtectedDataDir = getDeviceProtectedDataDir(pkg, userId).getAbsolutePath();
+ ii.credentialProtectedDataDir = getCredentialProtectedDataDir(pkg,
+ userId).getAbsolutePath();
+
+ 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();
+
+ 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;
+ }
+
+ 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();
+ }
+
+ 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;
+ }
+
+ 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());
+ }
+}
diff --git a/android/content/pm/parsing/ParsingPackage.java b/android/content/pm/parsing/ParsingPackage.java
new file mode 100644
index 0000000..2ee0ad6
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackage.java
@@ -0,0 +1,342 @@
+/*
+ * 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.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+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.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);
+
+ ParsingPackage addProtectedBroadcast(String protectedBroadcast);
+
+ ParsingPackage addProvider(ParsedProvider parsedProvider);
+
+ ParsingPackage addAttribution(ParsedAttribution attribution);
+
+ ParsingPackage addReceiver(ParsedActivity parsedReceiver);
+
+ ParsingPackage addReqFeature(FeatureInfo reqFeature);
+
+ ParsingPackage addRequestedPermission(String permission);
+
+ ParsingPackage addService(ParsedService parsedService);
+
+ ParsingPackage addUsesLibrary(String libraryName);
+
+ ParsingPackage addUsesOptionalLibrary(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 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 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(int gwpAsanMode);
+
+ ParsingPackage setCrossProfile(boolean crossProfile);
+
+ ParsingPackage setFullBackupContent(int fullBackupContent);
+
+ 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 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);
+
+ // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
+ // for moving to the next step
+ @Deprecated
+ Object hideAsParsed();
+}
diff --git a/android/content/pm/parsing/ParsingPackageImpl.java b/android/content/pm/parsing/ParsingPackageImpl.java
new file mode 100644
index 0000000..f932bc2
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackageImpl.java
@@ -0,0 +1,2632 @@
+/*
+ * 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.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.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.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;
+
+/**
+ * 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);
+ 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 baseCodePath;
+
+ private boolean requiredForAllUsers;
+ @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;
+ private boolean overlayIsStatic;
+ @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)
+ 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();
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedStringList.class)
+ private List<String> requestedPermissions = 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;
+
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ protected String volumeUuid;
+ @Nullable
+ private PackageParser.SigningDetails signingDetails;
+
+ @NonNull
+ @DataClass.ParcelWith(ForInternedString.class)
+ protected String codePath;
+
+ private boolean use32BitAbi;
+ private boolean visibleToInstantApps;
+
+ private boolean forceQueryable;
+
+ @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;
+
+ // Usually there's code to set this 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 boolean enabled = true;
+
+ private boolean crossProfile;
+ private int fullBackupContent;
+ private int iconRes;
+ private int installLocation = PackageParser.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;
+ 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;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String taskAffinity;
+ private int theme;
+
+ private int uiOptions;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String zygotePreloadName;
+
+ private boolean externalStorage;
+ private boolean baseHardwareAccelerated;
+ private boolean allowBackup;
+ private boolean killAfterRestore;
+ private boolean restoreAnyVersion;
+ private boolean fullBackupOnly;
+ private boolean persistent;
+ private boolean debuggable;
+ private boolean vmSafeMode;
+ private boolean hasCode;
+ private boolean allowTaskReparenting;
+ private boolean allowClearUserData;
+ private boolean largeHeap;
+ private boolean usesCleartextTraffic;
+ private boolean supportsRtl;
+ private boolean testOnly;
+ private boolean multiArch;
+ private boolean extractNativeLibs;
+ private boolean game;
+
+ /**
+ * @see ParsingPackageRead#getResizeableActivity()
+ */
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean resizeableActivity;
+
+ private boolean staticSharedLibrary;
+ private boolean overlay;
+ private boolean isolatedSplitLoading;
+ private boolean hasDomainUrls;
+ private boolean profileableByShell;
+ private boolean backupInForeground;
+ private boolean useEmbeddedDex;
+ private boolean defaultToDeviceProtectedStorage;
+ private boolean directBootAware;
+ private boolean partiallyDirectBootAware;
+ private boolean resizeableActivityViaSdkVersion;
+ private boolean allowClearUserDataOnFailedRestore;
+ private boolean allowAudioPlaybackCapture;
+ private boolean requestLegacyExternalStorage;
+ private boolean usesNonSdkApi;
+ private boolean hasFragileUserData;
+ private boolean cantSaveState;
+ private boolean allowNativeHeapPointerTagging;
+ private int autoRevokePermissions;
+ private boolean preserveLegacyExternalStorage;
+
+ protected int gwpAsanMode;
+
+ // TODO(chiuwinson): Non-null
+ @Nullable
+ private ArraySet<String> mimeGroups;
+
+ @VisibleForTesting
+ public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseCodePath,
+ @NonNull String codePath, @Nullable TypedArray manifestArray) {
+ this.packageName = TextUtils.safeIntern(packageName);
+ this.baseCodePath = baseCodePath;
+ this.codePath = codePath;
+
+ 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;
+ }
+
+ @Override
+ public Object hideAsParsed() {
+ // There is no equivalent for core-only parsing
+ throw new UnsupportedOperationException();
+ }
+
+ @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 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 addRequestedPermission(String permission) {
+ this.requestedPermissions = CollectionUtils.add(this.requestedPermissions,
+ TextUtils.safeIntern(permission));
+ 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 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;
+ }
+
+ @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);
+ return appInfo;
+ }
+
+ @Override
+ public ApplicationInfo toAppInfoWithoutStateWithoutFlags() {
+ ApplicationInfo appInfo = new ApplicationInfo();
+
+ 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 = credentialProtectedDataDir;
+// appInfo.dataDir = dataDir;
+ appInfo.descriptionRes = descriptionRes;
+// appInfo.deviceProtectedDataDir = deviceProtectedDataDir;
+ appInfo.enabled = enabled;
+ appInfo.fullBackupContent = fullBackupContent;
+// appInfo.hiddenUntilInstalled = hiddenUntilInstalled;
+ appInfo.icon = (PackageParser.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;
+ if (appInfo.name != null) {
+ appInfo.name = appInfo.name.trim();
+ }
+// appInfo.nativeLibraryDir = nativeLibraryDir;
+// appInfo.nativeLibraryRootDir = nativeLibraryRootDir;
+// appInfo.nativeLibraryRootRequiresIsa = nativeLibraryRootRequiresIsa;
+ appInfo.networkSecurityConfigRes = networkSecurityConfigRes;
+ appInfo.nonLocalizedLabel = nonLocalizedLabel;
+ if (appInfo.nonLocalizedLabel != null) {
+ appInfo.nonLocalizedLabel = appInfo.nonLocalizedLabel.toString().trim();
+ }
+ appInfo.packageName = packageName;
+ appInfo.permission = permission;
+// appInfo.primaryCpuAbi = primaryCpuAbi;
+ appInfo.processName = getProcessName();
+ appInfo.requiresSmallestWidthDp = requiresSmallestWidthDp;
+// appInfo.secondaryCpuAbi = secondaryCpuAbi;
+// appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
+// appInfo.seInfo = seInfo;
+// appInfo.seInfoUser = seInfoUser;
+// appInfo.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+// ? null : usesLibraryFiles.toArray(new String[0]);
+// appInfo.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+ appInfo.splitClassLoaderNames = splitClassLoaderNames;
+ appInfo.splitDependencies = splitDependencies;
+ appInfo.splitNames = splitNames;
+ appInfo.storageUuid = StorageManager.convert(volumeUuid);
+ appInfo.targetSandboxVersion = targetSandboxVersion;
+ appInfo.targetSdkVersion = targetSdkVersion;
+ appInfo.taskAffinity = taskAffinity;
+ appInfo.theme = theme;
+// appInfo.uid = uid;
+ appInfo.uiOptions = uiOptions;
+ appInfo.volumeUuid = volumeUuid;
+ appInfo.zygotePreloadName = zygotePreloadName;
+ appInfo.crossProfile = isCrossProfile();
+ appInfo.setGwpAsanMode(gwpAsanMode);
+ appInfo.setBaseCodePath(baseCodePath);
+ appInfo.setBaseResourcePath(baseCodePath);
+ appInfo.setCodePath(codePath);
+ appInfo.setResourcePath(codePath);
+ appInfo.setSplitCodePaths(splitCodePaths);
+ appInfo.setSplitResourcePaths(splitCodePaths);
+ appInfo.setVersionCode(PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode));
+
+ // TODO(b/135203078): Can this be removed? Looks only used in ActivityInfo.
+// appInfo.showUserIcon = pkg.getShowUserIcon();
+ // TODO(b/135203078): Unused?
+// appInfo.resourceDirs = pkg.getResourceDirs();
+ // TODO(b/135203078): Unused?
+// appInfo.enabledSetting = pkg.getEnabledSetting();
+ // TODO(b/135203078): See ParsingPackageImpl#getHiddenApiEnforcementPolicy
+// appInfo.mHiddenApiPolicy = pkg.getHiddenApiPolicy();
+
+ 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.baseCodePath);
+ dest.writeBoolean(this.requiredForAllUsers);
+ 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);
+ dest.writeBoolean(this.overlayIsStatic);
+ 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.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);
+ sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
+ sForStringSet.parcel(this.upgradeKeySets, dest, flags);
+ dest.writeMap(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.codePath);
+ dest.writeBoolean(this.use32BitAbi);
+ dest.writeBoolean(this.visibleToInstantApps);
+ dest.writeBoolean(this.forceQueryable);
+ dest.writeParcelableList(this.queriesIntents, flags);
+ sForInternedStringList.parcel(this.queriesPackages, 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.writeBoolean(this.enabled);
+ dest.writeBoolean(this.crossProfile);
+ dest.writeInt(this.fullBackupContent);
+ 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);
+
+ dest.writeBoolean(this.externalStorage);
+ dest.writeBoolean(this.baseHardwareAccelerated);
+ dest.writeBoolean(this.allowBackup);
+ dest.writeBoolean(this.killAfterRestore);
+ dest.writeBoolean(this.restoreAnyVersion);
+ dest.writeBoolean(this.fullBackupOnly);
+ dest.writeBoolean(this.persistent);
+ dest.writeBoolean(this.debuggable);
+ dest.writeBoolean(this.vmSafeMode);
+ dest.writeBoolean(this.hasCode);
+ dest.writeBoolean(this.allowTaskReparenting);
+ dest.writeBoolean(this.allowClearUserData);
+ dest.writeBoolean(this.largeHeap);
+ dest.writeBoolean(this.usesCleartextTraffic);
+ dest.writeBoolean(this.supportsRtl);
+ dest.writeBoolean(this.testOnly);
+ dest.writeBoolean(this.multiArch);
+ dest.writeBoolean(this.extractNativeLibs);
+ dest.writeBoolean(this.game);
+
+ sForBoolean.parcel(this.resizeableActivity, dest, flags);
+
+ dest.writeBoolean(this.staticSharedLibrary);
+ dest.writeBoolean(this.overlay);
+ dest.writeBoolean(this.isolatedSplitLoading);
+ dest.writeBoolean(this.hasDomainUrls);
+ dest.writeBoolean(this.profileableByShell);
+ dest.writeBoolean(this.backupInForeground);
+ dest.writeBoolean(this.useEmbeddedDex);
+ dest.writeBoolean(this.defaultToDeviceProtectedStorage);
+ dest.writeBoolean(this.directBootAware);
+ dest.writeBoolean(this.partiallyDirectBootAware);
+ dest.writeBoolean(this.resizeableActivityViaSdkVersion);
+ dest.writeBoolean(this.allowClearUserDataOnFailedRestore);
+ dest.writeBoolean(this.allowAudioPlaybackCapture);
+ dest.writeBoolean(this.requestLegacyExternalStorage);
+ dest.writeBoolean(this.usesNonSdkApi);
+ dest.writeBoolean(this.hasFragileUserData);
+ dest.writeBoolean(this.cantSaveState);
+ dest.writeBoolean(this.allowNativeHeapPointerTagging);
+ dest.writeInt(this.autoRevokePermissions);
+ dest.writeBoolean(this.preserveLegacyExternalStorage);
+ dest.writeArraySet(this.mimeGroups);
+ dest.writeInt(this.gwpAsanMode);
+ dest.writeSparseIntArray(this.minExtensionVersions);
+ }
+
+ 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.baseCodePath = in.readString();
+ this.requiredForAllUsers = in.readBoolean();
+ 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.overlayIsStatic = in.readBoolean();
+ 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.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.implicitPermissions = sForInternedStringList.unparcel(in);
+ this.upgradeKeySets = sForStringSet.unparcel(in);
+ this.keySetMapping = in.readHashMap(boot);
+ 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.codePath = in.readString();
+ this.use32BitAbi = in.readBoolean();
+ this.visibleToInstantApps = in.readBoolean();
+ this.forceQueryable = in.readBoolean();
+ this.queriesIntents = in.createTypedArrayList(Intent.CREATOR);
+ this.queriesPackages = sForInternedStringList.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.enabled = in.readBoolean();
+ this.crossProfile = in.readBoolean();
+ this.fullBackupContent = 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.externalStorage = in.readBoolean();
+ this.baseHardwareAccelerated = in.readBoolean();
+ this.allowBackup = in.readBoolean();
+ this.killAfterRestore = in.readBoolean();
+ this.restoreAnyVersion = in.readBoolean();
+ this.fullBackupOnly = in.readBoolean();
+ this.persistent = in.readBoolean();
+ this.debuggable = in.readBoolean();
+ this.vmSafeMode = in.readBoolean();
+ this.hasCode = in.readBoolean();
+ this.allowTaskReparenting = in.readBoolean();
+ this.allowClearUserData = in.readBoolean();
+ this.largeHeap = in.readBoolean();
+ this.usesCleartextTraffic = in.readBoolean();
+ this.supportsRtl = in.readBoolean();
+ this.testOnly = in.readBoolean();
+ this.multiArch = in.readBoolean();
+ this.extractNativeLibs = in.readBoolean();
+ this.game = in.readBoolean();
+ this.resizeableActivity = sForBoolean.unparcel(in);
+
+ this.staticSharedLibrary = in.readBoolean();
+ this.overlay = in.readBoolean();
+ this.isolatedSplitLoading = in.readBoolean();
+ this.hasDomainUrls = in.readBoolean();
+ this.profileableByShell = in.readBoolean();
+ this.backupInForeground = in.readBoolean();
+ this.useEmbeddedDex = in.readBoolean();
+ this.defaultToDeviceProtectedStorage = in.readBoolean();
+ this.directBootAware = in.readBoolean();
+ this.partiallyDirectBootAware = in.readBoolean();
+ this.resizeableActivityViaSdkVersion = in.readBoolean();
+ this.allowClearUserDataOnFailedRestore = in.readBoolean();
+ this.allowAudioPlaybackCapture = in.readBoolean();
+ this.requestLegacyExternalStorage = in.readBoolean();
+ this.usesNonSdkApi = in.readBoolean();
+ this.hasFragileUserData = in.readBoolean();
+ this.cantSaveState = in.readBoolean();
+ this.allowNativeHeapPointerTagging = in.readBoolean();
+ this.autoRevokePermissions = in.readInt();
+ this.preserveLegacyExternalStorage = in.readBoolean();
+ this.mimeGroups = (ArraySet<String>) in.readArraySet(boot);
+ this.gwpAsanMode = in.readInt();
+ this.minExtensionVersions = in.readSparseIntArray();
+ }
+
+ 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 getBaseCodePath() {
+ return baseCodePath;
+ }
+
+ @Override
+ public boolean isRequiredForAllUsers() {
+ return requiredForAllUsers;
+ }
+
+ @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 overlayIsStatic;
+ }
+
+ @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> 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;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getRequestedPermissions() {
+ return requestedPermissions;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getImplicitPermissions() {
+ return implicitPermissions;
+ }
+
+ @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 getCodePath() {
+ return codePath;
+ }
+
+ @Override
+ public boolean isUse32BitAbi() {
+ return use32BitAbi;
+ }
+
+ @Override
+ public boolean isVisibleToInstantApps() {
+ return visibleToInstantApps;
+ }
+
+ @Override
+ public boolean isForceQueryable() {
+ return forceQueryable;
+ }
+
+ @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 enabled;
+ }
+
+ @Override
+ public boolean isCrossProfile() {
+ return crossProfile;
+ }
+
+ @Override
+ public int getFullBackupContent() {
+ return fullBackupContent;
+ }
+
+ @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 externalStorage;
+ }
+
+ @Override
+ public boolean isBaseHardwareAccelerated() {
+ return baseHardwareAccelerated;
+ }
+
+ @Override
+ public boolean isAllowBackup() {
+ return allowBackup;
+ }
+
+ @Override
+ public boolean isKillAfterRestore() {
+ return killAfterRestore;
+ }
+
+ @Override
+ public boolean isRestoreAnyVersion() {
+ return restoreAnyVersion;
+ }
+
+ @Override
+ public boolean isFullBackupOnly() {
+ return fullBackupOnly;
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return persistent;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return debuggable;
+ }
+
+ @Override
+ public boolean isVmSafeMode() {
+ return vmSafeMode;
+ }
+
+ @Override
+ public boolean isHasCode() {
+ return hasCode;
+ }
+
+ @Override
+ public boolean isAllowTaskReparenting() {
+ return allowTaskReparenting;
+ }
+
+ @Override
+ public boolean isAllowClearUserData() {
+ return allowClearUserData;
+ }
+
+ @Override
+ public boolean isLargeHeap() {
+ return largeHeap;
+ }
+
+ @Override
+ public boolean isUsesCleartextTraffic() {
+ return usesCleartextTraffic;
+ }
+
+ @Override
+ public boolean isSupportsRtl() {
+ return supportsRtl;
+ }
+
+ @Override
+ public boolean isTestOnly() {
+ return testOnly;
+ }
+
+ @Override
+ public boolean isMultiArch() {
+ return multiArch;
+ }
+
+ @Override
+ public boolean isExtractNativeLibs() {
+ return extractNativeLibs;
+ }
+
+ @Override
+ public boolean isGame() {
+ return game;
+ }
+
+ /**
+ * @see ParsingPackageRead#getResizeableActivity()
+ */
+ @Nullable
+ @Override
+ public Boolean getResizeableActivity() {
+ return resizeableActivity;
+ }
+
+ @Override
+ public boolean isStaticSharedLibrary() {
+ return staticSharedLibrary;
+ }
+
+ @Override
+ public boolean isOverlay() {
+ return overlay;
+ }
+
+ @Override
+ public boolean isIsolatedSplitLoading() {
+ return isolatedSplitLoading;
+ }
+
+ @Override
+ public boolean isHasDomainUrls() {
+ return hasDomainUrls;
+ }
+
+ @Override
+ public boolean isProfileableByShell() {
+ return profileableByShell;
+ }
+
+ @Override
+ public boolean isBackupInForeground() {
+ return backupInForeground;
+ }
+
+ @Override
+ public boolean isUseEmbeddedDex() {
+ return useEmbeddedDex;
+ }
+
+ @Override
+ public boolean isDefaultToDeviceProtectedStorage() {
+ return defaultToDeviceProtectedStorage;
+ }
+
+ @Override
+ public boolean isDirectBootAware() {
+ return directBootAware;
+ }
+
+ @Override
+ public int getGwpAsanMode() {
+ return gwpAsanMode;
+ }
+
+ @Override
+ public boolean isPartiallyDirectBootAware() {
+ return partiallyDirectBootAware;
+ }
+
+ @Override
+ public boolean isResizeableActivityViaSdkVersion() {
+ return resizeableActivityViaSdkVersion;
+ }
+
+ @Override
+ public boolean isAllowClearUserDataOnFailedRestore() {
+ return allowClearUserDataOnFailedRestore;
+ }
+
+ @Override
+ public boolean isAllowAudioPlaybackCapture() {
+ return allowAudioPlaybackCapture;
+ }
+
+ @Override
+ public boolean isRequestLegacyExternalStorage() {
+ return requestLegacyExternalStorage;
+ }
+
+ @Override
+ public boolean isUsesNonSdkApi() {
+ return usesNonSdkApi;
+ }
+
+ @Override
+ public boolean isHasFragileUserData() {
+ return hasFragileUserData;
+ }
+
+ @Override
+ public boolean isCantSaveState() {
+ return cantSaveState;
+ }
+
+ @Override
+ public boolean isAllowNativeHeapPointerTagging() {
+ return allowNativeHeapPointerTagging;
+ }
+
+ @Override
+ public int getAutoRevokePermissions() {
+ return autoRevokePermissions;
+ }
+
+ @Override
+ public boolean hasPreserveLegacyExternalStorage() {
+ return preserveLegacyExternalStorage;
+ }
+
+ @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) {
+ requiredForAllUsers = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayPriority(int value) {
+ overlayPriority = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlayIsStatic(boolean value) {
+ overlayIsStatic = value;
+ return this;
+ }
+
+ @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) {
+ use32BitAbi = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setVisibleToInstantApps(boolean value) {
+ visibleToInstantApps = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setForceQueryable(boolean value) {
+ forceQueryable = value;
+ return this;
+ }
+
+ @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) {
+ enabled = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setCrossProfile(boolean value) {
+ crossProfile = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setFullBackupContent(int value) {
+ fullBackupContent = 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 setNonLocalizedLabel(@Nullable CharSequence value) {
+ nonLocalizedLabel = 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 setUiOptions(int value) {
+ uiOptions = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setExternalStorage(boolean value) {
+ externalStorage = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setBaseHardwareAccelerated(boolean value) {
+ baseHardwareAccelerated = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowBackup(boolean value) {
+ allowBackup = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setKillAfterRestore(boolean value) {
+ killAfterRestore = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRestoreAnyVersion(boolean value) {
+ restoreAnyVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setFullBackupOnly(boolean value) {
+ fullBackupOnly = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setPersistent(boolean value) {
+ persistent = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setDebuggable(boolean value) {
+ debuggable = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setVmSafeMode(boolean value) {
+ vmSafeMode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setHasCode(boolean value) {
+ hasCode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowTaskReparenting(boolean value) {
+ allowTaskReparenting = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowClearUserData(boolean value) {
+ allowClearUserData = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setLargeHeap(boolean value) {
+ largeHeap = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setUsesCleartextTraffic(boolean value) {
+ usesCleartextTraffic = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setSupportsRtl(boolean value) {
+ supportsRtl = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setTestOnly(boolean value) {
+ testOnly = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setMultiArch(boolean value) {
+ multiArch = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setExtractNativeLibs(boolean value) {
+ extractNativeLibs = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setGame(boolean value) {
+ game = value;
+ return this;
+ }
+
+ /**
+ * @see ParsingPackageRead#getResizeableActivity()
+ */
+ @Override
+ public ParsingPackageImpl setResizeableActivity(@Nullable Boolean value) {
+ resizeableActivity = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setStaticSharedLibrary(boolean value) {
+ staticSharedLibrary = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setOverlay(boolean value) {
+ overlay = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setIsolatedSplitLoading(boolean value) {
+ isolatedSplitLoading = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setHasDomainUrls(boolean value) {
+ hasDomainUrls = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setProfileableByShell(boolean value) {
+ profileableByShell = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setBackupInForeground(boolean value) {
+ backupInForeground = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setUseEmbeddedDex(boolean value) {
+ useEmbeddedDex = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setDefaultToDeviceProtectedStorage(boolean value) {
+ defaultToDeviceProtectedStorage = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setDirectBootAware(boolean value) {
+ directBootAware = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setGwpAsanMode(int value) {
+ gwpAsanMode = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
+ partiallyDirectBootAware = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setResizeableActivityViaSdkVersion(boolean value) {
+ resizeableActivityViaSdkVersion = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowClearUserDataOnFailedRestore(boolean value) {
+ allowClearUserDataOnFailedRestore = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowAudioPlaybackCapture(boolean value) {
+ allowAudioPlaybackCapture = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setRequestLegacyExternalStorage(boolean value) {
+ requestLegacyExternalStorage = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setUsesNonSdkApi(boolean value) {
+ usesNonSdkApi = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setHasFragileUserData(boolean value) {
+ hasFragileUserData = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setCantSaveState(boolean value) {
+ cantSaveState = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAllowNativeHeapPointerTagging(boolean value) {
+ allowNativeHeapPointerTagging = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setAutoRevokePermissions(int value) {
+ autoRevokePermissions = value;
+ return this;
+ }
+
+ @Override
+ public ParsingPackageImpl setPreserveLegacyExternalStorage(boolean value) {
+ preserveLegacyExternalStorage = value;
+ return this;
+ }
+
+ @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;
+ 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;
+ }
+}
diff --git a/android/content/pm/parsing/ParsingPackageRead.java b/android/content/pm/parsing/ParsingPackageRead.java
new file mode 100644
index 0000000..5b53c18
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackageRead.java
@@ -0,0 +1,866 @@
+/*
+ * 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.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.os.Bundle;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+
+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 PackageParser#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 PackageParser#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();
+
+ /**
+ * 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<String> getRequestedPermissions();
+
+ /**
+ * 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();
+
+ /**
+ * 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_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 getBaseCodePath();
+
+ /**
+ * 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 getCodePath();
+
+ /**
+ * @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 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 PackageParser#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
+ */
+ public int getGwpAsanMode();
+
+ // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
+ ApplicationInfo toAppInfoWithoutState();
+
+ /**
+ * same as toAppInfoWithoutState except without flag computation.
+ */
+ ApplicationInfo toAppInfoWithoutStateWithoutFlags();
+}
diff --git a/android/content/pm/parsing/ParsingPackageUtils.java b/android/content/pm/parsing/ParsingPackageUtils.java
new file mode 100644
index 0000000..aeb4db4
--- /dev/null
+++ b/android/content/pm/parsing/ParsingPackageUtils.java
@@ -0,0 +1,2768 @@
+/*
+ * 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.FEATURE_WATCH;
+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.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleableRes;
+import android.app.ActivityThread;
+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.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.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.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.permission.SplitPermissionInfoParcelable;
+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.RemoteException;
+import android.os.Trace;
+import android.os.ext.SdkExtensions;
+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.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+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 {
+
+ public static final String TAG = ParsingUtils.TAG;
+
+ /**
+ * 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> parseDefaultOneTime(File file, int flags,
+ @NonNull ParseInput.Callback inputCallback, @NonNull Callback callback) {
+ if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
+ // Caller expressed no opinion about what encryption
+ // aware/unaware components they want to see, so match both
+ flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
+ ParseInput input = new ParseTypeImpl(inputCallback).reset();
+ ParseResult<ParsingPackage> result;
+
+
+ ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, callback);
+ try {
+ result = parser.parsePackage(input, file, flags);
+ 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 ((flags & PackageManager.GET_SIGNATURES) != 0
+ || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ ParsingPackageUtils.collectCertificates(pkg, false /* skipVerify */);
+ }
+
+ 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;
+ private Callback mCallback;
+
+ public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
+ DisplayMetrics displayMetrics, @NonNull Callback callback) {
+ mOnlyCoreApps = onlyCoreApps;
+ mSeparateProcesses = separateProcesses;
+ mDisplayMetrics = displayMetrics;
+ 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 sanity 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(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 sanity 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(ParsingPackageRead, boolean)}.
+ */
+ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
+ int flags) throws PackageParserException {
+ final PackageParser.PackageLite lite = ApkLiteParseUtils.parseClusterPackageLite(packageDir,
+ 0);
+ if (mOnlyCoreApps && !lite.coreApp) {
+ 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.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+ 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 AssetManager assets = assetLoader.getBaseAssetManager();
+ final File baseApk = new File(lite.baseCodePath);
+ ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
+ lite.codePath, assets, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ ParsingPackage pkg = result.getResult();
+ if (!ArrayUtils.isEmpty(lite.splitNames)) {
+ pkg.asSplit(
+ lite.splitNames,
+ lite.splitCodePaths,
+ lite.splitRevisionCodes,
+ splitDependencies
+ );
+ final int num = lite.splitNames.length;
+
+ for (int i = 0; i < num; i++) {
+ final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+ parseSplitApk(input, pkg, i, splitAssets, flags);
+ }
+ }
+
+ pkg.setUse32BitAbi(lite.use32bitAbi);
+ return input.success(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(ParsingPackageRead, boolean)}.
+ */
+ private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
+ int flags) throws PackageParserException {
+ final PackageParser.PackageLite lite = ApkLiteParseUtils.parseMonolithicPackageLite(apkFile,
+ flags);
+ if (mOnlyCoreApps && !lite.coreApp) {
+ return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
+ "Not a coreApp: " + apkFile);
+ }
+
+ final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ try {
+ ParseResult<ParsingPackage> result = parseBaseApk(input,
+ apkFile,
+ apkFile.getCanonicalPath(),
+ assetLoader.getBaseAssetManager(), flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult()
+ .setUse32BitAbi(lite.use32bitAbi));
+ } 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, AssetManager assets, int flags) {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ String volumeUuid = null;
+ if (apkPath.startsWith(PackageParser.MNT_EXPAND)) {
+ final int end = apkPath.indexOf('/', PackageParser.MNT_EXPAND.length());
+ volumeUuid = apkPath.substring(PackageParser.MNT_EXPAND.length(), end);
+ }
+
+ if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+
+ 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,
+ PackageParser.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 = assets.getApkAssets()[0];
+ if (apkAssets.definesOverlayable()) {
+ SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.get(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 & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
+ pkg.setSigningDetails(ParsingPackageUtils.collectCertificates(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 (PackageParser.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,
+ PackageParser.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, PackageParserException {
+ final String splitName;
+ final String pkgName;
+
+ try {
+ Pair<String, String> packageSplit = PackageParser.parsePackageSplitNames(parser,
+ parser);
+ 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
+ );
+ }
+ } catch (PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME);
+ }
+
+ 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 (PackageParser.TAG_APPLICATION.equals(tagName)) {
+ if (foundApp) {
+ if (PackageParser.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, PackageParser.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,
+ PackageParser.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, PackageParser.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,
+ PackageParser.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<Bundle> metaDataResult = parseMetaData(pkg, res, parser,
+ pkg.getMetaData(), input);
+ if (metaDataResult.isSuccess()) {
+ pkg.setMetaData(metaDataResult.getResult());
+ }
+ return metaDataResult;
+ case "uses-static-library":
+ return parseUsesStaticLibrary(input, pkg, res, parser);
+ case "uses-library":
+ return parseUsesLibrary(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(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION,
+ R.styleable.AndroidManifest_installLocation, sa))
+ .setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX,
+ R.styleable.AndroidManifest_targetSandboxVersion, sa))
+ /* Set the global "on SD card" flag */
+ .setExternalStorage((flags & PackageParser.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;
+
+ // TODO(b/135203078): Convert to instance methods to share variables
+ // <application> has special logic, so it's handled outside the general method
+ if (PackageParser.TAG_APPLICATION.equals(tagName)) {
+ if (foundApp) {
+ if (PackageParser.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 <feature> 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 PackageParser.TAG_OVERLAY:
+ return parseOverlay(input, pkg, res, parser);
+ case PackageParser.TAG_KEY_SETS:
+ return parseKeySets(input, pkg, res, parser);
+ case "feature": // TODO moltmann: Remove
+ case PackageParser.TAG_ATTRIBUTION:
+ return parseAttribution(input, pkg, res, parser);
+ case PackageParser.TAG_PERMISSION_GROUP:
+ return parsePermissionGroup(input, pkg, res, parser);
+ case PackageParser.TAG_PERMISSION:
+ return parsePermission(input, pkg, res, parser);
+ case PackageParser.TAG_PERMISSION_TREE:
+ return parsePermissionTree(input, pkg, res, parser);
+ case PackageParser.TAG_USES_PERMISSION:
+ case PackageParser.TAG_USES_PERMISSION_SDK_M:
+ case PackageParser.TAG_USES_PERMISSION_SDK_23:
+ return parseUsesPermission(input, pkg, res, parser);
+ case PackageParser.TAG_USES_CONFIGURATION:
+ return parseUsesConfiguration(input, pkg, res, parser);
+ case PackageParser.TAG_USES_FEATURE:
+ return parseUsesFeature(input, pkg, res, parser);
+ case PackageParser.TAG_FEATURE_GROUP:
+ return parseFeatureGroup(input, pkg, res, parser);
+ case PackageParser.TAG_USES_SDK:
+ return parseUsesSdk(input, pkg, res, parser);
+ case PackageParser.TAG_SUPPORT_SCREENS:
+ return parseSupportScreens(input, pkg, res, parser);
+ case PackageParser.TAG_PROTECTED_BROADCAST:
+ return parseProtectedBroadcast(input, pkg, res, parser);
+ case PackageParser.TAG_INSTRUMENTATION:
+ return parseInstrumentation(input, pkg, res, parser);
+ case PackageParser.TAG_ORIGINAL_PACKAGE:
+ return parseOriginalPackage(input, pkg, res, parser);
+ case PackageParser.TAG_ADOPT_PERMISSIONS:
+ return parseAdoptPermissions(input, pkg, res, parser);
+ case PackageParser.TAG_USES_GL_TEXTURE:
+ case PackageParser.TAG_COMPATIBLE_SCREENS:
+ case PackageParser.TAG_SUPPORTS_INPUT:
+ case PackageParser.TAG_EAT_COMMENT:
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ return input.success(pkg);
+ case PackageParser.TAG_RESTRICT_UPDATE:
+ return parseRestrictUpdateHash(flags, input, pkg, res, parser);
+ case PackageParser.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, PackageParser.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, PackageParser.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, PackageParser.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 String requiredFeature = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
+
+ final String requiredNotfeature = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestUsesPermission_requiredNotFeature,
+ 0);
+
+ XmlUtils.skipCurrentTag(parser);
+
+ // 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;
+ }
+
+ // Only allow requesting this permission if the platform supports the given feature.
+ if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(
+ requiredFeature)) {
+ return success;
+ }
+
+ // Only allow requesting this permission if the platform doesn't support the given
+ // feature.
+ if (requiredNotfeature != null && mCallback != null
+ && mCallback.hasFeature(requiredNotfeature)) {
+ return success;
+ }
+
+ if (!pkg.getRequestedPermissions().contains(name)) {
+ pkg.addRequestedPermission(name.intern());
+ } else {
+ Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ }
+
+ return success;
+ } 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.getBaseCodePath() + " " +
+ 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 (PackageParser.SDK_VERSION > 0) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+ try {
+ int minVers = 1;
+ String minCode = null;
+ int targetVers = 0;
+ 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, PackageParser.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,
+ PackageParser.SDK_VERSION, PackageParser.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 & PackageParser.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 = IntentFilter.WILDCARD;
+ 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 (PackageParser.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 (PackageParser.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 (PackageParser.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));
+ }
+
+ // TODO(b/135203078): Should parsing code be responsible for this? Maybe move to a
+ // util or just have PackageImpl return true if either flag is set
+ // Debuggable implies profileable
+ pkg.setProfileableByShell(pkg.isProfileableByShell() || pkg.isDebuggable());
+
+ 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));
+ } 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, PackageParser.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, PackageParser.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, PackageParser.sUseRoundIcon, input);
+ if (providerResult.isSuccess()) {
+ pkg.addProvider(providerResult.getResult());
+ }
+
+ result = providerResult;
+ break;
+ case "activity-alias":
+ activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
+ parser, PackageParser.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);
+
+ 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))
+ // 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))
+ // 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)
+ ParseResult<Bundle> metaDataResult = parseMetaData(pkg, res, parser,
+ pkg.getMetaData(), input);
+ if (metaDataResult.isSuccess()) {
+ pkg.setMetaData(metaDataResult.getResult());
+ }
+
+ return metaDataResult;
+ 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 "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> 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 {
+ return input.success(pkg.setProfileableByShell(pkg.isProfileableByShell()
+ || bool(false, R.styleable.AndroidManifestProfileable_shell, 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
+ */
+ 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
+ ? PackageParser.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(
+ PackageParser.METADATA_MAX_ASPECT_RATIO)) {
+ maxAspectRatio = appMetaData.getFloat(PackageParser.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(PackageParser.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) {
+ final float minAspectRatio;
+ float packageMinAspectRatio = pkg.getMinAspectRatio();
+ if (packageMinAspectRatio != 0) {
+ // Use the application max aspect ration as default if set.
+ minAspectRatio = packageMinAspectRatio;
+ } else {
+ // Default to (1.33) 4:3 aspect ratio for pre-Q apps and unset for Q and greater.
+ // NOTE: 4:3 was the min aspect ratio Android devices can support pre-Q per the CDD,
+ // except for watches which always supported 1:1.
+ minAspectRatio = pkg.getTargetSdkVersion() >= Build.VERSION_CODES.Q
+ ? 0
+ : (mCallback != null && mCallback.hasFeature(FEATURE_WATCH))
+ ? PackageParser.DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH
+ : PackageParser.DEFAULT_PRE_Q_MIN_ASPECT_RATIO;
+ }
+
+ 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 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.getBaseCodePath()
+ + ": 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, PackageParser.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.addRequestedPermission(npi.name)
+ .addImplicitPermission(npi.name);
+ }
+ }
+ if (newPermsMsg != null) {
+ Slog.i(TAG, newPermsMsg.toString());
+ }
+ }
+
+ private static void convertSplitPermissions(ParsingPackage pkg) {
+ List<SplitPermissionInfoParcelable> splitPermissions;
+
+ try {
+ splitPermissions = ActivityThread.getPermissionManager().getSplitPermissions();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ final int listSize = splitPermissions.size();
+ for (int is = 0; is < listSize; is++) {
+ final SplitPermissionInfoParcelable spi = splitPermissions.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.addRequestedPermission(perm)
+ .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);
+ }
+ }
+
+ private static ParseResult validateName(ParseInput input, 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 input.error("bad character '" + c + "'");
+ }
+ if (requireFilename && !FileUtils.isValidExtFilename(name)) {
+ return input.error("Invalid filename");
+ }
+ return hasSep || !requireSeparator
+ ? input.success(null)
+ : input.error("must have at least one '.' separator");
+ }
+
+ public static ParseResult<Bundle> parseMetaData(ParsingPackage pkg, Resources res,
+ XmlResourceParser parser, Bundle data, ParseInput input) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestMetaData);
+ try {
+ if (data == null) {
+ data = new Bundle();
+ }
+
+ String name = TextUtils.safeIntern(
+ nonConfigString(0, R.styleable.AndroidManifestMetaData_name, sa));
+ if (name == null) {
+ return input.error("<meta-data> requires an android:name attribute");
+ }
+
+ TypedValue v = sa.peekValue(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(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 (!PackageParser.RIGID_PARSER) {
+ Slog.w(TAG,
+ "<meta-data> only supports string, integer, float, color, "
+ + "boolean, and resource reference types: "
+ + parser.getName() + " at "
+ + pkg.getBaseCodePath() + " "
+ + parser.getPositionDescription());
+ } else {
+ return input.error("<meta-data> only supports string, integer, float, "
+ + "color, boolean, and resource reference types");
+ }
+ }
+ } else {
+ return input.error("<meta-data> requires an android:value "
+ + "or android:resource attribute");
+ }
+ }
+ return input.success(data);
+ } 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.
+ */
+ public static SigningDetails collectCertificates(ParsingPackageRead pkg, boolean skipVerify)
+ throws PackageParserException {
+ SigningDetails signingDetails = SigningDetails.UNKNOWN;
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ signingDetails = collectCertificates(
+ pkg.getBaseCodePath(),
+ skipVerify,
+ pkg.isStaticSharedLibrary(),
+ signingDetails,
+ pkg.getTargetSdkVersion()
+ );
+
+ String[] splitCodePaths = pkg.getSplitCodePaths();
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ for (int i = 0; i < splitCodePaths.length; i++) {
+ signingDetails = collectCertificates(
+ splitCodePaths[i],
+ skipVerify,
+ pkg.isStaticSharedLibrary(),
+ signingDetails,
+ pkg.getTargetSdkVersion()
+ );
+ }
+ }
+ return signingDetails;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ public static SigningDetails collectCertificates(String baseCodePath, boolean skipVerify,
+ boolean isStaticSharedLibrary, @NonNull SigningDetails existingSigningDetails,
+ int targetSdk) throws PackageParserException {
+ int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+ targetSdk);
+ if (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
+ verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
+ baseCodePath, minSignatureScheme);
+ } else {
+ verified = ApkSignatureVerifier.verify(baseCodePath, 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 (existingSigningDetails == SigningDetails.UNKNOWN) {
+ return verified;
+ } else {
+ if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) {
+ throw new PackageParserException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ baseCodePath + " has mismatched certificates");
+ }
+
+ return existingSigningDetails;
+ }
+ }
+
+ /*
+ 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);
+ }
+
+ /**
+ * 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 baseCodePath, @NonNull String codePath,
+ @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..ba61de1
--- /dev/null
+++ b/android/content/pm/parsing/ParsingUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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 {
+
+ // TODO(b/135203078): Consolidate log tags
+ public static final String TAG = "PackageParsing";
+
+ @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.getBaseCodePath() + " "
+ + 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..c4caedc
--- /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 android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageUserState;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
+
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class ComponentParseUtils {
+
+ private static final String TAG = ParsingPackageUtils.TAG;
+
+ 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 & PackageParser.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);
+ String nameError = PackageParser.validateName(subName, false, false);
+ if (nameError != null) {
+ return input.error("Invalid " + type + " name " + proc + " in package " + pkg
+ + ": " + nameError);
+ }
+ return input.success(pkg + proc);
+ }
+ String nameError = PackageParser.validateName(proc, true, false);
+ if (nameError != null && !"system".equals(proc)) {
+ return input.error("Invalid " + type + " name " + proc + " in package " + pkg
+ + ": " + nameError);
+ }
+ 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..4c93d09
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedActivity.java
@@ -0,0 +1,434 @@
+/*
+ * 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.content.pm.PackageParser;
+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;
+
+ @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.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 = PackageParser.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.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.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.documentLaunchMode = target.documentLaunchMode;
+// 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 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.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.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;
+ }
+
+ @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..f64560a
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -0,0 +1,518 @@
+/*
+ * 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.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.parsing.component.ComponentParseUtils.flag;
+
+import android.annotation.NonNull;
+import android.app.ActivityTaskManager;
+import android.content.pm.ActivityInfo;
+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.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Build;
+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;
+
+/** @hide */
+public class ParsedActivityUtils {
+
+ private static final String TAG = ParsingPackageUtils.TAG;
+
+ @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);
+ 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);
+
+ 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 = PackageParser.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*/);
+ 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);
+ 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 (PackageParser.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 (!PackageParser.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 (!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 = parseLayout(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);
+ }
+ }
+
+ ParseResult<ActivityInfo.WindowLayout> layoutResult = resolveWindowLayout(activity, input);
+ if (layoutResult.isError()) {
+ return input.error(layoutResult);
+ }
+ activity.windowLayout = layoutResult.getResult();
+
+ if (!setExported) {
+ activity.exported = activity.getIntents().size() > 0;
+ }
+
+ 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> parseLayout(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);
+ return input.success(new ActivityInfo.WindowLayout(width, widthFraction, height,
+ heightFraction, gravity, minWidth, minHeight));
+ } 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> resolveWindowLayout(
+ 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(
+ PackageParser.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(
+ PackageParser.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 */);
+ }
+ layout.windowLayoutAffinity = windowLayoutAffinity;
+ return input.success(layout);
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedAttribution.java b/android/content/pm/parsing/component/ParsedAttribution.java
new file mode 100644
index 0000000..02b3c7d
--- /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 = 1000;
+
+ /** 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.14.
+ //
+ // 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 = 1583436566499L,
+ codegenVersion = "1.0.14",
+ 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..c4b1a0e
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedAttributionUtils.java
@@ -0,0 +1,116 @@
+/*
+ * 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.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.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) {
+ // TODO moltmann: Remove handling of featureId
+ attributionTag = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestAttribution_featureId, 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..6323d69
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedComponent.java
@@ -0,0 +1,208 @@
+/*
+ * 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.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+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;
+
+/** @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;
+
+ 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);
+ }
+
+ @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);
+ }
+
+ 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);
+ }
+
+ @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;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedComponentUtils.java b/android/content/pm/parsing/component/ParsedComponentUtils.java
new file mode 100644
index 0000000..b37b617
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedComponentUtils.java
@@ -0,0 +1,100 @@
+/*
+ * 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.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingUtils;
+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;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+
+/** @hide */
+class ParsedComponentUtils {
+
+ private static final String TAG = ParsingPackageUtils.TAG;
+
+ @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);
+
+ if (useRoundIcon) {
+ component.icon = array.getResourceId(roundIconAttr, 0);
+ }
+
+ if (component.icon == 0) {
+ component.icon = array.getResourceId(iconAttr, 0);
+ }
+
+ component.logo = array.getResourceId(logoAttr, 0);
+ component.banner = array.getResourceId(bannerAttr, 0);
+
+ 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<Bundle> result = ParsingPackageUtils.parseMetaData(pkg, resources,
+ parser, component.metaData, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ Bundle bundle = result.getResult();
+ component.metaData = bundle;
+ return input.success(bundle);
+ }
+}
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..0ba92cc
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedIntentInfo.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.parsing.component;
+
+import android.annotation.Nullable;
+import android.content.IntentFilter;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+
+import com.android.internal.util.DataClass;
+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 "ProviderIntentInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + '}';
+ }
+
+ public static final Parcelable.Creator<ParsedIntentInfo> CREATOR =
+ new Parcelable.Creator<ParsedIntentInfo>() {
+ @Override
+ public ParsedIntentInfo createFromParcel(Parcel source) {
+ return new ParsedIntentInfo(source);
+ }
+
+ @Override
+ public ParsedIntentInfo[] newArray(int size) {
+ return new ParsedIntentInfo[size];
+ }
+ };
+
+ 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..390f769
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
@@ -0,0 +1,258 @@
+/*
+ * 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 = ParsingPackageUtils.TAG;
+
+ @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 (PackageParser.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 (PackageParser.DEBUG_PARSER) {
+ 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);
+ }
+
+ 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);
+ }
+
+ 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..a5e394d
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedMainComponent.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+ 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;
+ }
+
+ 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);
+ }
+
+ 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();
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+}
diff --git a/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/android/content/pm/parsing/component/ParsedMainComponentUtils.java
new file mode 100644
index 0000000..f4c9914
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedMainComponentUtils.java
@@ -0,0 +1,136 @@
+/*
+ * 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.ParsingPackageUtils;
+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 = ParsingPackageUtils.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) {
+ 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);
+ }
+
+ 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.getBaseCodePath() + " "
+ + 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..ced3226
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedPermission.java
@@ -0,0 +1,191 @@
+/*
+ * 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 com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+
+/** @hide */
+public class ParsedPermission extends ParsedComponent {
+
+ @Nullable
+ String backgroundPermission;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
+ private String group;
+ int requestRes;
+ int protectionLevel;
+ boolean tree;
+ @Nullable
+ private ParsedPermissionGroup parsedPermissionGroup;
+
+ @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(ParsedPermission other, PermissionInfo pendingPermissionInfo,
+ String packageName, String name) {
+ this(other);
+
+ this.flags = pendingPermissionInfo.flags;
+ this.descriptionRes = pendingPermissionInfo.descriptionRes;
+
+ this.backgroundPermission = pendingPermissionInfo.backgroundPermission;
+ this.group = pendingPermissionInfo.group;
+ this.requestRes = pendingPermissionInfo.requestRes;
+ this.protectionLevel = pendingPermissionInfo.protectionLevel;
+
+ setName(name);
+ setPackageName(packageName);
+ }
+
+ 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 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);
+ }
+
+ 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);
+ }
+
+ 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..1884a1e
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -0,0 +1,210 @@
+/*
+ * 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.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.Slog;
+
+import com.android.internal.R;
+import android.content.pm.parsing.ParsingPackageUtils;
+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 ParsedPermissionUtils {
+
+ private static final String TAG = ParsingPackageUtils.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);
+
+ // 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();
+ }
+
+ // TODO(b/135203078): This is impossible because of default value in above getInt
+ if (permission.protectionLevel == -1) {
+ return input.error("<permission> does not specify protectionLevel");
+ }
+
+ permission.protectionLevel = PermissionInfo.fixProtectionLevel(permission.protectionLevel);
+
+ if (permission.getProtectionFlags() != 0) {
+ if ((permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) == 0
+ && (permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
+ == 0
+ && (permission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) !=
+ PermissionInfo.PROTECTION_SIGNATURE) {
+ return input.error("<permission> protectionLevel specifies a non-instant flag "
+ + "but is not based on signature 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..e0ae81b
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProcess.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 java.util.Collections.emptySet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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();
+
+ protected int gwpAsanMode = -1;
+
+ 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.15.
+ //
+ // 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,
+ int gwpAsanMode) {
+ 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;
+
+ // 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 int getGwpAsanMode() {
+ return gwpAsanMode;
+ }
+
+ @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);
+ }
+
+ @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();
+
+ 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;
+
+ // 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 = 1584557524776L,
+ codegenVersion = "1.0.15",
+ 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 int gwpAsanMode\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..8372707
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -0,0 +1,214 @@
+/*
+ * 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.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 {
+
+ private static final String TAG = ParsingUtils.TAG;
+
+ @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);
+ } 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..aa5ea8d
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedProviderUtils.java
@@ -0,0 +1,355 @@
+/*
+ * 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.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.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 = ParsingPackageUtils.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);
+ 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 "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 prefix over literal path
+ PatternMatcher pa = null;
+ String 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_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.getBaseCodePath() + " " + 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.getBaseCodePath() + " " + 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_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.getBaseCodePath()
+ + " "
+ + 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..8a8a066
--- /dev/null
+++ b/android/content/pm/parsing/component/ParsedServiceUtils.java
@@ -0,0 +1,165 @@
+/*
+ * 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.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 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 {
+
+ private static final String TAG = ParsingPackageUtils.TAG;
+
+ @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
+ );
+
+ 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;
+ default:
+ parseResult = ParsingUtils.unknownTag(tag, pkg, parser, input);
+ break;
+ }
+
+ if (parseResult.isError()) {
+ return input.error(parseResult);
+ }
+ }
+
+ if (!setExported) {
+ service.exported = service.getIntents().size() > 0;
+ }
+
+ return input.success(service);
+ }
+}
diff --git a/android/content/pm/parsing/result/ParseInput.java b/android/content/pm/parsing/result/ParseInput.java
new file mode 100644
index 0000000..d5898b7
--- /dev/null
+++ b/android/content/pm/parsing/result/ParseInput.java
@@ -0,0 +1,143 @@
+/*
+ * 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.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;
+ }
+
+ <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..6115206
--- /dev/null
+++ b/android/content/pm/parsing/result/ParseTypeImpl.java
@@ -0,0 +1,230 @@
+/*
+ * 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.pm.PackageManager;
+import android.content.pm.parsing.ParsingUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+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;
+
+ /**
+ * @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();
+ }
+ return this;
+ }
+
+ @Override
+ public <ResultType> ParseResult<ResultType> success(ResultType result) {
+ if (mErrorCode != PackageManager.INSTALL_SUCCEEDED) {
+ Slog.wtf(ParsingUtils.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..421ae49
--- /dev/null
+++ b/android/content/pm/permission/SplitPermissionInfoParcelable.java
@@ -0,0 +1,202 @@
+/*
+ * 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.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.0.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ /**
+ * 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(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(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; }
+
+ @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
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ public SplitPermissionInfoParcelable createFromParcel(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();
+ return new SplitPermissionInfoParcelable(
+ splitPermission,
+ newPermissions,
+ targetSdk);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1567634390477L,
+ codegenVersion = "1.0.0",
+ 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(from=0L) int mTargetSdk\nprivate void onConstructed()\nclass SplitPermissionInfoParcelable extends java.lang.Object implements [android.os.Parcelable]\[email protected](genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/android/content/pm/split/DefaultSplitAssetLoader.java b/android/content/pm/split/DefaultSplitAssetLoader.java
new file mode 100644
index 0000000..9e3a8f4
--- /dev/null
+++ b/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.os.Build;
+
+import 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 mBaseCodePath;
+ private final String[] mSplitCodePaths;
+ private final @ParseFlags int mFlags;
+ private AssetManager mCachedAssetManager;
+
+ public DefaultSplitAssetLoader(PackageParser.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];
+
+ // Load the base.
+ int splitIdx = 0;
+ apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+
+ // Load any splits.
+ if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+ for (String apkPath : mSplitCodePaths) {
+ apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
+ }
+ }
+
+ AssetManager assets = new AssetManager();
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+
+ mCachedAssetManager = assets;
+ return mCachedAssetManager;
+ }
+
+ @Override
+ public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
+ return getBaseAssetManager();
+ }
+
+ @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..58eaabf
--- /dev/null
+++ b/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -0,0 +1,133 @@
+/*
+ * 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;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.os.Build;
+import android.util.SparseArray;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * 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(PackageParser.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);
+ }
+ }
+}
diff --git a/android/content/pm/split/SplitAssetLoader.java b/android/content/pm/split/SplitAssetLoader.java
new file mode 100644
index 0000000..108fb95
--- /dev/null
+++ b/android/content/pm/split/SplitAssetLoader.java
@@ -0,0 +1,30 @@
+/*
+ * 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.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;
+}
diff --git a/android/content/pm/split/SplitDependencyLoader.java b/android/content/pm/split/SplitDependencyLoader.java
new file mode 100644
index 0000000..3586546
--- /dev/null
+++ b/android/content/pm/split/SplitDependencyLoader.java
@@ -0,0 +1,242 @@
+/*
+ * 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.PackageParser;
+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;
+ }
+
+ public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+ PackageParser.PackageLite pkg) throws 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 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; splitIdx < pkg.splitNames.length; 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 IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+ + "' targets split '" + configForSplit + "', which is missing.");
+ }
+
+ if (!pkg.isFeatureSplits[depIdx]) {
+ throw new 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 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/res/ApkAssets.java b/android/content/res/ApkAssets.java
new file mode 100644
index 0000000..bc41806
--- /dev/null
+++ b/android/content/res/ApkAssets.java
@@ -0,0 +1,443 @@
+/*
+ * 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 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;
+
+ /** 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 final long mNativePtr;
+
+ @Nullable
+ @GuardedBy("this")
+ private final StringBlock mStringBlock;
+
+ @GuardedBy("this")
+ private boolean mOpen = true;
+
+ @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 nativeGetAssetPath(mNativePtr);
+ }
+ }
+
+ CharSequence getStringFromPool(int idx) {
+ if (mStringBlock == null) {
+ return null;
+ }
+
+ synchronized (this) {
+ return mStringBlock.get(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 paranoid.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
+ }
+ }
+
+ /** @hide */
+ @Nullable
+ public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
+ return nativeGetOverlayableInfo(mNativePtr, overlayableName);
+ }
+
+ /** @hide */
+ public boolean definesOverlayable() throws IOException {
+ 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=" + getAssetPath() + "}";
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ }
+
+ /**
+ * Closes this class and the contained {@link #mStringBlock}.
+ */
+ public void close() {
+ synchronized (this) {
+ if (mOpen) {
+ mOpen = false;
+ if (mStringBlock != null) {
+ mStringBlock.close();
+ }
+ nativeDestroy(mNativePtr);
+ }
+ }
+ }
+
+ 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 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..15a184f
--- /dev/null
+++ b/android/content/res/AssetManager.java
@@ -0,0 +1,1599 @@
+/*
+ * 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.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.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) {
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
+ }
+ 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) {
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
+ }
+ 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);
+ }
+ }
+
+ 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
+ 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
+ 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 paranoid.
+ 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
+ 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
+ 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
+ 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) {
+ nativeThemeDestroy(themePtr);
+ decRefsLocked(themePtr);
+ }
+ }
+
+ void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
+ synchronized (this) {
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ nativeThemeApplyStyle(mObject, themePtr, resId, force);
+ }
+ }
+
+ @UnsupportedAppUsage
+ 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 void nativeThemeDestroy(long themePtr);
+ private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
+ boolean force);
+ private static native void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr,
+ long srcAssetManagerPtr, long srcThemePtr);
+ static native void nativeThemeClear(long themePtr);
+ private static native int nativeThemeGetAttributeValue(long ptr, long themePtr,
+ @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve);
+ private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
+ String prefix);
+ static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
+
+ // AssetInputStream related native methods.
+ private static native void nativeAssetDestroy(long assetPtr);
+ private static native int nativeAssetReadChar(long assetPtr);
+ private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len);
+ private static native long nativeAssetSeek(long assetPtr, long offset, int whence);
+ private static native long nativeAssetGetLength(long assetPtr);
+ private static native long nativeAssetGetRemainingLength(long assetPtr);
+
+ private static native String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
+ 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..458e613
--- /dev/null
+++ b/android/content/res/BridgeTypedArray.java
@@ -0,0 +1,1034 @@
+/*
+ * 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.LayoutLog;
+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.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 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];
+ if (resourceValue instanceof TextResourceValue) {
+ String rawString = resourceValue.getRawXmlValue();
+ return Html.fromHtml(rawString, FROM_HTML_MODE_COMPACT);
+ }
+ return resourceValue.getValue();
+ }
+
+ /**
+ * 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(LayoutLog.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(LayoutLog.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.
+ try {
+ int i = Integer.parseInt(s);
+ if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
+ return i;
+ }
+ } catch (NumberFormatException ignored) {
+ // pass
+ }
+
+ 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) {
+ try {
+ return getDimension(index, null);
+ } catch (RuntimeException e) {
+ String s = getString(index);
+
+ if (s != null) {
+ // looks like we were unable to resolve the dimension value
+ Bridge.getLog().warning(LayoutLog.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) {
+ try {
+ // this will throw an exception if not found.
+ return getDimension(index, name);
+ } catch (RuntimeException e) {
+
+ if (LayoutInflater_Delegate.sIsInInclude) {
+ throw new RuntimeException("Layout Dimension '" + name + "' not found.");
+ }
+
+ Bridge.getLog().warning(LayoutLog.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);
+ }
+
+ /** @param name attribute name, used for error reporting. */
+ private int getDimension(int index, @Nullable String name) {
+ String s = getString(index);
+ if (s == null) {
+ if (name != null) {
+ throw new RuntimeException("Attribute '" + name + "' not found");
+ }
+ throw new RuntimeException();
+ }
+ // Check if the value is a magic constant that doesn't require a unit.
+ try {
+ int i = Integer.parseInt(s);
+ if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
+ return i;
+ }
+ } catch (NumberFormatException ignored) {
+ // pass
+ }
+ 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;
+ }
+
+ throw new RuntimeException();
+ }
+
+ /**
+ * 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(LayoutLog.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
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public int getType(int index) {
+ String value = getString(index);
+ if (value == null) {
+ return TYPE_NULL;
+ }
+ if (value.startsWith(PREFIX_RESOURCE_REF)) {
+ return TYPE_REFERENCE;
+ }
+ if (value.startsWith(PREFIX_THEME_REF)) {
+ return TYPE_ATTRIBUTE;
+ }
+ try {
+ // Don't care about the value. Only called to check if an exception is thrown.
+ convertValueToInt(value, 0);
+ if (value.startsWith("0x") || value.startsWith("0X")) {
+ return TYPE_INT_HEX;
+ }
+ // is it a color?
+ if (value.startsWith("#")) {
+ 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;
+ }
+ }
+ if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ return TYPE_INT_BOOLEAN;
+ }
+ return TYPE_INT_DEC;
+ } catch (NumberFormatException ignored) {
+ try {
+ Float.parseFloat(value);
+ return TYPE_FLOAT;
+ } catch (NumberFormatException ignore) {
+ }
+ // Might be a dimension.
+ if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) {
+ return TYPE_DIMENSION;
+ }
+ }
+ // TODO: handle fractions.
+ return TYPE_STRING;
+ }
+
+ /**
+ * 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;
+ }
+
+ 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..f23c802
--- /dev/null
+++ b/android/content/res/ColorStateList.java
@@ -0,0 +1,747 @@
+/*
+ * 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.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.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>
+ *
+ * <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
+ */
+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);
+ }
+
+ /**
+ * 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);
+
+ 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);
+ 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 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 = modulateColorAlpha(baseColor, alphaMod);
+ 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);
+ mColors[i] = modulateColorAlpha(baseColor, alphaMod);
+
+ // 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
+ 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 modulateColorAlpha(int baseColor, float alphaMod) {
+ if (alphaMod == 1.0f) {
+ return baseColor;
+ }
+
+ final int baseAlpha = Color.alpha(baseColor);
+ final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
+ 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
+ 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..c66c70a
--- /dev/null
+++ b/android/content/res/CompatibilityInfo.java
@@ -0,0 +1,635 @@
+/*
+ * 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.content.pm.ApplicationInfo;
+import android.graphics.Canvas;
+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.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;
+
+ /**
+ * 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
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat) {
+ 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 ((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) != 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 isScalingRequired() ? 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 screen rect to the application frame.
+ */
+ @UnsupportedAppUsage
+ public void translateRectInScreenToAppWinFrame(Rect rect) {
+ rect.scale(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 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 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);
+ }
+ }
+
+ /**
+ * 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
+ 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(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..6a9e0aa
--- /dev/null
+++ b/android/content/res/Configuration.java
@@ -0,0 +1,2745 @@
+/*
+ * 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.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.UiModeManager;
+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.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;
+
+ /**
+ * 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");
+ }
+ 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
+ @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;
+ locale = o.locale == null ? null : (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);
+ }
+
+ 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);
+ }
+ 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(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;
+ }
+ }
+ } 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();
+ }
+
+ /**
+ * 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;
+ }
+
+ 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()) {
+ 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);
+ }
+ }
+
+ /**
+ * 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}.
+ */
+ 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;
+ }
+
+ 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) {
+ // Sanity 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.writeParcelable(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);
+ dest.writeValue(windowConfiguration);
+ dest.writeInt(assetsSeq);
+ dest.writeInt(seq);
+ }
+
+ public void readFromParcel(Parcel source) {
+ fontScale = source.readFloat();
+ mcc = source.readInt();
+ mnc = source.readInt();
+
+ mLocaleList = source.readParcelable(LocaleList.class.getClassLoader());
+ 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.setTo((WindowConfiguration) source.readValue(null));
+ assetsSeq = source.readInt();
+ seq = 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;
+
+ // 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(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;
+ 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
+ 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);
+ }
+ 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";
+
+ /**
+ * 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);
+
+ // 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..5497e61
--- /dev/null
+++ b/android/content/res/DrawableCache.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * 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
+ 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..14eb11a
--- /dev/null
+++ b/android/content/res/FontResourcesParser.java
@@ -0,0 +1,251 @@
+/*
+ * 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 List<List<String>> mCerts;
+
+ public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
+ @NonNull String query, @Nullable List<List<String>> certs) {
+ mProviderAuthority = authority;
+ mProviderPackage = pkg;
+ mQuery = query;
+ mCerts = certs;
+ }
+
+ public @NonNull String getAuthority() {
+ return mProviderAuthority;
+ }
+
+ public @NonNull String getPackage() {
+ return mProviderPackage;
+ }
+
+ public @NonNull String getQuery() {
+ return mQuery;
+ }
+
+ 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);
+ 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);
+ }
+ 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..c477abc
--- /dev/null
+++ b/android/content/res/ObbInfo.java
@@ -0,0 +1,109 @@
+/*
+ * 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.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
+ 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..c399bc7
--- /dev/null
+++ b/android/content/res/Resources.java
@@ -0,0 +1,2524 @@
+/*
+ * 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;
+
+/**
+ * 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;
+
+ /** 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
+ 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 references to new implementations as well.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setImpl(ResourcesImpl impl) {
+ if (impl == mResourcesImpl) {
+ return;
+ }
+
+ mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
+ mResourcesImpl = impl;
+
+ // Create new ThemeImpls that are identical to the ones we have.
+ 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.setImpl(mResourcesImpl.newThemeImpl(theme.getKey()));
+ }
+ }
+ }
+ }
+
+ /** @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
+ 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
+ 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 {
+ @UnsupportedAppUsage
+ private ResourcesImpl.ThemeImpl mThemeImpl;
+
+ private Theme() {
+ }
+
+ void setImpl(ResourcesImpl.ThemeImpl impl) {
+ 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) {
+ 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) {
+ 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) {
+ 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 {
+ 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) {
+ 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) {
+ 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) {
+ 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() {
+ 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() {
+ 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) {
+ mThemeImpl.dump(priority, tag, prefix);
+ }
+
+ // Needed by layoutlib.
+ /*package*/ long getNativeTheme() {
+ return mThemeImpl.getNativeTheme();
+ }
+
+ /*package*/ int getAppliedStyleResId() {
+ return mThemeImpl.getAppliedStyleResId();
+ }
+
+ /**
+ * @hide
+ */
+ public ThemeKey getKey() {
+ 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() {
+ 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() {
+ mThemeImpl.rebase();
+ }
+
+ /**
+ * 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) {
+ 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(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
+ public DisplayAdjustments getDisplayAdjustments() {
+ return mResourcesImpl.getDisplayAdjustments();
+ }
+
+ /**
+ * 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..f40d60d
--- /dev/null
+++ b/android/content/res/ResourcesImpl.java
@@ -0,0 +1,1556 @@
+/*
+ * 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.Bitmap;
+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.SystemClock;
+import android.os.SystemProperties;
+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 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;
+
+ static final String TAG_PRELOAD = TAG + ".preload";
+
+ @UnsupportedAppUsage
+ private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
+ @UnsupportedAppUsage
+ private static final boolean TRACE_FOR_MISS_PRELOAD = false; // Do we still need it?
+
+ public static final boolean TRACE_FOR_DETAILED_PRELOAD =
+ SystemProperties.getBoolean("debug.trace_resource_preload", false);
+
+ /** Used only when TRACE_FOR_DETAILED_PRELOAD is true. */
+ private static int sPreloadTracingNumLoadedDrawables;
+ private long mPreloadTracingPreloadStartTime;
+ private long mPreloadTracingStartBitmapSize;
+ private long mPreloadTracingStartBitmapCount;
+
+ private static final int ID_OTHER = 0x01000004;
+
+ private static final Object sSync = new Object();
+
+ private static boolean sPreloaded;
+ @UnsupportedAppUsage
+ 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
+ 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
+ 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
+ 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
+ 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);
+ }
+
+ return Locale.adjustLanguageCode(language) + 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) {
+ if (TRACE_FOR_DETAILED_PRELOAD) {
+ // Log only framework resources
+ if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
+ + Integer.toHexString(id) + " " + name);
+ }
+ }
+ }
+ 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);
+ }
+ }
+ }
+
+ // For preload tracing.
+ long startTime = 0;
+ int startBitmapCount = 0;
+ long startBitmapSize = 0;
+ int startDrawableCount = 0;
+ if (TRACE_FOR_DETAILED_PRELOAD) {
+ startTime = System.nanoTime();
+ startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
+ startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
+ startDrawableCount = sPreloadTracingNumLoadedDrawables;
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
+ }
+
+
+ final Drawable dr;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ 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);
+
+ if (TRACE_FOR_DETAILED_PRELOAD) {
+ if (((id >>> 24) == 0x1)) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ final long time = System.nanoTime() - startTime;
+ final int loadedBitmapCount =
+ Bitmap.sPreloadTracingNumInstantiatedBitmaps - startBitmapCount;
+ final long loadedBitmapSize =
+ Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize;
+ final int loadedDrawables =
+ sPreloadTracingNumLoadedDrawables - startDrawableCount;
+
+ sPreloadTracingNumLoadedDrawables++;
+
+ final boolean isRoot = (android.os.Process.myUid() == 0);
+
+ Log.d(TAG_PRELOAD,
+ (isRoot ? "Preloaded FW drawable #"
+ : "Loaded non-preloaded FW drawable #")
+ + Integer.toHexString(id)
+ + " " + name
+ + " " + file
+ + " " + dr.getClass().getCanonicalName()
+ + " #nested_drawables= " + loadedDrawables
+ + " #bitmaps= " + loadedBitmapCount
+ + " total_bitmap_size= " + loadedBitmapSize
+ + " in[us] " + (time / 1000));
+ }
+ }
+ }
+
+ 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);
+
+ if (TRACE_FOR_DETAILED_PRELOAD) {
+ mPreloadTracingPreloadStartTime = SystemClock.uptimeMillis();
+ mPreloadTracingStartBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
+ mPreloadTracingStartBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
+ Log.d(TAG_PRELOAD, "Preload starting");
+ }
+ }
+ }
+
+ /**
+ * Called by zygote when it is done preloading resources, to change back
+ * to normal Resources operation.
+ */
+ void finishPreloading() {
+ if (mPreloading) {
+ if (TRACE_FOR_DETAILED_PRELOAD) {
+ final long time = SystemClock.uptimeMillis() - mPreloadTracingPreloadStartTime;
+ final long size =
+ Bitmap.sPreloadTracingTotalBitmapsSize - mPreloadTracingStartBitmapSize;
+ final long count = Bitmap.sPreloadTracingNumInstantiatedBitmaps
+ - mPreloadTracingStartBitmapCount;
+ Log.d(TAG_PRELOAD, "Preload finished, "
+ + count + " bitmaps of " + size + " bytes in " + time + " ms");
+ }
+
+ 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();
+ }
+
+ /**
+ * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
+ */
+ ThemeImpl newThemeImpl(Resources.ThemeKey key) {
+ ThemeImpl impl = new ThemeImpl();
+ impl.mKey.setTo(key);
+ impl.rebase();
+ return impl;
+ }
+
+ 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 final AssetManager mAssets;
+ private final long mTheme;
+
+ /**
+ * Resource identifier for the theme.
+ */
+ private int mThemeResId = 0;
+
+ /*package*/ ThemeImpl() {
+ mAssets = ResourcesImpl.this.mAssets;
+ mTheme = mAssets.createTheme();
+ }
+
+ @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) {
+ synchronized (mKey) {
+ mAssets.applyStyleToTheme(mTheme, resId, force);
+ mThemeResId = resId;
+ mKey.append(resId, force);
+ }
+ }
+
+ void setTo(ThemeImpl other) {
+ synchronized (mKey) {
+ synchronized (other.mKey) {
+ 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) {
+ synchronized (mKey) {
+ 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) {
+ synchronized (mKey) {
+ 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) {
+ synchronized (mKey) {
+ return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
+ }
+ }
+
+ int[] getAllAttributes() {
+ return mAssets.getStyleAttributes(getAppliedStyleResId());
+ }
+
+ @Config int getChangingConfigurations() {
+ synchronized (mKey) {
+ final @NativeConfig int nativeChangingConfig =
+ AssetManager.nativeThemeGetChangingConfigurations(mTheme);
+ return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
+ }
+ }
+
+ public void dump(int priority, String tag, String prefix) {
+ synchronized (mKey) {
+ mAssets.dumpTheme(mTheme, priority, tag, prefix);
+ }
+ }
+
+ String[] getTheme() {
+ synchronized (mKey) {
+ 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() {
+ synchronized (mKey) {
+ AssetManager.nativeThemeClear(mTheme);
+
+ // Reapply the same styles in the same order.
+ for (int i = 0; i < mKey.mCount; i++) {
+ final int resId = mKey.mResId[i];
+ final boolean force = mKey.mForce[i];
+ mAssets.applyStyleToTheme(mTheme, resId, force);
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ synchronized (mKey) {
+ 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..9da0f20
--- /dev/null
+++ b/android/content/res/ResourcesKey.java
@@ -0,0 +1,198 @@
+/*
+ * 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 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[] mOverlayDirs;
+
+ @Nullable
+ public final String[] mLibDirs;
+
+ public final int mDisplayId;
+
+ @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[] overlayDirs,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @Nullable CompatibilityInfo compatInfo,
+ @Nullable ResourcesLoader[] loader) {
+ mResDir = resDir;
+ mSplitResDirs = splitResDirs;
+ mOverlayDirs = overlayDirs;
+ mLibDirs = libDirs;
+ mLoaders = (loader != null && loader.length == 0) ? null : loader;
+ mDisplayId = displayId;
+ 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(mOverlayDirs);
+ hash = 31 * hash + Arrays.hashCode(mLibDirs);
+ hash = 31 * hash + 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[] overlayDirs,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @Nullable CompatibilityInfo compatInfo) {
+ this(resDir, splitResDirs, overlayDirs, 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(mOverlayDirs, 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(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(mOverlayDirs, peer.mOverlayDirs)) {
+ 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 (mOverlayDirs != null) {
+ builder.append(TextUtils.join(",", mOverlayDirs));
+ }
+ 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..cd57d5c
--- /dev/null
+++ b/android/content/res/Resources_Delegate.java
@@ -0,0 +1,1172 @@
+/*
+ * 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.LayoutLog;
+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.util.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.Color;
+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;
+
+@SuppressWarnings("deprecation")
+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(LayoutLog.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] = Color.parseColor(element);
+ } else {
+ values[i] = getInt(element);
+ }
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" + element +
+ "\"", null, null);
+ } catch (IllegalArgumentException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains wrong color format: \"" + element +
+ "\"", null, null);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.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(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" + firstValue + "\"",
+ null, null);
+ return new int[1];
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.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(LayoutLog.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(LayoutLog.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(LayoutLog.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(LayoutLog.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(LayoutLog.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.getEnum(defType);
+ return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) :
+ null;
+ }
+
+ ResourceType type = ResourceType.getEnum(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..8f3f77b
--- /dev/null
+++ b/android/content/res/StringBlock.java
@@ -0,0 +1,537 @@
+/*
+ * 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.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));
+ }
+
+ @UnsupportedAppUsage
+ public CharSequence get(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);
+ 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.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 (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;
+ }
+
+ 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.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..29c5c93
--- /dev/null
+++ b/android/content/res/TypedArray.java
@@ -0,0 +1,1390 @@
+/*
+ * 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.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 {
+
+ 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
+ private DisplayMetrics mMetrics;
+ @UnsupportedAppUsage
+ private AssetManager mAssets;
+
+ @UnsupportedAppUsage
+ private boolean mRecycled;
+
+ @UnsupportedAppUsage
+ /*package*/ XmlBlock.Parser mXml;
+ @UnsupportedAppUsage
+ /*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);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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..cb93cbf
--- /dev/null
+++ b/android/content/res/XmlBlock.java
@@ -0,0 +1,556 @@
+/*
+ * 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.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.TypedValue;
+
+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}
+ */
+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;
+ }
+ }
+
+ /*package*/ 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;
+ }
+ public String getText() {
+ int id = nativeGetText(mParseState);
+ return id >= 0 ? mStrings.get(id).toString() : 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;
+ }
+ public String getNamespace() {
+ int id = nativeGetNamespace(mParseState);
+ return id >= 0 ? mStrings.get(id).toString() : "";
+ }
+ public String getName() {
+ int id = nativeGetName(mParseState);
+ return id >= 0 ? mStrings.get(id).toString() : null;
+ }
+ public String getAttributeNamespace(int index) {
+ int id = nativeGetAttributeNamespace(mParseState, index);
+ if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id);
+ if (id >= 0) return mStrings.get(id).toString();
+ else if (id == -1) return "";
+ throw new IndexOutOfBoundsException(String.valueOf(index));
+ }
+ public String getAttributeName(int index) {
+ int id = nativeGetAttributeName(mParseState, index);
+ if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id);
+ if (id >= 0) return mStrings.get(id).toString();
+ 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;
+ }
+ public String getAttributeValue(int index) {
+ int id = nativeGetAttributeStringValue(mParseState, index);
+ if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id);
+ if (id >= 0) return mStrings.get(id).toString();
+
+ // 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.get(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!");
+ }
+
+ public String getIdAttribute() {
+ int id = nativeGetIdAttribute(mParseState);
+ return id >= 0 ? mStrings.get(id).toString() : null;
+ }
+ public String getClassAttribute() {
+ int id = nativeGetClassAttribute(mParseState);
+ return id >= 0 ? mStrings.get(id).toString() : null;
+ }
+
+ public int getIdAttributeResourceValue(int defaultValue) {
+ //todo: create and use native method
+ return getAttributeResourceValue(null, "id", defaultValue);
+ }
+
+ public int getStyleAttribute() {
+ return nativeGetStyleAttribute(mParseState);
+ }
+
+ public void close() {
+ synchronized (mBlock) {
+ if (mParseState != 0) {
+ nativeDestroyParseState(mParseState);
+ mParseState = 0;
+ mBlock.decOpenCountLocked();
+ }
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ close();
+ }
+
+ /*package*/ final CharSequence getPooledString(int id) {
+ return mStrings.get(id);
+ }
+
+ @UnsupportedAppUsage
+ /*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..0a698d1
--- /dev/null
+++ b/android/content/res/loader/ResourcesProvider.java
@@ -0,0 +1,294 @@
+/*
+ * 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);
+ }
+
+ if (mOpen) {
+ mOpen = false;
+ mApkAssets.close();
+ }
+ }
+ }
+}
diff --git a/android/content/rollback/PackageRollbackInfo.java b/android/content/rollback/PackageRollbackInfo.java
new file mode 100644
index 0000000..b273cd6
--- /dev/null
+++ b/android/content/rollback/PackageRollbackInfo.java
@@ -0,0 +1,262 @@
+/*
+ * 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.annotation.TestApi;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+import android.util.SparseLongArray;
+
+import java.util.ArrayList;
+
+/**
+ * Information about a rollback available for a particular package.
+ *
+ * @hide
+ */
+@SystemApi @TestApi
+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 IntArray 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 IntArray mSnapshottedUsers;
+
+ /**
+ * A mapping between user and an inode of theirs CE data snapshot.
+ */
+ // NOTE: Not a part of the Parcelable representation of this object.
+ private final SparseLongArray mCeSnapshotInodes;
+
+ /**
+ * 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 IntArray 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 IntArray getSnapshottedUsers() {
+ return mSnapshottedUsers;
+ }
+
+ /** @hide */
+ public SparseLongArray getCeSnapshotInodes() {
+ return mCeSnapshotInodes;
+ }
+
+ /** @hide */
+ public void putCeSnapshotInode(int userId, long ceSnapshotInode) {
+ mCeSnapshotInodes.put(userId, ceSnapshotInode);
+ }
+
+ /** @hide */
+ public void removePendingBackup(int userId) {
+ int idx = mPendingBackups.indexOf(userId);
+ if (idx != -1) {
+ mPendingBackups.remove(idx);
+ }
+ }
+
+ /** @hide */
+ public void removePendingRestoreInfo(int userId) {
+ removeRestoreInfo(getRestoreInfo(userId));
+ }
+
+ /** @hide */
+ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+ VersionedPackage packageRolledBackTo,
+ @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
+ boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
+ @NonNull SparseLongArray ceSnapshotInodes) {
+ this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
+ isApkInApex, snapshottedUsers, ceSnapshotInodes,
+ PackageManager.RollbackDataPolicy.RESTORE);
+ }
+
+ /** @hide */
+ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+ VersionedPackage packageRolledBackTo,
+ @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
+ boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
+ @NonNull SparseLongArray ceSnapshotInodes,
+ @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;
+ this.mCeSnapshotInodes = ceSnapshotInodes;
+ }
+
+ 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.mCeSnapshotInodes = 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..c09cfd5
--- /dev/null
+++ b/android/content/rollback/RollbackInfo.java
@@ -0,0 +1,140 @@
+/*
+ * 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.annotation.TestApi;
+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 @TestApi
+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..7ebeb21
--- /dev/null
+++ b/android/content/rollback/RollbackManager.java
@@ -0,0 +1,278 @@
+/*
+ * 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.PackageInstaller;
+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 @TestApi
+@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/type/DefaultMimeMapFactory.java b/android/content/type/DefaultMimeMapFactory.java
new file mode 100644
index 0000000..11d20d4
--- /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.put(specs.get(0), specs.subList(1, specs.size()));
+ }
+ } catch (IOException | RuntimeException e) {
+ throw new RuntimeException("Failed to parse " + resourceName, e);
+ }
+ }
+
+}