summaryrefslogtreecommitdiff
path: root/android/content
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
commit10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch)
tree8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/content
parent677516fb6b6f207d373984757d3d9450474b6b00 (diff)
downloadandroid-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \ --bid 4335822 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4335822.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/content')
-rw-r--r--android/content/AbstractThreadedSyncAdapter.java435
-rw-r--r--android/content/ActivityNotFoundException.java35
-rw-r--r--android/content/AsyncQueryHandler.java360
-rw-r--r--android/content/AsyncTaskLoader.java380
-rw-r--r--android/content/BroadcastReceiver.java653
-rw-r--r--android/content/ClipData.java1137
-rw-r--r--android/content/ClipDescription.java371
-rw-r--r--android/content/ClipboardManager.java225
-rw-r--r--android/content/ComponentCallbacks.java68
-rw-r--r--android/content/ComponentCallbacks2.java178
-rw-r--r--android/content/ComponentName.java391
-rw-r--r--android/content/ContentInsertHandler.java50
-rw-r--r--android/content/ContentProvider.java2112
-rw-r--r--android/content/ContentProviderClient.java595
-rw-r--r--android/content/ContentProviderNative.java797
-rw-r--r--android/content/ContentProviderOperation.java691
-rw-r--r--android/content/ContentProviderResult.java91
-rw-r--r--android/content/ContentQueryMap.java182
-rw-r--r--android/content/ContentResolver.java3080
-rw-r--r--android/content/ContentUris.java112
-rw-r--r--android/content/ContentValues.java533
-rw-r--r--android/content/Context.java4780
-rw-r--r--android/content/ContextWrapper.java973
-rw-r--r--android/content/CursorEntityIterator.java112
-rw-r--r--android/content/CursorLoader.java243
-rw-r--r--android/content/DefaultDataHandler.java262
-rw-r--r--android/content/DialogInterface.java157
-rw-r--r--android/content/Entity.java70
-rw-r--r--android/content/EntityIterator.java39
-rw-r--r--android/content/IContentProvider.java95
-rw-r--r--android/content/Intent.java10086
-rw-r--r--android/content/IntentFilter.java2027
-rw-r--r--android/content/IntentSender.java383
-rw-r--r--android/content/Loader.java552
-rw-r--r--android/content/MutableContextWrapper.java38
-rw-r--r--android/content/OperationApplicationException.java54
-rw-r--r--android/content/PeriodicSync.java165
-rw-r--r--android/content/QuickViewConstants.java68
-rw-r--r--android/content/ReceiverCallNotAllowedException.java32
-rw-r--r--android/content/RestrictionEntry.java558
-rw-r--r--android/content/RestrictionsManager.java748
-rw-r--r--android/content/SearchRecentSuggestionsProvider.java397
-rw-r--r--android/content/ServiceConnection.java66
-rw-r--r--android/content/SharedPreferences.java381
-rw-r--r--android/content/SyncActivityTooManyDeletes.java133
-rw-r--r--android/content/SyncAdapterType.java245
-rw-r--r--android/content/SyncAdaptersCache.java155
-rw-r--r--android/content/SyncContext.java80
-rw-r--r--android/content/SyncInfo.java112
-rw-r--r--android/content/SyncRequest.java547
-rw-r--r--android/content/SyncResult.java325
-rw-r--r--android/content/SyncStats.java178
-rw-r--r--android/content/SyncStatusInfo.java254
-rw-r--r--android/content/SyncStatusObserver.java21
-rw-r--r--android/content/UndoManager.java937
-rw-r--r--android/content/UndoOperation.java112
-rw-r--r--android/content/UndoOwner.java75
-rw-r--r--android/content/UriMatcher.java279
-rw-r--r--android/content/UriPermission.java117
-rw-r--r--android/content/om/OverlayInfo.java263
-rw-r--r--android/content/pm/ActivityInfo.java1349
-rw-r--r--android/content/pm/ApplicationInfo.java1577
-rw-r--r--android/content/pm/AppsQueryHelper.java217
-rw-r--r--android/content/pm/AuxiliaryResolveInfo.java86
-rw-r--r--android/content/pm/BaseParceledListSlice.java211
-rw-r--r--android/content/pm/ChangedPackages.java84
-rw-r--r--android/content/pm/ComponentInfo.java258
-rw-r--r--android/content/pm/ConfigurationInfo.java145
-rw-r--r--android/content/pm/EphemeralIntentFilter.java85
-rw-r--r--android/content/pm/EphemeralResolveInfo.java224
-rw-r--r--android/content/pm/FallbackCategoryProvider.java69
-rw-r--r--android/content/pm/FeatureGroupInfo.java65
-rw-r--r--android/content/pm/FeatureInfo.java145
-rw-r--r--android/content/pm/InstantAppInfo.java149
-rw-r--r--android/content/pm/InstantAppIntentFilter.java82
-rw-r--r--android/content/pm/InstantAppRequest.java55
-rw-r--r--android/content/pm/InstantAppResolveInfo.java250
-rw-r--r--android/content/pm/InstrumentationInfo.java214
-rw-r--r--android/content/pm/IntentFilterVerificationInfo.java258
-rw-r--r--android/content/pm/KeySet.java109
-rw-r--r--android/content/pm/LabeledIntent.java193
-rw-r--r--android/content/pm/LauncherActivityInfo.java176
-rw-r--r--android/content/pm/LauncherApps.java1477
-rw-r--r--android/content/pm/LimitedLengthInputStream.java94
-rw-r--r--android/content/pm/MacAuthenticatedInputStream.java78
-rw-r--r--android/content/pm/PackageBackwardCompatibility.java89
-rw-r--r--android/content/pm/PackageCleanItem.java85
-rw-r--r--android/content/pm/PackageInfo.java417
-rw-r--r--android/content/pm/PackageInfoLite.java128
-rw-r--r--android/content/pm/PackageInstaller.java1718
-rw-r--r--android/content/pm/PackageItemInfo.java431
-rw-r--r--android/content/pm/PackageManager.java5875
-rw-r--r--android/content/pm/PackageManagerInternal.java386
-rw-r--r--android/content/pm/PackageParser.java7606
-rw-r--r--android/content/pm/PackageParserCacheHelper.java157
-rw-r--r--android/content/pm/PackageStats.java211
-rw-r--r--android/content/pm/PackageUserState.java252
-rw-r--r--android/content/pm/ParceledListSlice.java88
-rw-r--r--android/content/pm/PathPermission.java68
-rw-r--r--android/content/pm/PermissionGroupInfo.java145
-rw-r--r--android/content/pm/PermissionInfo.java348
-rw-r--r--android/content/pm/ProviderInfo.java181
-rw-r--r--android/content/pm/RegisteredServicesCache.java782
-rw-r--r--android/content/pm/RegisteredServicesCacheListener.java30
-rw-r--r--android/content/pm/ResolveInfo.java479
-rw-r--r--android/content/pm/SELinuxUtil.java41
-rw-r--r--android/content/pm/ServiceInfo.java132
-rw-r--r--android/content/pm/SharedLibraryInfo.java228
-rw-r--r--android/content/pm/ShortcutInfo.java1941
-rw-r--r--android/content/pm/ShortcutManager.java738
-rw-r--r--android/content/pm/ShortcutServiceInternal.java80
-rw-r--r--android/content/pm/Signature.java306
-rw-r--r--android/content/pm/StringParceledListSlice.java83
-rw-r--r--android/content/pm/UserInfo.java295
-rw-r--r--android/content/pm/VerificationParams.java211
-rw-r--r--android/content/pm/VerifierDeviceIdentity.java240
-rw-r--r--android/content/pm/VerifierInfo.java83
-rw-r--r--android/content/pm/VersionedPackage.java100
-rw-r--r--android/content/pm/XmlSerializerAndParser.java29
-rw-r--r--android/content/pm/permission/RuntimePermissionPresentationInfo.java110
-rw-r--r--android/content/pm/permission/RuntimePermissionPresenter.java289
-rw-r--r--android/content/pm/split/DefaultSplitAssetLoader.java100
-rw-r--r--android/content/pm/split/SplitAssetDependencyLoader.java129
-rw-r--r--android/content/pm/split/SplitAssetLoader.java30
-rw-r--r--android/content/pm/split/SplitDependencyLoader.java242
-rw-r--r--android/content/res/AssetFileDescriptor.java379
-rw-r--r--android/content/res/AssetManager.java914
-rw-r--r--android/content/res/AssetManager_Delegate.java64
-rw-r--r--android/content/res/BridgeAssetManager.java62
-rw-r--r--android/content/res/BridgeTypedArray.java1025
-rw-r--r--android/content/res/ColorStateList.java736
-rw-r--r--android/content/res/CompatResources.java69
-rw-r--r--android/content/res/CompatibilityInfo.java615
-rw-r--r--android/content/res/ComplexColor.java78
-rw-r--r--android/content/res/ComplexColor_Accessor.java46
-rw-r--r--android/content/res/Configuration.java2385
-rw-r--r--android/content/res/ConfigurationBoundResourceCache.java50
-rw-r--r--android/content/res/ConstantState.java63
-rw-r--r--android/content/res/DrawableCache.java47
-rw-r--r--android/content/res/FontResourcesParser.java230
-rw-r--r--android/content/res/GradientColor.java596
-rw-r--r--android/content/res/ObbInfo.java106
-rw-r--r--android/content/res/ObbScanner.java63
-rw-r--r--android/content/res/ResourceId.java44
-rw-r--r--android/content/res/Resources.java2184
-rw-r--r--android/content/res/ResourcesImpl.java1380
-rw-r--r--android/content/res/ResourcesKey.java169
-rw-r--r--android/content/res/Resources_Delegate.java1100
-rw-r--r--android/content/res/Resources_Theme_Delegate.java152
-rw-r--r--android/content/res/StringBlock.java496
-rw-r--r--android/content/res/ThemedResourceCache.java234
-rw-r--r--android/content/res/TypedArray.java1286
-rw-r--r--android/content/res/TypedArray_Delegate.java35
-rw-r--r--android/content/res/XmlBlock.java535
-rw-r--r--android/content/res/XmlResourceParser.java36
155 files changed, 86307 insertions, 0 deletions
diff --git a/android/content/AbstractThreadedSyncAdapter.java b/android/content/AbstractThreadedSyncAdapter.java
new file mode 100644
index 00000000..2629929e
--- /dev/null
+++ b/android/content/AbstractThreadedSyncAdapter.java
@@ -0,0 +1,435 @@
+/*
+ * 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.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+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>
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.content.SyncAdapter" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;meta-data android:name="android.content.SyncAdapter"
+ * android:resource="@xml/syncadapter" /&gt;
+ * </pre>
+ * The <code>android:resource</code> attribute must point to a resource that looks like:
+ * <pre>
+ * &lt;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"
+ * /&gt;
+ * </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 intialize 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 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();
+ }
+
+ /**
+ * 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 00000000..16149bbc
--- /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/AsyncQueryHandler.java b/android/content/AsyncQueryHandler.java
new file mode 100644
index 00000000..07da99d9
--- /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 00000000..b7545bf5
--- /dev/null
+++ b/android/content/AsyncTaskLoader.java
@@ -0,0 +1,380 @@
+/*
+ * 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.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.
+ */
+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
+ }
+ }
+ }
+
+ 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
+ */
+ 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/BroadcastReceiver.java b/android/content/BroadcastReceiver.java
new file mode 100644
index 00000000..58a9183d
--- /dev/null
+++ b/android/content/BroadcastReceiver.java
@@ -0,0 +1,653 @@
+/*
+ * 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.app.ActivityThread;
+import android.app.IActivityManager;
+import android.app.QueuedWork;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+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 &lt;receiver&gt;}
+ * 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 {
+ 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;
+
+ final int mType;
+ final boolean mOrderedHint;
+ final boolean mInitialStickyHint;
+ final IBinder mToken;
+ final int mSendingUser;
+ final int mFlags;
+
+ int mResultCode;
+ String mResultData;
+ Bundle mResultExtras;
+ boolean mAbortBroadcast;
+ boolean mFinished;
+
+ /** @hide */
+ 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 &lt;receiver&gt; 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
+ */
+ public final void setPendingResult(PendingResult result) {
+ mPendingResult = result;
+ }
+
+ /**
+ * For internal use to set the result data that is active. @hide
+ */
+ public final PendingResult getPendingResult() {
+ return mPendingResult;
+ }
+
+ /** @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 00000000..9323261f
--- /dev/null
+++ b/android/content/ClipData.java
@@ -0,0 +1,1137 @@
+/**
+ * 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.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+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 com.android.internal.util.ArrayUtils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import libcore.io.IoUtils;
+
+/**
+ * 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;
+ 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 formated text into plain text.
+ */
+ 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 formated 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");
+ }
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /** @removed use #addItem(ContentResolver, Item) instead */
+ @Deprecated
+ public void addItem(Item item, ContentResolver resolver) {
+ addItem(resolver, 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 */
+ 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(' ');
+ }
+ mItems.get(0).toShortString(b);
+ if (mItems.size() > 1) {
+ b.append(" ...");
+ }
+ }
+ }
+
+ /** @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.writeString(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.readString();
+ 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 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 00000000..8e30fd6e
--- /dev/null
+++ b/android/content/ClipDescription.java
@@ -0,0 +1,371 @@
+/**
+ * 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 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 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 *&#47;*.
+ * @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;
+ }
+
+ @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 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 00000000..718e465b
--- /dev/null
+++ b/android/content/ClipboardManager.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.SystemService;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+
+import java.util.ArrayList;
+
+/**
+ * Interface to the clipboard service, for placing and retrieving text in
+ * the global clipboard.
+ *
+ * <p>
+ * The ClipboardManager API itself is very simple: it consists of methods
+ * to atomically get and set the current primary clipboard data. That data
+ * is expressed as a {@link ClipData} object, which defines the protocol
+ * for data exchange between applications.
+ *
+ * <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>
+ */
+@SystemService(Context.CLIPBOARD_SERVICE)
+public class ClipboardManager extends android.text.ClipboardManager {
+ private final Context mContext;
+ private final IClipboard mService;
+
+ private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
+ = new ArrayList<OnPrimaryClipChangedListener>();
+
+ private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
+ = new IOnPrimaryClipChangedListener.Stub() {
+ public void dispatchPrimaryClipChanged() {
+ mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED);
+ }
+ };
+
+ static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REPORT_PRIMARY_CLIP_CHANGED:
+ reportPrimaryClipChanged();
+ }
+ }
+ };
+
+ /**
+ * 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) throws ServiceNotFoundException {
+ mContext = context;
+ mService = IClipboard.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
+ }
+
+ /**
+ * 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.
+ */
+ public void setPrimaryClip(ClipData clip) {
+ try {
+ if (clip != null) {
+ clip.prepareToLeaveProcess(true);
+ }
+ mService.setPrimaryClip(clip, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current primary clip on the clipboard.
+ */
+ public ClipData getPrimaryClip() {
+ try {
+ return mService.getPrimaryClip(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a description of the current primary clip on the clipboard
+ * but not a copy of its data.
+ */
+ public ClipDescription getPrimaryClipDescription() {
+ try {
+ return mService.getPrimaryClipDescription(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if there is currently a primary clip on the clipboard.
+ */
+ public boolean hasPrimaryClip() {
+ try {
+ return mService.hasPrimaryClip(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
+ synchronized (mPrimaryClipChangedListeners) {
+ if (mPrimaryClipChangedListeners.isEmpty()) {
+ try {
+ mService.addPrimaryClipChangedListener(
+ mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ mPrimaryClipChangedListeners.add(what);
+ }
+ }
+
+ public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
+ synchronized (mPrimaryClipChangedListeners) {
+ mPrimaryClipChangedListeners.remove(what);
+ if (mPrimaryClipChangedListeners.isEmpty()) {
+ try {
+ mService.removePrimaryClipChangedListener(
+ mPrimaryClipChangedServiceListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #getPrimaryClip()} instead. This retrieves
+ * the primary clip and tries to coerce it to a string.
+ */
+ @Deprecated
+ public CharSequence getText() {
+ ClipData clip = getPrimaryClip();
+ if (clip != null && clip.getItemCount() > 0) {
+ return clip.getItemAt(0).coerceToText(mContext);
+ }
+ 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) {
+ setPrimaryClip(ClipData.newPlainText(null, text));
+ }
+
+ /**
+ * @deprecated Use {@link #hasPrimaryClip()} instead.
+ */
+ @Deprecated
+ public boolean hasText() {
+ try {
+ return mService.hasClipboardText(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void reportPrimaryClipChanged() {
+ Object[] listeners;
+
+ synchronized (mPrimaryClipChangedListeners) {
+ final int N = mPrimaryClipChangedListeners.size();
+ if (N <= 0) {
+ return;
+ }
+ listeners = mPrimaryClipChangedListeners.toArray();
+ }
+
+ for (int i=0; i<listeners.length; i++) {
+ ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged();
+ }
+ }
+}
diff --git a/android/content/ComponentCallbacks.java b/android/content/ComponentCallbacks.java
new file mode 100644
index 00000000..b96c8d38
--- /dev/null
+++ b/android/content/ComponentCallbacks.java
@@ -0,0 +1,68 @@
+/*
+ * 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.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(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 00000000..6576c0f9
--- /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 00000000..ea6b7690
--- /dev/null
+++ b/android/content/ComponentName.java
@@ -0,0 +1,391 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.io.PrintWriter;
+import java.lang.Comparable;
+
+/**
+ * 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);
+ }
+
+ /**
+ * 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 */
+ public static void appendShortString(StringBuilder sb, String packageName, String className) {
+ sb.append(packageName).append('/');
+ appendShortClassName(sb, packageName, className);
+ }
+
+ /** @hide */
+ 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 + "}";
+ }
+
+ @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) {
+ 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 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();
+ }
+}
diff --git a/android/content/ContentInsertHandler.java b/android/content/ContentInsertHandler.java
new file mode 100644
index 00000000..fbf726e0
--- /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/ContentProvider.java b/android/content/ContentProvider.java
new file mode 100644
index 00000000..cdeaea3e
--- /dev/null
+++ b/android/content/ContentProvider.java
@@ -0,0 +1,2112 @@
+/*
+ * 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.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+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.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+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;
+
+/**
+ * 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>
+ */
+public abstract class ContentProvider implements ComponentCallbacks2 {
+
+ private static final String TAG = "ContentProvider";
+
+ /*
+ * Note: if you add methods to ContentProvider, you must add similar methods to
+ * MockContentProvider.
+ */
+
+ 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.
+ private String mAuthority;
+ private String[] mAuthorities;
+ private String mReadPermission;
+ private String mWritePermission;
+ private PathPermission[] mPathPermissions;
+ private boolean mExported;
+ private boolean mNoPerms;
+ private boolean mSingleUser;
+
+ private final ThreadLocal<String> mCallingPackage = new ThreadLocal<>();
+
+ 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
+ */
+ 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
+ */
+ 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 {
+ AppOpsManager mAppOpsManager = null;
+ int mReadOp = AppOpsManager.OP_NONE;
+ int mWriteOp = AppOpsManager.OP_NONE;
+
+ ContentProvider getContentProvider() {
+ return ContentProvider.this;
+ }
+
+ @Override
+ public String getProviderName() {
+ return getContentProvider().getClass().getName();
+ }
+
+ @Override
+ public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, 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 = ContentProvider.this.query(
+ uri, projection, queryArgs,
+ CancellationSignal.fromTransport(cancellationSignal));
+ if (cursor == null) {
+ return null;
+ }
+
+ // Return an empty cursor for all columns.
+ return new MatrixCursor(cursor.getColumnNames(), 0);
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.query(
+ uri, projection, queryArgs,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ return ContentProvider.this.getType(uri);
+ }
+
+ @Override
+ public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
+ validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return rejectInsert(uri, initialValues);
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return maybeAddUserId(ContentProvider.this.insert(uri, initialValues), userId);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.bulkInsert(uri, initialValues);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ 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();
+ validateIncomingUri(uri);
+ userIds[i] = getUserIdFromUri(uri);
+ if (userIds[i] != UserHandle.USER_CURRENT) {
+ // Removing the user id from the uri.
+ operation = new ContentProviderOperation(operation, true);
+ operations.set(i, operation);
+ }
+ if (operation.isReadOperation()) {
+ if (enforceReadPermission(callingPkg, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
+ }
+ if (operation.isWriteOperation()) {
+ if (enforceWritePermission(callingPkg, uri, null)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
+ }
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ ContentProviderResult[] results = ContentProvider.this.applyBatch(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;
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.delete(uri, selection, selectionArgs);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public int update(String callingPkg, Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.update(uri, values, selection, selectionArgs);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal,
+ IBinder callerToken) throws FileNotFoundException {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(callingPkg, uri, mode, callerToken);
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.openFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(
+ String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
+ throws FileNotFoundException {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(callingPkg, uri, mode, null);
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.openAssetFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public Bundle call(
+ String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) {
+ Bundle.setDefusable(extras, true);
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.call(method, arg, extras);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
+ Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
+ Bundle.setDefusable(opts, true);
+ validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ enforceFilePermission(callingPkg, uri, "r", null);
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.openTypedAssetFile(
+ uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public ICancellationSignal createCancellationSignal() {
+ return CancellationSignal.createTransport();
+ }
+
+ @Override
+ public Uri canonicalize(String callingPkg, Uri uri) {
+ validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return maybeAddUserId(ContentProvider.this.canonicalize(uri), userId);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, Uri uri) {
+ validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return maybeAddUserId(ContentProvider.this.uncanonicalize(uri), userId);
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ @Override
+ public boolean refresh(String callingPkg, Uri uri, Bundle args,
+ ICancellationSignal cancellationSignal) throws RemoteException {
+ validateIncomingUri(uri);
+ uri = getUriWithoutUserId(uri);
+ if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return ContentProvider.this.refresh(uri, args,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ setCallingPackage(original);
+ }
+ }
+
+ private void enforceFilePermission(String callingPkg, Uri uri, String mode,
+ IBinder callerToken) throws FileNotFoundException, SecurityException {
+ if (mode != null && mode.indexOf('w') != -1) {
+ if (enforceWritePermission(callingPkg, uri, callerToken)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ } else {
+ if (enforceReadPermission(callingPkg, uri, callerToken)
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ }
+ }
+
+ private int enforceReadPermission(String callingPkg, Uri uri, IBinder callerToken)
+ throws SecurityException {
+ final int mode = enforceReadPermissionInner(uri, callingPkg, callerToken);
+ if (mode != MODE_ALLOWED) {
+ return mode;
+ }
+
+ if (mReadOp != AppOpsManager.OP_NONE) {
+ return mAppOpsManager.noteProxyOp(mReadOp, callingPkg);
+ }
+
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ private int enforceWritePermission(String callingPkg, Uri uri, IBinder callerToken)
+ throws SecurityException {
+ final int mode = enforceWritePermissionInner(uri, callingPkg, callerToken);
+ if (mode != MODE_ALLOWED) {
+ return mode;
+ }
+
+ if (mWriteOp != AppOpsManager.OP_NONE) {
+ return mAppOpsManager.noteProxyOp(mWriteOp, callingPkg);
+ }
+
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ boolean checkUser(int pid, int uid, Context context) {
+ return UserHandle.getUserId(uid) == context.getUserId()
+ || mSingleUser
+ || context.checkPermission(INTERACT_ACROSS_USERS, 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,
+ IBinder callerToken) {
+ if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(),
+ callerToken) != PERMISSION_GRANTED) {
+ return MODE_ERRORED;
+ }
+
+ final int permOp = AppOpsManager.permissionToOpCode(permission);
+ if (permOp != AppOpsManager.OP_NONE) {
+ return mTransport.mAppOpsManager.noteProxyOp(permOp, callingPkg);
+ }
+
+ return MODE_ALLOWED;
+ }
+
+ /** {@hide} */
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg, 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, 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, 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, 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, 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, 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;
+ }
+
+ /**
+ * Set the calling package, returning the current value (or {@code null})
+ * which can be used later to restore the previous state.
+ */
+ private String setCallingPackage(String callingPackage) {
+ final String original = mCallingPackage.get();
+ mCallingPackage.set(callingPackage);
+ 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 String pkg = mCallingPackage.get();
+ if (pkg != null) {
+ mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
+ }
+ return pkg;
+ }
+
+ /**
+ * 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 */
+ public final void setAppOps(int readOp, int writeOp) {
+ if (!mNoPerms) {
+ mTransport.mReadOp = readOp;
+ mTransport.mWriteOp = writeOp;
+ }
+ }
+
+ /** @hide */
+ public AppOpsManager getAppOpsManager() {
+ return mTransport.mAppOpsManager;
+ }
+
+ /**
+ * 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 ContentResolver#QUERY_ARG_SQL_SELECTION}
+ * <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}
+ * <li>{@link 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 #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 all additional information necessary for the query.
+ * Values in the Bundle may include SQL style arguments.
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or {@code null}.
+ * @return a Cursor or {@code null}.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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 args 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.
+ */
+ public boolean refresh(Uri uri, @Nullable Bundle args,
+ @Nullable CancellationSignal cancellationSignal) {
+ return false;
+ }
+
+ /**
+ * @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. This must not be {@code null}.
+ * @param values A set of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
+ * @return The URI for the newly inserted item.
+ */
+ public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues 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.
+ */
+ 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 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.
+ * This must not be {@code null}.
+ * @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);
+
+ /**
+ * 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)
+ */
+ 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)
+ */
+ 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 *&#47;* 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)
+ */
+ 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 *&#47;*, 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 *&#47;*, 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)
+ */
+ 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
+ */
+ public IContentProvider getIContentProvider() {
+ return mTransport;
+ }
+
+ /**
+ * Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
+ * when directly instantiating the provider for testing.
+ * @hide
+ */
+ 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;
+
+ /*
+ * 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.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);
+ }
+ 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
+ */
+ 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.
+ */
+ 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 &lt;provider_component_name&gt;".
+ *
+ * @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");
+ }
+
+ /** @hide */
+ private void 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);
+ }
+ }
+ if (!matchesOurAuthorities(getAuthorityWithoutUserId(auth))) {
+ String message = "The authority of the uri " + uri + " does not match the one of the "
+ + "contentProvider: ";
+ if (mAuthority != null) {
+ message += mAuthority;
+ } else {
+ message += Arrays.toString(mAuthorities);
+ }
+ throw new SecurityException(message);
+ }
+ }
+
+ /** @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);
+ }
+
+ /**
+ * Removes userId part from authority string. Expects format:
+ * userId@some.authority
+ * 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 */
+ 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 00000000..f8c139fe
--- /dev/null
+++ b/android/content/ContentProviderClient.java
@@ -0,0 +1,595 @@
+/*
+ * 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.content.res.AssetFileDescriptor;
+import android.database.CrossProcessCursorWrapper;
+import android.database.Cursor;
+import android.net.Uri;
+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 com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+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 AutoCloseable {
+ private static final String TAG = "ContentProviderClient";
+
+ @GuardedBy("ContentProviderClient.class")
+ private static Handler sAnrHandler;
+
+ private final ContentResolver mContentResolver;
+ private final IContentProvider mContentProvider;
+ private final String mPackageName;
+ 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) {
+ mContentResolver = contentResolver;
+ mContentProvider = contentProvider;
+ mPackageName = contentResolver.mPackageName;
+
+ mStable = stable;
+
+ mCloseGuard.open("close");
+ }
+
+ /** {@hide} */
+ public void setDetectNotResponding(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 */);
+ }
+ } else {
+ mAnrRunnable = null;
+ }
+ }
+ }
+
+ 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} */
+ public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
+ throws RemoteException {
+ Preconditions.checkNotNull(uri, "url");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = mContentProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ final Cursor cursor = mContentProvider.query(
+ mPackageName, 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} */
+ public @Nullable String getType(@NonNull Uri url) throws RemoteException {
+ Preconditions.checkNotNull(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} */
+ public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
+ throws RemoteException {
+ Preconditions.checkNotNull(url, "url");
+ Preconditions.checkNotNull(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} */
+ public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
+ Preconditions.checkNotNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.canonicalize(mPackageName, url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#uncanonicalize} */
+ public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
+ Preconditions.checkNotNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.uncanonicalize(mPackageName, url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#refresh} */
+ public boolean refresh(Uri url, @Nullable Bundle args,
+ @Nullable CancellationSignal cancellationSignal) throws RemoteException {
+ Preconditions.checkNotNull(url, "url");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = mContentProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
+ } 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 {
+ Preconditions.checkNotNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.insert(mPackageName, url, initialValues);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
+ /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
+ public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
+ throws RemoteException {
+ Preconditions.checkNotNull(url, "url");
+ Preconditions.checkNotNull(initialValues, "initialValues");
+
+ beforeRemote();
+ try {
+ return mContentProvider.bulkInsert(mPackageName, 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 {
+ Preconditions.checkNotNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
+ } 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 {
+ Preconditions.checkNotNull(url, "url");
+
+ beforeRemote();
+ try {
+ return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
+ } 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.
+ */
+ public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ Preconditions.checkNotNull(url, "url");
+ Preconditions.checkNotNull(mode, "mode");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ signal.throwIfCanceled();
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
+ return mContentProvider.openFile(mPackageName, 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.
+ */
+ public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
+ @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
+ Preconditions.checkNotNull(url, "url");
+ Preconditions.checkNotNull(mode, "mode");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ signal.throwIfCanceled();
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
+ return mContentProvider.openAssetFile(mPackageName, 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 {
+ Preconditions.checkNotNull(uri, "uri");
+ Preconditions.checkNotNull(mimeType, "mimeType");
+
+ beforeRemote();
+ try {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ signal.throwIfCanceled();
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
+ return mContentProvider.openTypedAssetFile(
+ mPackageName, uri, mimeType, 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 {
+ Preconditions.checkNotNull(operations, "operations");
+
+ beforeRemote();
+ try {
+ return mContentProvider.applyBatch(mPackageName, 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 {
+ Preconditions.checkNotNull(method, "method");
+
+ beforeRemote();
+ try {
+ return mContentProvider.call(mPackageName, 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)) {
+ 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} */
+ public static void releaseQuietly(ContentProviderClient client) {
+ if (client != null) {
+ try {
+ client.release();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ 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 00000000..d428a3a8
--- /dev/null
+++ b/android/content/ContentProviderNative.java
@@ -0,0 +1,797 @@
+/*
+ * 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.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.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.
+ */
+ 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();
+ 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, 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 INSERT_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+
+ Uri out = insert(callingPkg, url, values);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case BULK_INSERT_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
+
+ int count = bulkInsert(callingPkg, url, values);
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case APPLY_BATCH_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = 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, operations);
+ reply.writeNoException();
+ reply.writeTypedArray(results, 0);
+ return true;
+ }
+
+ case DELETE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String selection = data.readString();
+ String[] selectionArgs = data.readStringArray();
+
+ int count = delete(callingPkg, url, selection, selectionArgs);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case UPDATE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+ String selection = data.readString();
+ String[] selectionArgs = data.readStringArray();
+
+ int count = update(callingPkg, url, values, selection, selectionArgs);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case OPEN_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = 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, 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();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(callingPkg, 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 method = data.readString();
+ String stringArg = data.readString();
+ Bundle args = data.readBundle();
+
+ Bundle responseBundle = call(callingPkg, method, stringArg, args);
+
+ 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();
+ 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, 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();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = canonicalize(callingPkg, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case UNCANONICALIZE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = uncanonicalize(callingPkg, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case REFRESH_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ Bundle args = data.readBundle();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
+
+ boolean out = refresh(callingPkg, url, args, signal);
+ reply.writeNoException();
+ reply.writeInt(out ? 0 : -1);
+ 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, 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);
+ 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
+ public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+ values.writeToParcel(data, 0);
+
+ 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, Uri url, ContentValues[] values) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ 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,
+ ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
+ 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, Uri url, String selection, String[] selectionArgs)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+ data.writeString(selection);
+ data.writeStringArray(selectionArgs);
+
+ 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, Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+ values.writeToParcel(data, 0);
+ data.writeString(selection);
+ data.writeStringArray(selectionArgs);
+
+ 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, 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);
+ 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, 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);
+ 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, String method, String request, Bundle args)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(method);
+ data.writeString(request);
+ data.writeBundle(args);
+
+ 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, 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);
+ 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, Uri url) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ 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
+ public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ 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, Uri url, Bundle args, ICancellationSignal signal)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+ data.writeBundle(args);
+ 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();
+ }
+ }
+
+ private IBinder mRemote;
+}
diff --git a/android/content/ContentProviderOperation.java b/android/content/ContentProviderOperation.java
new file mode 100644
index 00000000..8f3a3174
--- /dev/null
+++ b/android/content/ContentProviderOperation.java
@@ -0,0 +1,691 @@
+/*
+ * 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.content.ContentProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 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 */
+ public final static int TYPE_INSERT = 1;
+ /** @hide exposed for unit tests */
+ public final static int TYPE_UPDATE = 2;
+ /** @hide exposed for unit tests */
+ public final static int TYPE_DELETE = 3;
+ /** @hide exposed for unit tests */
+ public final static int TYPE_ASSERT = 4;
+
+ private final int mType;
+ private final Uri mUri;
+ private final String mSelection;
+ private final String[] mSelectionArgs;
+ private final ContentValues mValues;
+ private final Integer mExpectedCount;
+ private final ContentValues mValuesBackReferences;
+ private final Map<Integer, Integer> mSelectionArgsBackReferences;
+ private final boolean mYieldAllowed;
+
+ 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;
+ mValues = builder.mValues;
+ mSelection = builder.mSelection;
+ mSelectionArgs = builder.mSelectionArgs;
+ mExpectedCount = builder.mExpectedCount;
+ mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences;
+ mValuesBackReferences = builder.mValuesBackReferences;
+ mYieldAllowed = builder.mYieldAllowed;
+ }
+
+ private ContentProviderOperation(Parcel source) {
+ mType = source.readInt();
+ mUri = Uri.CREATOR.createFromParcel(source);
+ mValues = source.readInt() != 0 ? ContentValues.CREATOR.createFromParcel(source) : null;
+ mSelection = source.readInt() != 0 ? source.readString() : null;
+ mSelectionArgs = source.readInt() != 0 ? source.readStringArray() : null;
+ mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
+ mValuesBackReferences = source.readInt() != 0
+ ? ContentValues.CREATOR.createFromParcel(source)
+ : null;
+ mSelectionArgsBackReferences = source.readInt() != 0
+ ? new HashMap<Integer, Integer>()
+ : null;
+ if (mSelectionArgsBackReferences != null) {
+ final int count = source.readInt();
+ for (int i = 0; i < count; i++) {
+ mSelectionArgsBackReferences.put(source.readInt(), source.readInt());
+ }
+ }
+ mYieldAllowed = source.readInt() != 0;
+ }
+
+ /** @hide */
+ public ContentProviderOperation(ContentProviderOperation cpo, boolean removeUserIdFromUri) {
+ mType = cpo.mType;
+ if (removeUserIdFromUri) {
+ mUri = ContentProvider.getUriWithoutUserId(cpo.mUri);
+ } else {
+ mUri = cpo.mUri;
+ }
+ mValues = cpo.mValues;
+ mSelection = cpo.mSelection;
+ mSelectionArgs = cpo.mSelectionArgs;
+ mExpectedCount = cpo.mExpectedCount;
+ mSelectionArgsBackReferences = cpo.mSelectionArgsBackReferences;
+ mValuesBackReferences = cpo.mValuesBackReferences;
+ mYieldAllowed = cpo.mYieldAllowed;
+ }
+
+ /** @hide */
+ public ContentProviderOperation getWithoutUserIdInUri() {
+ if (ContentProvider.uriHasUserId(mUri)) {
+ return new ContentProviderOperation(this, true);
+ }
+ return this;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ Uri.writeToParcel(dest, mUri);
+ if (mValues != null) {
+ dest.writeInt(1);
+ mValues.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSelection != null) {
+ dest.writeInt(1);
+ dest.writeString(mSelection);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSelectionArgs != null) {
+ dest.writeInt(1);
+ dest.writeStringArray(mSelectionArgs);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mExpectedCount != null) {
+ dest.writeInt(1);
+ dest.writeInt(mExpectedCount);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mValuesBackReferences != null) {
+ dest.writeInt(1);
+ mValuesBackReferences.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSelectionArgsBackReferences != null) {
+ dest.writeInt(1);
+ dest.writeInt(mSelectionArgsBackReferences.size());
+ for (Map.Entry<Integer, Integer> entry : mSelectionArgsBackReferences.entrySet()) {
+ dest.writeInt(entry.getKey());
+ dest.writeInt(entry.getValue());
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mYieldAllowed ? 1 : 0);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building an insert {@link ContentProviderOperation}.
+ * @param uri The {@link Uri} that is the target of the insert.
+ * @return a {@link Builder}
+ */
+ public static Builder newInsert(Uri uri) {
+ return new Builder(TYPE_INSERT, uri);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building an update {@link ContentProviderOperation}.
+ * @param uri The {@link Uri} that is the target of the update.
+ * @return a {@link Builder}
+ */
+ public static Builder newUpdate(Uri uri) {
+ return new Builder(TYPE_UPDATE, uri);
+ }
+
+ /**
+ * Create a {@link Builder} suitable for building a delete {@link ContentProviderOperation}.
+ * @param uri The {@link Uri} that is the target of the delete.
+ * @return a {@link Builder}
+ */
+ public static Builder newDelete(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 Builder newAssertQuery(Uri uri) {
+ return new Builder(TYPE_ASSERT, uri);
+ }
+
+ /**
+ * Gets the Uri for the target of the operation.
+ */
+ public 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;
+ }
+
+ /** @hide exposed for unit tests */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns true if the operation represents an insertion.
+ *
+ * @see #newInsert
+ */
+ public boolean isInsert() {
+ return mType == TYPE_INSERT;
+ }
+
+ /**
+ * Returns true if the operation represents a deletion.
+ *
+ * @see #newDelete
+ */
+ public boolean isDelete() {
+ return mType == TYPE_DELETE;
+ }
+
+ /**
+ * Returns true if the operation represents an update.
+ *
+ * @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 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 ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs,
+ int numBackRefs) throws OperationApplicationException {
+ ContentValues values = resolveValueBackReferences(backRefs, numBackRefs);
+ String[] selectionArgs =
+ resolveSelectionArgsBackReferences(backRefs, numBackRefs);
+
+ if (mType == TYPE_INSERT) {
+ Uri newUri = provider.insert(mUri, values);
+ if (newUri == null) {
+ throw new OperationApplicationException("insert failed");
+ }
+ return new ContentProviderResult(newUri);
+ }
+
+ int numRows;
+ if (mType == TYPE_DELETE) {
+ numRows = provider.delete(mUri, mSelection, selectionArgs);
+ } else if (mType == TYPE_UPDATE) {
+ numRows = provider.update(mUri, values, mSelection, selectionArgs);
+ } 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, mSelection, selectionArgs, 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
+ Log.e(TAG, this.toString());
+ throw new OperationApplicationException("Found value " + cursorValue
+ + " when expected " + expectedValue + " for column "
+ + projection[i]);
+ }
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ } else {
+ Log.e(TAG, this.toString());
+ throw new IllegalStateException("bad type, " + mType);
+ }
+
+ if (mExpectedCount != null && mExpectedCount != numRows) {
+ Log.e(TAG, this.toString());
+ throw new OperationApplicationException("wrong number of rows: " + numRows);
+ }
+
+ return new ContentProviderResult(numRows);
+ }
+
+ /**
+ * The ContentValues back references are represented as a ContentValues object where the
+ * key refers to a column and the value is an index of the back reference whose
+ * valued should be associated with the column.
+ * <p>
+ * This is intended to be a private method but it is exposed for
+ * unit testing purposes
+ * @param backRefs an array of previous results
+ * @param numBackRefs the number of valid previous results in backRefs
+ * @return the ContentValues that should be used in this operation application after
+ * expansion of back references. This can be called if either mValues or mValuesBackReferences
+ * is null
+ */
+ public ContentValues resolveValueBackReferences(
+ ContentProviderResult[] backRefs, int numBackRefs) {
+ if (mValuesBackReferences == null) {
+ return mValues;
+ }
+ final ContentValues values;
+ if (mValues == null) {
+ values = new ContentValues();
+ } else {
+ values = new ContentValues(mValues);
+ }
+ for (Map.Entry<String, Object> entry : mValuesBackReferences.valueSet()) {
+ String key = entry.getKey();
+ Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
+ if (backRefIndex == null) {
+ Log.e(TAG, this.toString());
+ throw new IllegalArgumentException("values backref " + key + " is not an integer");
+ }
+ values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
+ }
+ return values;
+ }
+
+ /**
+ * The Selection Arguments back references are represented as a Map of Integer->Integer where
+ * the key is an index into the selection argument array (see {@link Builder#withSelection})
+ * and the value is the index of the previous result that should be used for that selection
+ * argument array slot.
+ * <p>
+ * This is intended to be a private method but it is exposed for
+ * unit testing purposes
+ * @param backRefs an array of previous results
+ * @param numBackRefs the number of valid previous results in backRefs
+ * @return the ContentValues that should be used in this operation application after
+ * expansion of back references. This can be called if either mValues or mValuesBackReferences
+ * is null
+ */
+ public String[] resolveSelectionArgsBackReferences(
+ ContentProviderResult[] backRefs, int numBackRefs) {
+ if (mSelectionArgsBackReferences == null) {
+ return mSelectionArgs;
+ }
+ String[] newArgs = new String[mSelectionArgs.length];
+ System.arraycopy(mSelectionArgs, 0, newArgs, 0, mSelectionArgs.length);
+ for (Map.Entry<Integer, Integer> selectionArgBackRef
+ : mSelectionArgsBackReferences.entrySet()) {
+ final Integer selectionArgIndex = selectionArgBackRef.getKey();
+ final int backRefIndex = selectionArgBackRef.getValue();
+ newArgs[selectionArgIndex] =
+ String.valueOf(backRefToValue(backRefs, numBackRefs, backRefIndex));
+ }
+ return newArgs;
+ }
+
+ @Override
+ public String toString() {
+ return "mType: " + mType + ", mUri: " + mUri +
+ ", mSelection: " + mSelection +
+ ", mExpectedCount: " + mExpectedCount +
+ ", mYieldAllowed: " + mYieldAllowed +
+ ", mValues: " + mValues +
+ ", mValuesBackReferences: " + mValuesBackReferences +
+ ", mSelectionArgsBackReferences: " + mSelectionArgsBackReferences;
+ }
+
+ /**
+ * Return the string representation of the requested back reference.
+ * @param backRefs an array of results
+ * @param numBackRefs the number of items in the backRefs array that are valid
+ * @param backRefIndex which backRef to be used
+ * @throws ArrayIndexOutOfBoundsException thrown if the backRefIndex is larger than
+ * the numBackRefs
+ * @return the string representation of the requested back reference.
+ */
+ private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
+ Integer backRefIndex) {
+ if (backRefIndex >= numBackRefs) {
+ Log.e(TAG, this.toString());
+ throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
+ + " but there are only " + numBackRefs + " back refs");
+ }
+ ContentProviderResult backRef = backRefs[backRefIndex];
+ long backRefValue;
+ if (backRef.uri != null) {
+ backRefValue = ContentUris.parseId(backRef.uri);
+ } else {
+ backRefValue = backRef.count;
+ }
+ return backRefValue;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ContentProviderOperation> CREATOR =
+ new Creator<ContentProviderOperation>() {
+ public ContentProviderOperation createFromParcel(Parcel source) {
+ return new ContentProviderOperation(source);
+ }
+
+ public ContentProviderOperation[] newArray(int size) {
+ return new ContentProviderOperation[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 String mSelection;
+ private String[] mSelectionArgs;
+ private ContentValues mValues;
+ private Integer mExpectedCount;
+ private ContentValues mValuesBackReferences;
+ private Map<Integer, Integer> mSelectionArgsBackReferences;
+ private boolean mYieldAllowed;
+
+ /** Create a {@link Builder} of a given type. The uri must not be null. */
+ private Builder(int type, Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("uri must not be null");
+ }
+ mType = type;
+ mUri = uri;
+ }
+
+ /** Create a ContentProviderOperation from this {@link Builder}. */
+ public ContentProviderOperation build() {
+ if (mType == TYPE_UPDATE) {
+ if ((mValues == null || mValues.isEmpty())
+ && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
+ throw new IllegalArgumentException("Empty values");
+ }
+ }
+ if (mType == TYPE_ASSERT) {
+ if ((mValues == null || mValues.isEmpty())
+ && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
+ && (mExpectedCount == null)) {
+ throw new IllegalArgumentException("Empty values");
+ }
+ }
+ return new ContentProviderOperation(this);
+ }
+
+ /**
+ * Add a {@link ContentValues} of back references. The key is the name of the column
+ * and the value is an integer that is the index of the previous result whose
+ * value should be used for the column. The value is added as a {@link String}.
+ * A column value from the back references takes precedence over a value specified in
+ * {@link #withValues}.
+ * This can only be used with builders of type insert, update, or assert.
+ * @return this builder, to allow for chaining.
+ */
+ public Builder withValueBackReferences(ContentValues backReferences) {
+ if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException(
+ "only inserts, updates, and asserts can have value back-references");
+ }
+ mValuesBackReferences = backReferences;
+ return this;
+ }
+
+ /**
+ * Add a ContentValues back reference.
+ * A column value from the back references takes precedence over a value specified in
+ * {@link #withValues}.
+ * This can only be used with builders of type insert, update, or assert.
+ * @return this builder, to allow for chaining.
+ */
+ public Builder withValueBackReference(String key, int previousResult) {
+ if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException(
+ "only inserts, updates, and asserts can have value back-references");
+ }
+ if (mValuesBackReferences == null) {
+ mValuesBackReferences = new ContentValues();
+ }
+ mValuesBackReferences.put(key, previousResult);
+ return this;
+ }
+
+ /**
+ * Add a back references as a selection arg. Any value at that index of the selection arg
+ * that was specified by {@link #withSelection} will be overwritten.
+ * This can only be used with builders of type update, delete, or assert.
+ * @return this builder, to allow for chaining.
+ */
+ public Builder withSelectionBackReference(int selectionArgIndex, int previousResult) {
+ if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException("only updates, deletes, and asserts "
+ + "can have selection back-references");
+ }
+ if (mSelectionArgsBackReferences == null) {
+ mSelectionArgsBackReferences = new HashMap<Integer, Integer>();
+ }
+ mSelectionArgsBackReferences.put(selectionArgIndex, previousResult);
+ return this;
+ }
+
+ /**
+ * The ContentValues to use. This may be null. These values may be overwritten by
+ * the corresponding value specified by {@link #withValueBackReference} or by
+ * future calls to {@link #withValues} or {@link #withValue}.
+ * This can only be used with builders of type insert, update, or assert.
+ * @return this builder, to allow for chaining.
+ */
+ public Builder withValues(ContentValues values) {
+ if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException(
+ "only inserts, updates, and asserts can have values");
+ }
+ if (mValues == null) {
+ mValues = new ContentValues();
+ }
+ mValues.putAll(values);
+ return this;
+ }
+
+ /**
+ * A value to insert or update. This value may be overwritten by
+ * the corresponding value specified by {@link #withValueBackReference}.
+ * This can only be used with builders of type insert, update, or assert.
+ * @param key the name of this value
+ * @param value the value itself. the type must be acceptable for insertion by
+ * {@link ContentValues#put}
+ * @return this builder, to allow for chaining.
+ */
+ public Builder withValue(String key, Object value) {
+ if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException("only inserts and updates can have values");
+ }
+ if (mValues == null) {
+ mValues = new ContentValues();
+ }
+ if (value == null) {
+ mValues.putNull(key);
+ } else if (value instanceof String) {
+ mValues.put(key, (String) value);
+ } else if (value instanceof Byte) {
+ mValues.put(key, (Byte) value);
+ } else if (value instanceof Short) {
+ mValues.put(key, (Short) value);
+ } else if (value instanceof Integer) {
+ mValues.put(key, (Integer) value);
+ } else if (value instanceof Long) {
+ mValues.put(key, (Long) value);
+ } else if (value instanceof Float) {
+ mValues.put(key, (Float) value);
+ } else if (value instanceof Double) {
+ mValues.put(key, (Double) value);
+ } else if (value instanceof Boolean) {
+ mValues.put(key, (Boolean) value);
+ } else if (value instanceof byte[]) {
+ mValues.put(key, (byte[]) value);
+ } else {
+ throw new IllegalArgumentException("bad value type: " + value.getClass().getName());
+ }
+ return this;
+ }
+
+ /**
+ * The selection and arguments to use. An occurrence of '?' in the selection will be
+ * replaced with the corresponding occurence of the selection argument. Any of the
+ * selection arguments may be overwritten by a selection argument back reference as
+ * specified by {@link #withSelectionBackReference}.
+ * This can only be used with builders of type update, delete, or assert.
+ * @return this builder, to allow for chaining.
+ */
+ public Builder withSelection(String selection, String[] selectionArgs) {
+ if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
+ throw new IllegalArgumentException(
+ "only updates, deletes, and asserts can have selections");
+ }
+ mSelection = selection;
+ if (selectionArgs == null) {
+ mSelectionArgs = null;
+ } else {
+ mSelectionArgs = new String[selectionArgs.length];
+ System.arraycopy(selectionArgs, 0, mSelectionArgs, 0, selectionArgs.length);
+ }
+ 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 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 Builder withYieldAllowed(boolean yieldAllowed) {
+ mYieldAllowed = yieldAllowed;
+ return this;
+ }
+ }
+}
diff --git a/android/content/ContentProviderResult.java b/android/content/ContentProviderResult.java
new file mode 100644
index 00000000..4196f27f
--- /dev/null
+++ b/android/content/ContentProviderResult.java
@@ -0,0 +1,91 @@
+/*
+ * 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.content.ContentProvider;
+import android.net.Uri;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * Contains the result of the application of a {@link ContentProviderOperation}. It is guaranteed
+ * to have exactly one of {@link #uri} or {@link #count} set.
+ */
+public class ContentProviderResult implements Parcelable {
+ public final Uri uri;
+ public final Integer count;
+
+ public ContentProviderResult(Uri uri) {
+ if (uri == null) throw new IllegalArgumentException("uri must not be null");
+ this.uri = uri;
+ this.count = null;
+ }
+
+ public ContentProviderResult(int count) {
+ this.count = count;
+ this.uri = null;
+ }
+
+ public ContentProviderResult(Parcel source) {
+ int type = source.readInt();
+ if (type == 1) {
+ count = source.readInt();
+ uri = null;
+ } else {
+ count = null;
+ uri = Uri.CREATOR.createFromParcel(source);
+ }
+ }
+
+ /** @hide */
+ public ContentProviderResult(ContentProviderResult cpr, int userId) {
+ uri = ContentProvider.maybeAddUserId(cpr.uri, userId);
+ count = cpr.count;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ if (uri == null) {
+ dest.writeInt(1);
+ dest.writeInt(count);
+ } else {
+ dest.writeInt(2);
+ uri.writeToParcel(dest, 0);
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ContentProviderResult> CREATOR =
+ new Creator<ContentProviderResult>() {
+ public ContentProviderResult createFromParcel(Parcel source) {
+ return new ContentProviderResult(source);
+ }
+
+ public ContentProviderResult[] newArray(int size) {
+ return new ContentProviderResult[size];
+ }
+ };
+
+ public String toString() {
+ if (uri != null) {
+ return "ContentProviderResult(uri=" + uri.toString() + ")";
+ }
+ return "ContentProviderResult(count=" + count + ")";
+ }
+}
diff --git a/android/content/ContentQueryMap.java b/android/content/ContentQueryMap.java
new file mode 100644
index 00000000..8aeaa8f8
--- /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 00000000..9ccc552f
--- /dev/null
+++ b/android/content/ContentResolver.java
@@ -0,0 +1,3080 @@
+/*
+ * 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.accounts.Account;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.AppGlobals;
+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.Point;
+import android.graphics.drawable.Drawable;
+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.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.MimeIconUtils;
+import com.android.internal.util.Preconditions;
+
+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.List;
+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>
+ */
+public abstract class ContentResolver {
+ /**
+ * @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";
+
+ /**
+ * 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
+ */
+ 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
+ */
+ 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
+ */
+ public static final String QUERY_ARG_SQL_SORT_ORDER = "android:query-arg-sql-sort-order";
+
+ /**
+ * Specifies the list of columns 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 text values.
+ *
+ * <p>Providers may support custom collators. When specifying a custom collator
+ * the value is determined by the Provider.
+ *
+ * <li>{@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}.
+ *
+ * <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 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 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
+ */
+ public static final String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
+
+ /** @hide */
+ @IntDef(flag = false, 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 */
+ 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 */
+ public static final int SYNC_OBSERVER_TYPE_STATUS = 1<<3;
+ /** @hide */
+ public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ NOTIFY_SYNC_TO_NETWORK,
+ NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS
+ })
+ @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;
+
+ // 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
+
+ public ContentResolver(Context context) {
+ mContext = context != null ? context : ActivityThread.currentApplication();
+ mPackageName = mContext.getOpPackageName();
+ mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ }
+
+ /** @hide */
+ 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
+ */
+ protected IContentProvider acquireExistingProvider(Context c, String name) {
+ return acquireProvider(c, name);
+ }
+
+ /** @hide */
+ public abstract boolean releaseProvider(IContentProvider icp);
+ /** @hide */
+ protected abstract IContentProvider acquireUnstableProvider(Context c, String name);
+ /** @hide */
+ public abstract boolean releaseUnstableProvider(IContentProvider icp);
+ /** @hide */
+ 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
+ */
+ public final @Nullable String getType(@NonNull Uri url) {
+ Preconditions.checkNotNull(url, "url");
+
+ // XXX would like to have an acquireExistingUnstableProvider for this.
+ IContentProvider provider = acquireExistingProvider(url);
+ if (provider != null) {
+ try {
+ return provider.getType(url);
+ } catch (RemoteException e) {
+ 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 {
+ String type = ActivityManager.getService().getProviderMimeType(
+ ContentProvider.getUriWithoutUserId(url), resolveUserId(url));
+ return type;
+ } 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;
+ }
+ }
+
+ /**
+ * 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 *&#47;*, 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.
+ */
+ public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter) {
+ Preconditions.checkNotNull(url, "url");
+ Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
+
+ 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, or null
+ * @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, or null
+ * @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, #QUERY_ARG_SORT_DIRECTION, #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 any arguments to the query.
+ * @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, or null
+ * @see Cursor
+ */
+ public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
+ @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable CancellationSignal cancellationSignal) {
+ Preconditions.checkNotNull(uri, "uri");
+ 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, 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, 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);
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ public final @Nullable Uri canonicalize(@NonNull Uri url) {
+ Preconditions.checkNotNull(url, "url");
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ return provider.canonicalize(mPackageName, url);
+ } 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
+ */
+ public final @Nullable Uri uncanonicalize(@NonNull Uri url) {
+ Preconditions.checkNotNull(url, "url");
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ return provider.uncanonicalize(mPackageName, 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 args 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.
+ */
+ public final boolean refresh(@NonNull Uri url, @Nullable Bundle args,
+ @Nullable CancellationSignal cancellationSignal) {
+ Preconditions.checkNotNull(url, "url");
+ 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, url, args, remoteCancellationSignal);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return false;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * 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 {
+ Preconditions.checkNotNull(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");
+ }
+ }
+
+ /**
+ * 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 {
+ 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");
+ }
+
+ /**
+ * 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 "*&#47;*". 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 "*&#47;*". 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 {
+ Preconditions.checkNotNull(uri, "uri");
+ Preconditions.checkNotNull(mode, "mode");
+
+ 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, 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, 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);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 *&#47;*, 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 *&#47;*, 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 {
+ Preconditions.checkNotNull(uri, "uri");
+ Preconditions.checkNotNull(mimeType, "mimeType");
+
+ 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, 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, 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());
+
+ } 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 {
+ public Resources r;
+ public int id;
+ }
+
+ /**
+ * Resolves an android.resource URI to a {@link Resources} and a resource id.
+ *
+ * @hide
+ */
+ 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.
+ */
+ public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
+ @Nullable ContentValues values) {
+ Preconditions.checkNotNull(url, "url");
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ Uri createdRow = provider.insert(mPackageName, url, values);
+ 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.
+ */
+ public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
+ @NonNull ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
+ Preconditions.checkNotNull(authority, "authority");
+ Preconditions.checkNotNull(operations, "operations");
+ 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.
+ */
+ public final int bulkInsert(@RequiresPermission.Write @NonNull Uri url,
+ @NonNull ContentValues[] values) {
+ Preconditions.checkNotNull(url, "url");
+ Preconditions.checkNotNull(values, "values");
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ int rowsCreated = provider.bulkInsert(mPackageName, 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) {
+ Preconditions.checkNotNull(url, "url");
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
+ 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
+ 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ Preconditions.checkNotNull(method, "method");
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ final Bundle res = provider.call(mPackageName, 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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 it 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = acquireProvider(uri);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, 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 it 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) {
+ Preconditions.checkNotNull(name, "name");
+ IContentProvider provider = acquireProvider(name);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = acquireUnstableProvider(uri);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, 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) {
+ Preconditions.checkNotNull(name, "name");
+ IContentProvider provider = acquireUnstableProvider(name);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider, 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ Preconditions.checkNotNull(observer, "observer");
+ registerContentObserver(
+ ContentProvider.getUriWithoutUserId(uri),
+ notifyForDescendants,
+ observer,
+ ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+ }
+
+ /** @hide - designated user version */
+ 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) {
+ }
+ }
+
+ /**
+ * Unregisters a change observer.
+ *
+ * @param observer The previously registered observer that is no longer needed.
+ * @see #registerContentObserver
+ */
+ public final void unregisterContentObserver(@NonNull ContentObserver observer) {
+ Preconditions.checkNotNull(observer, "observer");
+ try {
+ IContentObserver contentObserver = observer.releaseContentObserver();
+ if (contentObserver != null) {
+ getContentService().unregisterContentObserver(
+ contentObserver);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * 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)
+ */
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
+ boolean syncToNetwork) {
+ Preconditions.checkNotNull(uri, "uri");
+ notifyChange(
+ ContentProvider.getUriWithoutUserId(uri),
+ observer,
+ syncToNetwork,
+ ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+ }
+
+ /**
+ * 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 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ notifyChange(
+ ContentProvider.getUriWithoutUserId(uri),
+ observer,
+ flags,
+ ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+ }
+
+ /**
+ * Notify registered observers within the designated user(s) that a row was updated.
+ *
+ * @hide
+ */
+ public void notifyChange(@NonNull Uri uri, ContentObserver observer, boolean syncToNetwork,
+ @UserIdInt int userHandle) {
+ try {
+ getContentService().notifyChange(
+ uri, observer == null ? null : observer.getContentObserver(),
+ observer != null && observer.deliverSelfNotifications(),
+ syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
+ userHandle, mTargetSdkVersion);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Notify registered observers within the designated user(s) that a row was updated.
+ *
+ * @hide
+ */
+ public void notifyChange(@NonNull Uri uri, ContentObserver observer, @NotifyFlags int flags,
+ @UserIdInt int userHandle) {
+ try {
+ getContentService().notifyChange(
+ uri, observer == null ? null : observer.getContentObserver(),
+ observer != null && observer.deliverSelfNotifications(), flags,
+ userHandle, mTargetSdkVersion);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ try {
+ ActivityManager.getService().takePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * 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) {
+ Preconditions.checkNotNull(uri, "uri");
+ try {
+ ActivityManager.getService().releasePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * 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 ActivityManager.getService()
+ .getPersistedUriPermissions(mPackageName, true).getList();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Activity manager has died", e);
+ }
+ }
+
+ /**
+ * 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 ActivityManager.getService()
+ .getPersistedUriPermissions(mPackageName, false).getList();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Activity manager has died", e);
+ }
+ }
+
+ /**
+ * 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 {
+ getContentService().syncAsUser(request, userId);
+ } catch(RemoteException e) {
+ // Shouldn't happen.
+ }
+ }
+
+ /**
+ * Register a sync with the SyncManager. These requests are built using the
+ * {@link SyncRequest.Builder}.
+ */
+ public static void requestSync(SyncRequest request) {
+ try {
+ getContentService().sync(request);
+ } catch(RemoteException e) {
+ // Shouldn't happen.
+ }
+ }
+
+ /**
+ * 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) {
+ }
+ }
+
+ /**
+ * @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) {
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @see #getSyncAdapterTypes()
+ * @hide
+ */
+ public static SyncAdapterType[] getSyncAdapterTypesAsUser(@UserIdInt int userId) {
+ try {
+ return getContentService().getSyncAdapterTypesAsUser(userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @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) {
+ }
+ return ArrayUtils.emptyArray(String.class);
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * 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. A minimum value
+ * of 1 hour is enforced.
+ * @throws IllegalArgumentException if an illegal extra was set or if any of the parameters
+ * are null.
+ */
+ public static void addPeriodicSync(Account account, String authority, Bundle extras,
+ long pollFrequency) {
+ validateSyncExtrasBundle(extras);
+ if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
+ || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
+ || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
+ || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false)
+ || extras.getBoolean(SYNC_EXTRAS_FORCE, false)
+ || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) {
+ throw new IllegalArgumentException("illegal extras were set");
+ }
+ try {
+ getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
+ } catch (RemoteException e) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * {@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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @see #getMasterSyncAutomatically()
+ * @hide
+ */
+ public static boolean getMasterSyncAutomaticallyAsUser(@UserIdInt int userId) {
+ try {
+ return getContentService().getMasterSyncAutomaticallyAsUser(userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @see #getCurrentSyncs()
+ * @hide
+ */
+ public static List<SyncInfo> getCurrentSyncsAsUser(@UserIdInt int userId) {
+ try {
+ return getContentService().getCurrentSyncsAsUser(userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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
+ */
+ public static SyncStatusInfo getSyncStatus(Account account, String authority) {
+ try {
+ return getContentService().getSyncStatus(account, authority, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * @see #getSyncStatus(Account, String)
+ * @hide
+ */
+ public static SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+ @UserIdInt int userId) {
+ try {
+ return getContentService().getSyncStatusAsUser(account, authority, null, userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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 new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * 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) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /** {@hide} */
+ public void putCache(Uri key, Bundle value) {
+ try {
+ getContentService().putCache(mContext.getPackageName(), key, value,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public Bundle getCache(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 */
+ public static IContentService getContentService() {
+ if (sContentService != null) {
+ return sContentService;
+ }
+ IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
+ if (false) Log.v("ContentService", "default service binder = " + b);
+ sContentService = IContentService.Stub.asInterface(b);
+ if (false) Log.v("ContentService", "default service = " + sContentService);
+ return sContentService;
+ }
+
+ /** @hide */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ private static IContentService sContentService;
+ private final Context mContext;
+
+ final String mPackageName;
+ final int mTargetSdkVersion;
+
+ private static final String TAG = "ContentResolver";
+
+ /** @hide */
+ public int resolveUserId(Uri uri) {
+ return ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+ }
+
+ /** @hide */
+ public Drawable getTypeDrawable(String mimeType) {
+ return MimeIconUtils.loadMimeIcon(mContext, mimeType);
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/android/content/ContentUris.java b/android/content/ContentUris.java
new file mode 100644
index 00000000..dbe8a7c3
--- /dev/null
+++ b/android/content/ContentUris.java
@@ -0,0 +1,112 @@
+/*
+ * 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.net.Uri;
+
+/**
+* Utility methods useful for working with {@link android.net.Uri} objects
+* that use the &quot;content&quot; (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
+* &quot;directories&quot; although they do not refer to file directories. The right-most
+* segment in a path is often called a &quot;twig&quot;
+* </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(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 Uri.Builder appendId(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 Uri withAppendedId(Uri contentUri, long id) {
+ return appendId(contentUri.buildUpon(), id).build();
+ }
+}
diff --git a/android/content/ContentValues.java b/android/content/ContentValues.java
new file mode 100644
index 00000000..6f937989
--- /dev/null
+++ b/android/content/ContentValues.java
@@ -0,0 +1,533 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+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";
+
+ /** Holds the actual values */
+ private HashMap<String, Object> mValues;
+
+ /**
+ * Creates an empty set of values using the default initial size
+ */
+ public ContentValues() {
+ // Choosing a default size of 8 based on analysis of typical
+ // consumption by applications.
+ mValues = new HashMap<String, Object>(8);
+ }
+
+ /**
+ * 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) {
+ mValues = new HashMap<String, Object>(size, 1.0f);
+ }
+
+ /**
+ * Creates a set of values copied from the given set
+ *
+ * @param from the values to copy
+ */
+ public ContentValues(ContentValues from) {
+ mValues = new HashMap<String, Object>(from.mValues);
+ }
+
+ /**
+ * Creates a set of values copied from the given HashMap. This is used
+ * by the Parcel unmarshalling code.
+ *
+ * @param values the values to start with
+ * {@hide}
+ */
+ private ContentValues(HashMap<String, Object> values) {
+ mValues = values;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof ContentValues)) {
+ return false;
+ }
+ return mValues.equals(((ContentValues) object).mValues);
+ }
+
+ @Override
+ public int hashCode() {
+ return mValues.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) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Adds all values from the passed in ContentValues.
+ *
+ * @param other the ContentValues from which to copy
+ */
+ public void putAll(ContentValues other) {
+ mValues.putAll(other.mValues);
+ }
+
+ /**
+ * 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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.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) {
+ mValues.put(key, null);
+ }
+
+ /**
+ * Returns the number of values.
+ *
+ * @return the number of values
+ */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * Indicates whether this collection is empty.
+ *
+ * @return true iff size == 0
+ * {@hide}
+ * TODO: consider exposing this new method publicly
+ */
+ public boolean isEmpty() {
+ return mValues.isEmpty();
+ }
+
+ /**
+ * Remove a single value.
+ *
+ * @param key the name of the value to remove
+ */
+ public void remove(String key) {
+ mValues.remove(key);
+ }
+
+ /**
+ * Removes all values.
+ */
+ public void clear() {
+ mValues.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 mValues.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 mValues.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 = mValues.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 = mValues.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 = mValues.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 = mValues.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 = mValues.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 = mValues.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 = mValues.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 = mValues.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 = mValues.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 mValues.entrySet();
+ }
+
+ /**
+ * Returns a set of all of the keys
+ *
+ * @return a set of all of the keys
+ */
+ public Set<String> keySet() {
+ return mValues.keySet();
+ }
+
+ public static final Parcelable.Creator<ContentValues> CREATOR =
+ new Parcelable.Creator<ContentValues>() {
+ @SuppressWarnings({"deprecation", "unchecked"})
+ public ContentValues createFromParcel(Parcel in) {
+ // TODO - what ClassLoader should be passed to readHashMap?
+ HashMap<String, Object> values = in.readHashMap(null);
+ return new ContentValues(values);
+ }
+
+ public ContentValues[] newArray(int size) {
+ return new ContentValues[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeMap(mValues);
+ }
+
+ /**
+ * Unsupported, here until we get proper bulk insert APIs.
+ * {@hide}
+ */
+ @Deprecated
+ public void putStringArrayList(String key, ArrayList<String> value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Unsupported, here until we get proper bulk insert APIs.
+ * {@hide}
+ */
+ @SuppressWarnings("unchecked")
+ @Deprecated
+ public ArrayList<String> getStringArrayList(String key) {
+ return (ArrayList<String>) mValues.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 : mValues.keySet()) {
+ String value = getAsString(name);
+ if (sb.length() > 0) sb.append(" ");
+ sb.append(name + "=" + value);
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/content/Context.java b/android/content/Context.java
new file mode 100644
index 00000000..2d8249ac
--- /dev/null
+++ b/android/content/Context.java
@@ -0,0 +1,4780 @@
+/*
+ * 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.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.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.app.VrManager;
+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.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+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.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.textclassifier.TextClassificationManager;
+
+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;
+
+/**
+ * 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
+ * targetting such releases. For applications targetting 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,
+ 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
+ })
+ @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;
+
+ /**
+ * @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 importantance 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;
+
+ /** @hide */
+ @IntDef(flag = true,
+ 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 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
+ */
+ 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, or
+ * {@code null} if the resource could not be resolved.
+ * @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, or {@code null} if the resource could not be
+ * resolved.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @Nullable
+ 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. */
+ 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[])
+ */
+ public final TypedArray obtainStyledAttributes(@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[])
+ */
+ public final TypedArray obtainStyledAttributes(
+ @StyleRes int resid, @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)
+ */
+ public final TypedArray obtainStyledAttributes(
+ AttributeSet set, @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)
+ */
+ public final TypedArray obtainStyledAttributes(
+ AttributeSet set, @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. */
+ public abstract String getBasePackageName();
+
+ /** @hide Return the package name that should be used for app ops calls from
+ * this context. This is the same as {@link #getBasePackageName()} except in
+ * cases where system components are loaded into other app processes, in which
+ * case this will be the name of the primary package in that process (so that app
+ * ops uid verification will work with the name). */
+ public abstract String getOpPackageName();
+
+ /** 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
+ 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.
+ *
+ * @param name 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 #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 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)
+ */
+ 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 &nbsp;
+ *`
+ * @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 &nbsp;
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public void startActivityAsUser(@RequiresPermission Intent intent, 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 &nbsp;
+ *
+ * @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 &nbsp;
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ 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
+ */
+ 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
+ */
+ 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 &nbsp;
+ *
+ * @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 &nbsp;
+ *
+ * @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.
+ *
+ * @throws ActivityNotFoundException &nbsp;
+ *
+ * @see #startActivities(Intent[])
+ * @see PackageManager#resolveActivity
+ */
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public void 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 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 sendBroadcastMultiplePermissions(Intent intent,
+ 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
+ */
+ 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
+ */
+ 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)
+ 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)
+ 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)
+ 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);
+
+ /**
+ * <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);
+
+ /**
+ * @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)
+ 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 targetted 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)
+ 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>This function will throw {@link SecurityException} if you do not
+ * have permission to bind to the given service.
+ *
+ * <p class="note">Note: this method <em>can not 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}, or
+ * {@link #BIND_WAIVE_PRIORITY}.
+ * @return If you have successfully bound to the service, {@code true} is returned;
+ * {@code false} is returned if the connection is not made so you will not
+ * receive the service object. However, you should still 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
+ */
+ public abstract boolean bindService(@RequiresPermission Intent service,
+ @NonNull ServiceConnection conn, @BindServiceFlags int flags);
+
+ /**
+ * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
+ * argument for use by system server and other multi-user aware code.
+ * @hide
+ */
+ @SystemApi
+ @SuppressWarnings("unused")
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
+ int flags, UserHandle user) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Same as {@link #bindService(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)
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ Handler handler, UserHandle user) {
+ 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({
+ 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,
+ STORAGE_SERVICE,
+ STORAGE_STATS_SERVICE,
+ WALLPAPER_SERVICE,
+ TIME_ZONE_RULES_MANAGER_SERVICE,
+ VIBRATOR_SERVICE,
+ //@hide: STATUS_BAR_SERVICE,
+ CONNECTIVITY_SERVICE,
+ IPSEC_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_SERVICE,
+ NSD_SERVICE,
+ AUDIO_SERVICE,
+ FINGERPRINT_SERVICE,
+ MEDIA_ROUTER_SERVICE,
+ TELEPHONY_SERVICE,
+ TELEPHONY_SUBSCRIPTION_SERVICE,
+ CARRIER_CONFIG_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,
+ DROPBOX_SERVICE,
+ //@hide: DEVICE_IDLE_CONTROLLER,
+ 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,
+ USER_SERVICE,
+ RESTRICTIONS_SERVICE,
+ APP_OPS_SERVICE,
+ CAMERA_SERVICE,
+ PRINT_SERVICE,
+ CONSUMER_IR_SERVICE,
+ //@hide: TRUST_SERVICE,
+ TV_INPUT_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,
+ COMPANION_DEVICE_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}.
+ * <dt> {@link #LAYOUT_INFLATER_SERVICE} ("layout_inflater")
+ * <dd> A {@link android.view.LayoutInflater} for inflating layout resources
+ * in this context.
+ * <dt> {@link #ACTIVITY_SERVICE} ("activity")
+ * <dd> A {@link android.app.ActivityManager} for interacting with the
+ * global activity state of the system.
+ * <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} ("connection")
+ * <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
+ * handling management of network connections.
+ * <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.)
+ *
+ * @param name The name of the desired service.
+ *
+ * @return The service or 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 #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>
+ *
+ * @param serviceClass The class of the desired service.
+ * @return The service or null if the class is not a supported system service.
+ */
+ @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} 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} to retrieve a
+ * {@link android.os.RecoverySystem} for accessing the recovery system
+ * service.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String RECOVERY_SERVICE = "recovery";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.WindowManager} for accessing the system's window
+ * manager.
+ *
+ * @see #getSystemService
+ * @see android.view.WindowManager
+ */
+ public static final String WINDOW_SERVICE = "window";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.LayoutInflater} for inflating layout resources in this
+ * context.
+ *
+ * @see #getSystemService
+ * @see android.view.LayoutInflater
+ */
+ public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.accounts.AccountManager} for receiving intents at a
+ * time of your choosing.
+ *
+ * @see #getSystemService
+ * @see android.accounts.AccountManager
+ */
+ public static final String ACCOUNT_SERVICE = "account";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.ActivityManager} for interacting with the global
+ * system state.
+ *
+ * @see #getSystemService
+ * @see android.app.ActivityManager
+ */
+ public static final String ACTIVITY_SERVICE = "activity";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.AlarmManager} for receiving intents at a
+ * time of your choosing.
+ *
+ * @see #getSystemService
+ * @see android.app.AlarmManager
+ */
+ public static final String ALARM_SERVICE = "alarm";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.NotificationManager} for informing the user of
+ * background events.
+ *
+ * @see #getSystemService
+ * @see android.app.NotificationManager
+ */
+ public static final String NOTIFICATION_SERVICE = "notification";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.accessibility.AccessibilityManager} for giving the user
+ * feedback for UI events through the registered event listeners.
+ *
+ * @see #getSystemService
+ * @see android.view.accessibility.AccessibilityManager
+ */
+ public static final String ACCESSIBILITY_SERVICE = "accessibility";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.accessibility.CaptioningManager} for obtaining
+ * captioning properties and listening for changes in captioning
+ * preferences.
+ *
+ * @see #getSystemService
+ * @see android.view.accessibility.CaptioningManager
+ */
+ public static final String CAPTIONING_SERVICE = "captioning";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.NotificationManager} for controlling keyguard.
+ *
+ * @see #getSystemService
+ * @see android.app.KeyguardManager
+ */
+ public static final String KEYGUARD_SERVICE = "keyguard";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.location.LocationManager} for controlling location
+ * updates.
+ *
+ * @see #getSystemService
+ * @see android.location.LocationManager
+ */
+ public static final String LOCATION_SERVICE = "location";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.location.CountryDetector} for detecting the country that
+ * the user is in.
+ *
+ * @hide
+ */
+ public static final String COUNTRY_DETECTOR = "country_detector";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.app.SearchManager} for handling searches.
+ *
+ * @see #getSystemService
+ * @see android.app.SearchManager
+ */
+ public static final String SEARCH_SERVICE = "search";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.hardware.SensorManager} for accessing sensors.
+ *
+ * @see #getSystemService
+ * @see android.hardware.SensorManager
+ */
+ public static final String SENSOR_SERVICE = "sensor";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.os.storage.StorageManager} for accessing system storage
+ * functions.
+ *
+ * @see #getSystemService
+ * @see android.os.storage.StorageManager
+ */
+ public static final String STORAGE_SERVICE = "storage";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.app.usage.StorageStatsManager} for accessing system storage
+ * statistics.
+ *
+ * @see #getSystemService
+ * @see android.app.usage.StorageStatsManager
+ */
+ public static final String STORAGE_STATS_SERVICE = "storagestats";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * com.android.server.WallpaperService for accessing wallpapers.
+ *
+ * @see #getSystemService
+ */
+ public static final String WALLPAPER_SERVICE = "wallpaper";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.os.Vibrator} for interacting with the vibration hardware.
+ *
+ * @see #getSystemService
+ * @see android.os.Vibrator
+ */
+ public static final String VIBRATOR_SERVICE = "vibrator";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.app.StatusBarManager} for interacting with the status bar.
+ *
+ * @see #getSystemService
+ * @see android.app.StatusBarManager
+ * @hide
+ */
+ public static final String STATUS_BAR_SERVICE = "statusbar";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.ConnectivityManager} for handling management of
+ * network connections.
+ *
+ * @see #getSystemService
+ * @see android.net.ConnectivityManager
+ */
+ public static final String CONNECTIVITY_SERVICE = "connectivity";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
+ * IPSec.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String IPSEC_SERVICE = "ipsec";
+
+ /**
+ * Use with {@link #getSystemService} 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
+ * @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} to retrieve a {@link
+ * android.app.usage.NetworkStatsManager} for querying network usage stats.
+ *
+ * @see #getSystemService
+ * @see android.app.usage.NetworkStatsManager
+ */
+ public static final String NETWORK_STATS_SERVICE = "netstats";
+ /** {@hide} */
+ public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.WifiManager} for handling management of
+ * Wi-Fi access.
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.WifiManager
+ */
+ public static final String WIFI_SERVICE = "wifi";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.p2p.WifiP2pManager} for handling management of
+ * Wi-Fi peer-to-peer connections.
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.p2p.WifiP2pManager
+ */
+ public static final String WIFI_P2P_SERVICE = "wifip2p";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.wifi.aware.WifiAwareManager} for handling management of
+ * Wi-Fi Aware.
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.aware.WifiAwareManager
+ */
+ public static final String WIFI_AWARE_SERVICE = "wifiaware";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.WifiScanner} for scanning the wifi universe
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.WifiScanner
+ * @hide
+ */
+ @SystemApi
+ public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.RttManager} for ranging devices with wifi
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.RttManager
+ * @hide
+ */
+ @SystemApi
+ public static final String WIFI_RTT_SERVICE = "rttmanager";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.lowpan.LowpanManager} for handling management of
+ * LoWPAN access.
+ *
+ * @see #getSystemService
+ * @see android.net.lowpan.LowpanManager
+ *
+ * @hide
+ */
+ public static final String LOWPAN_SERVICE = "lowpan";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.EthernetManager} for handling management of
+ * Ethernet access.
+ *
+ * @see #getSystemService
+ * @see android.net.EthernetManager
+ *
+ * @hide
+ */
+ public static final String ETHERNET_SERVICE = "ethernet";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.nsd.NsdManager} for handling management of network service
+ * discovery
+ *
+ * @see #getSystemService
+ * @see android.net.nsd.NsdManager
+ */
+ public static final String NSD_SERVICE = "servicediscovery";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.AudioManager} for handling management of volume,
+ * ringer modes and audio routing.
+ *
+ * @see #getSystemService
+ * @see android.media.AudioManager
+ */
+ public static final String AUDIO_SERVICE = "audio";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.fingerprint.FingerprintManager} for handling management
+ * of fingerprints.
+ *
+ * @see #getSystemService
+ * @see android.hardware.fingerprint.FingerprintManager
+ */
+ public static final String FINGERPRINT_SERVICE = "fingerprint";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.MediaRouter} for controlling and managing
+ * routing of media.
+ *
+ * @see #getSystemService
+ * @see android.media.MediaRouter
+ */
+ public static final String MEDIA_ROUTER_SERVICE = "media_router";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.session.MediaSessionManager} for managing media Sessions.
+ *
+ * @see #getSystemService
+ * @see android.media.session.MediaSessionManager
+ */
+ public static final String MEDIA_SESSION_SERVICE = "media_session";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.telephony.TelephonyManager} for handling management the
+ * telephony features of the device.
+ *
+ * @see #getSystemService
+ * @see android.telephony.TelephonyManager
+ */
+ public static final String TELEPHONY_SERVICE = "phone";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.telephony.SubscriptionManager} for handling management the
+ * telephony subscriptions of the device.
+ *
+ * @see #getSystemService
+ * @see android.telephony.SubscriptionManager
+ */
+ public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.telecom.TelecomManager} to manage telecom-related features
+ * of the device.
+ *
+ * @see #getSystemService
+ * @see android.telecom.TelecomManager
+ */
+ public static final String TELECOM_SERVICE = "telecom";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.telephony.CarrierConfigManager} for reading carrier configuration values.
+ *
+ * @see #getSystemService
+ * @see android.telephony.CarrierConfigManager
+ */
+ public static final String CARRIER_CONFIG_SERVICE = "carrier_config";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.telephony.euicc.EuiccManager} to manage the device eUICC (embedded SIM).
+ *
+ * @see #getSystemService
+ * @see android.telephony.euicc.EuiccManager
+ * TODO(b/35851809): Unhide this API.
+ * @hide
+ */
+ public static final String EUICC_SERVICE = "euicc_service";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.text.ClipboardManager} for accessing and modifying
+ * {@link android.content.ClipboardManager} for accessing and modifying
+ * the contents of the global clipboard.
+ *
+ * @see #getSystemService
+ * @see android.content.ClipboardManager
+ */
+ public static final String CLIPBOARD_SERVICE = "clipboard";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link TextClassificationManager} for text classification services.
+ *
+ * @see #getSystemService
+ * @see TextClassificationManager
+ */
+ public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.inputmethod.InputMethodManager} for accessing input
+ * methods.
+ *
+ * @see #getSystemService
+ */
+ public static final String INPUT_METHOD_SERVICE = "input_method";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.textservice.TextServicesManager} for accessing
+ * text services.
+ *
+ * @see #getSystemService
+ */
+ public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets.
+ *
+ * @see #getSystemService
+ */
+ public static final String APPWIDGET_SERVICE = "appwidget";
+
+ /**
+ * Official published name of the (internal) voice interaction manager service.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
+
+ /**
+ * Official published name of the (internal) autofill service.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
+
+ /**
+ * Use with {@link #getSystemService} to access the
+ * {@link com.android.server.voiceinteraction.SoundTriggerService}.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
+
+
+ /**
+ * Use with {@link #getSystemService} to retrieve an
+ * {@link android.app.backup.IBackupManager IBackupManager} for communicating
+ * with the backup mechanism.
+ * @hide
+ *
+ * @see #getSystemService
+ */
+ @SystemApi
+ public static final String BACKUP_SERVICE = "backup";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.DropBoxManager} instance for recording
+ * diagnostic logs.
+ * @see #getSystemService
+ */
+ public static final String DROPBOX_SERVICE = "dropbox";
+
+ /**
+ * System service name for the DeviceIdleController. There is no Java API for this.
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.admin.DevicePolicyManager} for working with global
+ * device policy management.
+ *
+ * @see #getSystemService
+ */
+ public static final String DEVICE_POLICY_SERVICE = "device_policy";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.UiModeManager} for controlling UI modes.
+ *
+ * @see #getSystemService
+ */
+ public static final String UI_MODE_SERVICE = "uimode";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.DownloadManager} for requesting HTTP downloads.
+ *
+ * @see #getSystemService
+ */
+ public static final String DOWNLOAD_SERVICE = "download";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.BatteryManager} for managing battery state.
+ *
+ * @see #getSystemService
+ */
+ public static final String BATTERY_SERVICE = "batterymanager";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.nfc.NfcManager} for using NFC.
+ *
+ * @see #getSystemService
+ */
+ public static final String NFC_SERVICE = "nfc";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.bluetooth.BluetoothManager} for using Bluetooth.
+ *
+ * @see #getSystemService
+ */
+ public static final String BLUETOOTH_SERVICE = "bluetooth";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.sip.SipManager} for accessing the SIP related service.
+ *
+ * @see #getSystemService
+ */
+ /** @hide */
+ public static final String SIP_SERVICE = "sip";
+
+ /**
+ * Use with {@link #getSystemService} 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
+ * @see android.hardware.usb.UsbManager
+ */
+ public static final String USB_SERVICE = "usb";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.hardware.SerialManager} for access to serial ports.
+ *
+ * @see #getSystemService
+ * @see android.hardware.SerialManager
+ *
+ * @hide
+ */
+ public static final String SERIAL_SERVICE = "serial";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.hdmi.HdmiControlManager} for controlling and managing
+ * HDMI-CEC protocol.
+ *
+ * @see #getSystemService
+ * @see android.hardware.hdmi.HdmiControlManager
+ * @hide
+ */
+ @SystemApi
+ public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.input.InputManager} for interacting with input devices.
+ *
+ * @see #getSystemService
+ * @see android.hardware.input.InputManager
+ */
+ public static final String INPUT_SERVICE = "input";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.display.DisplayManager} for interacting with display devices.
+ *
+ * @see #getSystemService
+ * @see android.hardware.display.DisplayManager
+ */
+ public static final String DISPLAY_SERVICE = "display";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.UserManager} for managing users on devices that support multiple users.
+ *
+ * @see #getSystemService
+ * @see android.os.UserManager
+ */
+ public static final String USER_SERVICE = "user";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.LauncherApps} for querying and monitoring launchable apps across
+ * profiles of a user.
+ *
+ * @see #getSystemService
+ * @see android.content.pm.LauncherApps
+ */
+ public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.RestrictionsManager} for retrieving application restrictions
+ * and requesting permissions for restricted operations.
+ * @see #getSystemService
+ * @see android.content.RestrictionsManager
+ */
+ public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.AppOpsManager} for tracking application operations
+ * on the device.
+ *
+ * @see #getSystemService
+ * @see android.app.AppOpsManager
+ */
+ public static final String APP_OPS_SERVICE = "appops";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.camera2.CameraManager} for interacting with
+ * camera devices.
+ *
+ * @see #getSystemService
+ * @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
+ * @see android.print.PrintManager
+ */
+ public static final String PRINT_SERVICE = "print";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.companion.CompanionDeviceManager} for managing companion devices
+ *
+ * @see #getSystemService
+ * @see android.companion.CompanionDeviceManager
+ */
+ public static final String COMPANION_DEVICE_SERVICE = "companiondevice";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.ConsumerIrManager} for transmitting infrared
+ * signals from the device.
+ *
+ * @see #getSystemService
+ * @see android.hardware.ConsumerIrManager
+ */
+ public static final String CONSUMER_IR_SERVICE = "consumer_ir";
+
+ /**
+ * {@link android.app.trust.TrustManager} for managing trust agents.
+ * @see #getSystemService
+ * @see android.app.trust.TrustManager
+ * @hide
+ */
+ public static final String TRUST_SERVICE = "trust";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.tv.TvInputManager} for interacting with TV inputs
+ * on the device.
+ *
+ * @see #getSystemService
+ * @see android.media.tv.TvInputManager
+ */
+ public static final String TV_INPUT_SERVICE = "tv_input";
+
+ /**
+ * {@link android.net.NetworkScoreManager} for managing network scoring.
+ * @see #getSystemService
+ * @see android.net.NetworkScoreManager
+ * @hide
+ */
+ @SystemApi
+ public static final String NETWORK_SCORE_SERVICE = "network_score";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.app.usage.UsageStatsManager} for querying device usage stats.
+ *
+ * @see #getSystemService
+ * @see android.app.usage.UsageStatsManager
+ */
+ public static final String USAGE_STATS_SERVICE = "usagestats";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.app.job.JobScheduler} instance for managing occasional
+ * background tasks.
+ * @see #getSystemService
+ * @see android.app.job.JobScheduler
+ */
+ public static final String JOB_SCHEDULER_SERVICE = "jobscheduler";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.service.persistentdata.PersistentDataBlockManager} instance
+ * for interacting with a storage device that lives across factory resets.
+ *
+ * @see #getSystemService
+ * @see android.service.persistentdata.PersistentDataBlockManager
+ * @hide
+ */
+ @SystemApi
+ public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.service.oemlock.OemLockManager} instance for managing the OEM lock.
+ *
+ * @see #getSystemService
+ * @see android.service.oemlock.OemLockManager
+ * @hide
+ */
+ @SystemApi
+ public static final String OEM_LOCK_SERVICE = "oem_lock";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.media.projection.MediaProjectionManager} instance for managing
+ * media projection sessions.
+ * @see #getSystemService
+ * @see android.media.projection.MediaProjectionManager
+ */
+ public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.midi.MidiManager} for accessing the MIDI service.
+ *
+ * @see #getSystemService
+ */
+ public static final String MIDI_SERVICE = "midi";
+
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.radio.RadioManager} for accessing the broadcast radio service.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String RADIO_SERVICE = "broadcastradio";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.HardwarePropertiesManager} for accessing the hardware properties service.
+ *
+ * @see #getSystemService
+ */
+ public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service.
+ *
+ * @see #getSystemService
+ * @see android.content.pm.ShortcutManager
+ */
+ public static final String SHORTCUT_SERVICE = "shortcut";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.hardware.location.ContextHubManager} for accessing context hubs.
+ *
+ * @see #getSystemService
+ * @see android.hardware.location.ContextHubManager
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CONTEXTHUB_SERVICE = "contexthub";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.health.SystemHealthManager} for accessing system health (battery, power,
+ * memory, etc) metrics.
+ *
+ * @see #getSystemService
+ */
+ 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";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.content.om.OverlayManager} for managing overlay packages.
+ *
+ * @see #getSystemService
+ * @see android.content.om.OverlayManager
+ * @hide
+ */
+ public static final String OVERLAY_SERVICE = "overlay";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link VrManager} for accessing the VR service.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ @SystemApi
+ public static final String VR_SERVICE = "vrmanager";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve an
+ * {@link android.app.timezone.ITimeZoneRulesManager}.
+ * @hide
+ *
+ * @see #getSystemService
+ */
+ public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
+
+ /**
+ * 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
+ 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 &gt; 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
+ * &lt;grant-uri-permissions&gt;} 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 &gt; 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 &gt; 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 &gt; 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 &gt; 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 &nbsp;
+ * @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
+ */
+ public abstract Context createPackageContextAsUser(
+ String packageName, @CreatePackageOptions int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Creates a context given an {@link android.content.pm.ApplicationInfo}.
+ *
+ * @hide
+ */
+ 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 userId associated with this context
+ * @return user id
+ *
+ * @hide
+ */
+ @TestApi
+ public abstract @UserIdInt int getUserId();
+
+ /**
+ * 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.
+ *
+ * The returned display Context provides a {@link WindowManager}
+ * (see {@link #getSystemService(String)}) that is configured to show windows
+ * on the given display. The WindowManager's {@link WindowManager#getDefaultDisplay}
+ * method can be used to retrieve the Display from the returned Context.
+ *
+ * @param display A {@link Display} object specifying the display
+ * for whose metrics the Context's resources should be tailored and upon which
+ * new windows should be shown.
+ *
+ * @return A {@link Context} for the display.
+ */
+ public abstract Context createDisplayContext(@NonNull Display display);
+
+ /**
+ * 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);
+
+ /**
+ * @hide
+ */
+ public abstract Display getDisplay();
+
+ /**
+ * @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.");
+ }
+
+ /**
+ * 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()");
+ }
+ }
+}
diff --git a/android/content/ContextWrapper.java b/android/content/ContextWrapper.java
new file mode 100644
index 00000000..a9fd58bc
--- /dev/null
+++ b/android/content/ContextWrapper.java
@@ -0,0 +1,973 @@
+/*
+ * 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.SystemApi;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 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 {
+ 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 Context getApplicationContext() {
+ return mBase.getApplicationContext();
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ mBase.setTheme(resid);
+ }
+
+ /** @hide */
+ @Override
+ 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
+ public String getBasePackageName() {
+ return mBase.getBasePackageName();
+ }
+
+ /** @hide */
+ @Override
+ public String getOpPackageName() {
+ return mBase.getOpPackageName();
+ }
+
+ @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();
+ }
+
+ @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 void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ 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(Intent intent, String[] receiverPermissions) {
+ mBase.sendBroadcastMultiplePermissions(intent, 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
+ @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
+ 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
+ public ComponentName startServiceAsUser(Intent service, UserHandle user) {
+ return mBase.startServiceAsUser(service, user);
+ }
+
+ /** @hide */
+ @Override
+ 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);
+ }
+
+ /** @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 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 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();
+ }
+
+ @Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ return mBase.createConfigurationContext(overrideConfiguration);
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ return mBase.createDisplayContext(display);
+ }
+
+ @Override
+ public boolean isRestricted() {
+ return mBase.isRestricted();
+ }
+
+ /** @hide */
+ @Override
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ return mBase.getDisplayAdjustments(displayId);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public Display getDisplay() {
+ return mBase.getDisplay();
+ }
+
+ /**
+ * @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
+ */
+ public int getNextAutofillId() {
+ return mBase.getNextAutofillId();
+ }
+}
diff --git a/android/content/CursorEntityIterator.java b/android/content/CursorEntityIterator.java
new file mode 100644
index 00000000..18437e5f
--- /dev/null
+++ b/android/content/CursorEntityIterator.java
@@ -0,0 +1,112 @@
+/*
+ * 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.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
+ */
+ 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 00000000..c78871c3
--- /dev/null
+++ b/android/content/CursorLoader.java
@@ -0,0 +1,243 @@
+/*
+ * 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.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 paramters with {@link #setUri(Uri)}, {@link #setSelection(String)},
+ * {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
+ * and {@link #setProjection(String[])}.
+ */
+public class CursorLoader extends AsyncTaskLoader<Cursor> {
+ final ForceLoadContentObserver mObserver;
+
+ Uri mUri;
+ String[] mProjection;
+ String mSelection;
+ String[] mSelectionArgs;
+ String mSortOrder;
+
+ Cursor mCursor;
+ 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 contacts list 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 00000000..863c9f6f
--- /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>
+ * &lt;row uri="content://contacts/people">
+ * &lt;Col column = "name" value = "foo feebe "/>
+ * &lt;Col column = "addr" value = "Tx"/>
+ * &lt;/row></pre>
+ * <br/>
+ * Delete, it must be in order of uri, select, and arg:
+ * <pre>
+ * &lt;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>
+ * &lt;row uri="content://contacts/people">
+ * &lt;col column = "name" value = "foo feebe"/>
+ * &lt;col column = "addr" value = "Tx"/>
+ * &lt;row postfix="phones">
+ * &lt;col column="number" value="512-514-6535"/>
+ * &lt;/row>
+ * &lt;row postfix="phones">
+ * &lt;col column="cell" value="512-514-6535"/>
+ * &lt;/row>
+ * &lt;/row></pre>
+ * <br/>
+ * Insert multiple rows in to same table and same attributes:
+ * <pre>
+ * &lt;row uri="content://contacts/people" >
+ * &lt;row>
+ * &lt;col column= "name" value = "foo feebe"/>
+ * &lt;col column= "addr" value = "Tx"/>
+ * &lt;/row>
+ * &lt;row>
+ * &lt;/row>
+ * &lt;/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 00000000..511f356c
--- /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 00000000..607cb3f4
--- /dev/null
+++ b/android/content/Entity.java
@@ -0,0 +1,70 @@
+/*
+ * 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.net.Uri;
+
+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 {
+ final private ContentValues mValues;
+ 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 00000000..55c47ba7
--- /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 00000000..66087fb9
--- /dev/null
+++ b/android/content/IContentProvider.java
@@ -0,0 +1,95 @@
+/*
+ * 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.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
+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, Uri url, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
+ throws RemoteException;
+ public String getType(Uri url) throws RemoteException;
+ public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
+ throws RemoteException;
+ public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
+ throws RemoteException;
+ public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+ throws RemoteException;
+ public int update(String callingPkg, Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException;
+ public ParcelFileDescriptor openFile(
+ String callingPkg, Uri url, String mode, ICancellationSignal signal,
+ IBinder callerToken)
+ throws RemoteException, FileNotFoundException;
+ public AssetFileDescriptor openAssetFile(
+ String callingPkg, Uri url, String mode, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException;
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException;
+ public Bundle call(
+ String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras)
+ throws RemoteException;
+ public ICancellationSignal createCancellationSignal() throws RemoteException;
+
+ public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
+ public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;
+
+ public boolean refresh(String callingPkg, Uri url, @Nullable Bundle args,
+ ICancellationSignal cancellationSignal) throws RemoteException;
+
+ // Data interchange.
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+ Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException;
+
+ /* IPC constants */
+ static final String descriptor = "android.content.IContentProvider";
+
+ 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;
+}
diff --git a/android/content/Intent.java b/android/content/Intent.java
new file mode 100644
index 00000000..08acfb65
--- /dev/null
+++ b/android/content/Intent.java
@@ -0,0 +1,10086 @@
+/*
+ * 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.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+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.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.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
+import android.os.StrictMode;
+import android.os.UserHandle;
+import android.provider.ContactsContract.QuickContact;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+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.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;
+
+/**
+ * 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 &lt;intent-filter&gt; 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 user to browse through a list of notes data and view details about
+ * individual items. Text in italics indicate places were you would replace a
+ * name with one specific to your own package.</p>
+ *
+ * <pre> &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ * package="<i>com.android.notepad</i>"&gt;
+ * &lt;application android:icon="@drawable/app_notes"
+ * android:label="@string/app_name"&gt;
+ *
+ * &lt;provider class=".NotePadProvider"
+ * android:authorities="<i>com.google.provider.NotePad</i>" /&gt;
+ *
+ * &lt;activity class=".NotesList" android:label="@string/title_notes_list"&gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.intent.action.MAIN" /&gt;
+ * &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.intent.action.VIEW" /&gt;
+ * &lt;action android:name="android.intent.action.EDIT" /&gt;
+ * &lt;action android:name="android.intent.action.PICK" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.intent.action.GET_CONTENT" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;/activity&gt;
+ *
+ * &lt;activity class=".NoteEditor" android:label="@string/title_note"&gt;
+ * &lt;intent-filter android:label="@string/resolve_edit"&gt;
+ * &lt;action android:name="android.intent.action.VIEW" /&gt;
+ * &lt;action android:name="android.intent.action.EDIT" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;
+ *
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.intent.action.INSERT" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;
+ *
+ * &lt;/activity&gt;
+ *
+ * &lt;activity class=".TitleEditor" android:label="@string/title_edit_title"
+ * android:theme="@android:style/Theme.Dialog"&gt;
+ * &lt;intent-filter android:label="@string/resolve_title"&gt;
+ * &lt;action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;category android:name="android.intent.category.ALTERNATIVE" /&gt;
+ * &lt;category android:name="android.intent.category.SELECTED_ALTERNATIVE" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;/activity&gt;
+ *
+ * &lt;/application&gt;
+ * &lt;/manifest&gt;</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>
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="{@link #ACTION_MAIN android.intent.action.MAIN}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" /&gt;
+ * &lt;/intent-filter&gt;</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>
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ * &lt;action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ * &lt;action android:name="{@link #ACTION_PICK android.intent.action.PICK}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>This declares the things that the activity can do on a directory of
+ * notes. The type being supported is given with the &lt;type&gt; 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>
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</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>
+ * &lt;intent-filter android:label="@string/resolve_edit"&gt;
+ * &lt;action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ * &lt;action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</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>
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="{@link #ACTION_INSERT android.intent.action.INSERT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</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>
+ * &lt;intent-filter android:label="@string/resolve_title"&gt;
+ * &lt;action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</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
+ * &lt;receiver&gt; 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 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_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 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 Parcelable.Creator<ShortcutIconResource> CREATOR =
+ new Parcelable.Creator<ShortcutIconResource>() {
+
+ public ShortcutIconResource createFromParcel(Parcel source) {
+ ShortcutIconResource icon = new ShortcutIconResource();
+ icon.packageName = source.readString();
+ icon.resourceName = source.readString();
+ 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.writeString(packageName);
+ dest.writeString(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.
+ * @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.
+ * @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.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_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.
+ */
+ @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";
+
+ /**
+ * 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
+ */
+ @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";
+
+ /**
+ * @hide
+ * @removed
+ * @deprecated Do not use. This will go away.
+ * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
+ = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+ /**
+ * Activity Action: Launch instant application installer.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE
+ = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
+
+ /**
+ * @hide
+ * @removed
+ * @deprecated Do not use. This will go away.
+ * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
+ = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+ /**
+ * Service Action: Resolve instant application.
+ * <p>
+ * The system will have a persistent connection to this service.
+ * 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";
+
+ /**
+ * @hide
+ * @removed
+ * @deprecated Do not use. This will go away.
+ * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS
+ = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS";
+ /**
+ * Activity Action: Launch instant app settings.
+ *
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS
+ = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
+
+ /**
+ * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
+ * package. Specifies the installer package name; this package will receive the
+ * {@link #ACTION_APP_ERROR} intent.
+ */
+ public static final String EXTRA_INSTALLER_PACKAGE_NAME
+ = "android.intent.extra.INSTALLER_PACKAGE_NAME";
+
+ /**
+ * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
+ * package. Specifies that the application being installed should not be
+ * treated as coming from an unknown source, but as coming from the app
+ * invoking the Intent. For this to work you must start the installer with
+ * startActivityForResult().
+ */
+ public static final String EXTRA_NOT_UNKNOWN_SOURCE
+ = "android.intent.extra.NOT_UNKNOWN_SOURCE";
+
+ /**
+ * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link #ACTION_VIEW} to indicate the URI from which the local APK in the Intent
+ * data field originated from.
+ */
+ public static final String EXTRA_ORIGINATING_URI
+ = "android.intent.extra.ORIGINATING_URI";
+
+ /**
+ * This extra can be used with any Intent used to launch an activity, supplying information
+ * about who is launching that activity. This field contains a {@link android.net.Uri}
+ * object, typically an http: or https: URI of the web site that the referral came from;
+ * it can also use the {@link #URI_ANDROID_APP_SCHEME android-app:} scheme to identify
+ * a native application that it came from.
+ *
+ * <p>To retrieve this value in a client, use {@link android.app.Activity#getReferrer}
+ * instead of directly retrieving the extra. It is also valid for applications to
+ * instead supply {@link #EXTRA_REFERRER_NAME} for cases where they can only create
+ * a string, not a Uri; the field here, if supplied, will always take precedence,
+ * however.</p>
+ *
+ * @see #EXTRA_REFERRER_NAME
+ */
+ public static final String EXTRA_REFERRER
+ = "android.intent.extra.REFERRER";
+
+ /**
+ * Alternate version of {@link #EXTRA_REFERRER} that supplies the URI as a String rather
+ * than a {@link android.net.Uri} object. Only for use in cases where Uri objects can
+ * not be created, in particular when Intent extras are supplied through the
+ * {@link #URI_INTENT_SCHEME intent:} or {@link #URI_ANDROID_APP_SCHEME android-app:}
+ * schemes.
+ *
+ * @see #EXTRA_REFERRER
+ */
+ public static final String EXTRA_REFERRER_NAME
+ = "android.intent.extra.REFERRER_NAME";
+
+ /**
+ * Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link #ACTION_VIEW} to indicate the uid of the package that initiated the install
+ * Currently only a system app that hosts the provider authority "downloads" or holds the
+ * permission {@link android.Manifest.permission.MANAGE_DOCUMENTS} can use this.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_ORIGINATING_UID
+ = "android.intent.extra.ORIGINATING_UID";
+
+ /**
+ * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
+ * package. Tells the installer UI to skip the confirmation with the user
+ * if the .apk is replacing an existing one.
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, Android
+ * will no longer show an interstitial message about updating existing
+ * applications so this is no longer needed.
+ */
+ @Deprecated
+ public static final String EXTRA_ALLOW_REPLACE
+ = "android.intent.extra.ALLOW_REPLACE";
+
+ /**
+ * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} or
+ * {@link #ACTION_UNINSTALL_PACKAGE}. Specifies that the installer UI should
+ * return to the application the result code of the install/uninstall. The returned result
+ * code will be {@link android.app.Activity#RESULT_OK} on success or
+ * {@link android.app.Activity#RESULT_FIRST_USER} on failure.
+ */
+ public static final String EXTRA_RETURN_RESULT
+ = "android.intent.extra.RETURN_RESULT";
+
+ /**
+ * Package manager install result code. @hide because result codes are not
+ * yet ready to be exposed.
+ */
+ public static final String EXTRA_INSTALL_RESULT
+ = "android.intent.extra.INSTALL_RESULT";
+
+ /**
+ * Activity Action: Launch application uninstaller.
+ * <p>
+ * Input: The data must be a package: URI whose scheme specific part is
+ * the package name of the current installed package to be uninstalled.
+ * You can optionally supply {@link #EXTRA_RETURN_RESULT}.
+ * <p>
+ * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
+ * succeeded.
+ */
+ @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 associated with a {@link #ACTION_UPGRADE_SETUP} activity
+ * describing the last run version of the platform that was setup.
+ * @hide
+ */
+ 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 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 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";
+
+ /**
+ * 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: 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";
+
+ /**
+ * Activity action: Launch UI to manage which apps have a given permission.
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission access
+ * to 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";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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><em>time-zone</em> - 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)
+ 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";
+ /**
+ * @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
+ * </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
+ * </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: 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 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'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: 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 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.
+ * The path to the file is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ 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.
+ */
+ @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&lt;RestrictionEntry&gt;</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
+ */
+ 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
+ */
+ 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";
+
+ /**
+ * 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.
+ *
+ * <p>Not for use by third-party applications.
+ *
+ * @see #EXTRA_FORCE_MASTER_CLEAR
+ *
+ * {@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.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
+
+ /**
+ * 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
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
+
+ /**
+ * 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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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
+ @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;
+
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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 &lt;intent-filter&gt; 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 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";
+ /**
+ * 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";
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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 ephemeral installation success.
+ * @hide
+ */
+ public static final String EXTRA_EPHEMERAL_SUCCESS = "android.intent.extra.EPHEMERAL_SUCCESS";
+
+ /**
+ * A {@link IntentSender} to start after ephemeral installation failure.
+ * @hide
+ */
+ public static final String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE";
+
+ /**
+ * The host name that triggered an ephemeral resolution.
+ * @hide
+ */
+ public static final String EXTRA_EPHEMERAL_HOSTNAME = "android.intent.extra.EPHEMERAL_HOSTNAME";
+
+ /**
+ * An opaque token to track ephemeral resolution.
+ * @hide
+ */
+ public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN";
+
+ /**
+ * The version code of the app to install components from.
+ * @hide
+ */
+ public static final String EXTRA_VERSION_CODE = "android.intent.extra.VERSION_CODE";
+
+ /**
+ * The app that triggered the ephemeral installation.
+ * @hide
+ */
+ 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
+ */
+ 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";
+
+ /**
+ * @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 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 broadcasts intents related to addition and removal of managed
+ * profiles - {@link #ACTION_MANAGED_PROFILE_ADDED} and {@link #ACTION_MANAGED_PROFILE_REMOVED}.
+ */
+ 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&lt;RestrictionEntry&gt;</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 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;
+
+ /** {@hide} */
+ 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_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";
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ 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 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_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,
+ })
+ @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_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,
+ })
+ @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;
+
+ /**
+ * Internal flag used to indicate that a system component has done their
+ * homework and verified that they correctly handle packages and components
+ * that come and go over time. In particular:
+ * <ul>
+ * <li>Apps installed on external storage, which will appear to be
+ * uninstalled while the the device is ejected.
+ * <li>Apps with encryption unaware components, which will appear to not
+ * exist while the device is locked.
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int FLAG_DEBUG_TRIAGED_MISSING = 0x00000100;
+
+ /**
+ * 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
+ * transfered 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, 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 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 boot has completed</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 sevices as a convenience to
+ * avoid having to implement a more complex mechanism around detection
+ * of boot completion.
+ *
+ * @hide
+ */
+ 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 mPackage;
+ private ComponentName mComponent;
+ private int mFlags;
+ private ArraySet<String> mCategories;
+ 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(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.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.maybeIsEmpty()) {
+ 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;
+ }
+
+ // 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 */
+ 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 "-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 "--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 */
+ 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>]",
+ " [-c <CATEGORY> [-c <CATEGORY>] ...]",
+ " [-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]",
+ " [--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);
+ }
+
+ /**
+ * 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} */
+ 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 that previously added with putExtra()
+ * or null if none was found.
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ 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 that 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 that 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 that 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 that 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 that 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 that 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 that 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 that 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 that previously added with putExtra()
+ * or null if no String value was found.
+ *
+ * @see #putExtra(String, String)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no CharSequence value was found.
+ *
+ * @see #putExtra(String, CharSequence)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no Parcelable value was found.
+ *
+ * @see #putExtra(String, Parcelable)
+ */
+ public <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 that previously added with putExtra()
+ * or null if no Parcelable[] value was found.
+ *
+ * @see #putExtra(String, Parcelable[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no ArrayList<Parcelable> value was found.
+ *
+ * @see #putParcelableArrayListExtra(String, ArrayList)
+ */
+ public <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 that previously added with putExtra()
+ * or null if no Serializable value was found.
+ *
+ * @see #putExtra(String, Serializable)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no ArrayList<Integer> value was found.
+ *
+ * @see #putIntegerArrayListExtra(String, ArrayList)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no ArrayList<String> value was found.
+ *
+ * @see #putStringArrayListExtra(String, ArrayList)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no ArrayList<CharSequence> value was found.
+ *
+ * @see #putCharSequenceArrayListExtra(String, ArrayList)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no boolean array value was found.
+ *
+ * @see #putExtra(String, boolean[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no byte array value was found.
+ *
+ * @see #putExtra(String, byte[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no short array value was found.
+ *
+ * @see #putExtra(String, short[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no char array value was found.
+ *
+ * @see #putExtra(String, char[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no int array value was found.
+ *
+ * @see #putExtra(String, int[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no long array value was found.
+ *
+ * @see #putExtra(String, long[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no float array value was found.
+ *
+ * @see #putExtra(String, float[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no double array value was found.
+ *
+ * @see #putExtra(String, double[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no String array value was found.
+ *
+ * @see #putExtra(String, String[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no CharSequence array value was found.
+ *
+ * @see #putExtra(String, CharSequence[])
+ */
+ public 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 that previously added with putExtra()
+ * or null if no Bundle value was found.
+ *
+ * @see #putExtra(String, Bundle)
+ */
+ public 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 that previously added with putExtra()
+ * or null if no IBinder value was found.
+ *
+ * @see #putExtra(String, IBinder)
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ 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 that previously added with putExtra()
+ * or defaultValue if none was found.
+ *
+ * @see #putExtra
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ 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 */
+ 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
+ */
+ 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));
+ }
+
+ /**
+ * 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 "*&#47;*".
+ *
+ * @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, 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, 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, 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, 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,
+ 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, 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, 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,
+ 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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
+ 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(@NonNull 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,
+ 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;
+
+ /**
+ * 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> 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_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.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("Intent", "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,
+ * class, and categories are the same. This does <em>not</em> compare
+ * any extra data included in the intents.
+ *
+ * @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.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;
+ }
+
+ /**
+ * 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 (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 */
+ 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 (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("}");
+ }
+ }
+
+ /**
+ * 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 == '.' || 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 (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.writeString(mAction);
+ Uri.writeToParcel(out, mData);
+ out.writeString(mType);
+ out.writeInt(mFlags);
+ out.writeString(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.writeString(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 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.readString());
+ mData = Uri.CREATOR.createFromParcel(in);
+ mType = in.readString();
+ mFlags = in.readInt();
+ mPackage = in.readString();
+ 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.readString().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);
+
+ 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 (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_COMPONENT.equals(attrName)) {
+ intent.setComponent(ComponentName.unflattenFromString(attrValue));
+ } else if (ATTR_FLAGS.equals(attrName)) {
+ intent.setFlags(Integer.parseInt(attrValue, 16));
+ } else {
+ Log.e("Intent", "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("Intent", "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
+ */
+ 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_VERIFIED:
+ // 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());
+ }
+ }
+ }
+
+ /**
+ * @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 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 (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+ || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action)
+ || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
+ 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 (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+ || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action)
+ || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
+ 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;
+ }
+
+ 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 00000000..c9bce530
--- /dev/null
+++ b/android/content/IntentFilter.java
@@ -0,0 +1,2027 @@
+/*
+ * 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.SystemApi;
+import android.net.Uri;
+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 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.Iterator;
+import java.util.Set;
+
+/**
+ * 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, the field
+ * will not be tested if no values have been given (treating it as a wildcard);
+ * if no data characteristics are specified, however, 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 authories 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 TYPE_STR = "type";
+ 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";
+
+ private int mPriority;
+ private int mOrder;
+ 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> mDataTypes = null;
+ private boolean mHasPartialTypes = 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.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);
+ }
+ mHasPartialTypes = o.mHasPartialTypes;
+ 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
+ * programatically. 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
+ */
+ 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
+ */
+ 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 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 {
+ final int slashpos = type.indexOf('/');
+ final int typelen = type.length();
+ if (slashpos > 0 && typelen >= slashpos+2) {
+ if (mDataTypes == null) mDataTypes = new ArrayList<String>();
+ if (typelen == slashpos+2 && type.charAt(slashpos+1) == '*') {
+ String str = type.substring(0, slashpos);
+ if (!mDataTypes.contains(str)) {
+ mDataTypes.add(str.intern());
+ }
+ mHasPartialTypes = true;
+ } else {
+ if (!mDataTypes.contains(type)) {
+ mDataTypes.add(type.intern());
+ }
+ }
+ return;
+ }
+
+ throw new MalformedMimeTypeException(type);
+ }
+
+ /**
+ * 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 */
+ public final boolean hasExactDataType(String type) {
+ return mDataTypes != null && mDataTypes.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;
+ }
+
+ /**
+ * 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);
+ }
+
+ 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) {
+ String host = data.getHost();
+ if (host == null) {
+ return NO_MATCH_DATA;
+ }
+ if (false) Log.v("IntentFilter",
+ "Match host " + host + ": " + mHost);
+ 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) {
+ 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.match(data)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ 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 */
+ 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) {
+ 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.match(data)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ 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) {
+ if (mDataAuthorities == null || data == 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);
+ 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) {
+ 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 : "")) {
+ match = MATCH_CATEGORY_SCHEME;
+ } else {
+ return NO_MATCH_DATA;
+ }
+
+ final ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;
+ if (schemeSpecificParts != null && data != null) {
+ match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart())
+ ? 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);
+ if (authMatch >= 0) {
+ final ArrayList<PatternMatcher> paths = mDataPaths;
+ if (paths == null) {
+ match = authMatch;
+ } else if (hasDataPath(data.getPath())) {
+ 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)) {
+ 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) {
+ if (action != null && !matchAction(action)) {
+ if (false) Log.v(
+ logTag, "No matching action " + action + " for " + this);
+ return NO_MATCH_ACTION;
+ }
+
+ int dataMatch = matchData(type, scheme, data);
+ 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);
+ }
+ N = countDataTypes();
+ for (int i=0; i<N; i++) {
+ serializer.startTag(null, TYPE_STR);
+ String type = mDataTypes.get(i);
+ if (type.indexOf('/') < 0) type = type + "/*";
+ serializer.attribute(null, NAME_STR, type);
+ serializer.endTag(null, TYPE_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);
+ }
+ }
+
+ 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(TYPE_STR)) {
+ String name = parser.getAttributeValue(null, NAME_STR);
+ if (name != null) {
+ try {
+ addDataType(name);
+ } catch (MalformedMimeTypeException e) {
+ }
+ }
+ } 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);
+ }
+ }
+
+ 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 (mDataTypes != null) {
+ Iterator<String> it = mDataTypes.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("Type: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
+ if (mPriority != 0 || mHasPartialTypes) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("mPriority="); sb.append(mPriority);
+ sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes);
+ 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 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 (mDataTypes != null) {
+ dest.writeInt(1);
+ dest.writeStringList(mDataTypes);
+ } 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(mHasPartialTypes ? 1 : 0);
+ dest.writeInt(getAutoVerify() ? 1 : 0);
+ dest.writeInt(mInstantAppVisibility);
+ }
+
+ /**
+ * 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) {
+ mDataTypes = new ArrayList<String>();
+ source.readStringList(mDataTypes);
+ }
+ 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();
+ mHasPartialTypes = source.readInt() > 0;
+ setAutoVerify(source.readInt() > 0);
+ setVisibilityToInstantApp(source.readInt());
+ }
+
+ 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 (mHasPartialTypes && t.contains("*")) {
+ return true;
+ }
+
+ final int slashpos = type.indexOf('/');
+ if (slashpos > 0) {
+ if (mHasPartialTypes && 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 00000000..ff127df6
--- /dev/null
+++ b/android/content/IntentSender.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;
+
+import android.app.ActivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+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 {
+ 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 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 */
+ public IIntentSender getTarget() {
+ return mTarget;
+ }
+
+ /** @hide */
+ public IBinder getWhitelistToken() {
+ return mWhitelistToken;
+ }
+
+ /** @hide */
+ 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 00000000..3faf13b6
--- /dev/null
+++ b/android/content/Loader.java
@@ -0,0 +1,552 @@
+/*
+ * 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
+ */
+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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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);
+ }
+ }
+} \ No newline at end of file
diff --git a/android/content/MutableContextWrapper.java b/android/content/MutableContextWrapper.java
new file mode 100644
index 00000000..820479cd
--- /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 00000000..2fc19bb5
--- /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 00000000..0441cccd
--- /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 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/QuickViewConstants.java b/android/content/QuickViewConstants.java
new file mode 100644
index 00000000..7455d0cb
--- /dev/null
+++ b/android/content/QuickViewConstants.java
@@ -0,0 +1,68 @@
+/*
+ * 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 view 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 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 00000000..96b269c8
--- /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 00000000..0473475c
--- /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 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 00000000..b463ec62
--- /dev/null
+++ b/android/content/RestrictionsManager.java
@@ -0,0 +1,748 @@
+/*
+ * 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.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.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>
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+ * &lt;restrictions xmlns:android="http://schemas.android.com/apk/res/android" &gt;
+ * &lt;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" &gt;
+ * &lt;restriction ... /&gt;
+ * ...
+ * &lt;/restriction&gt;
+ * &lt;restriction ... /&gt;
+ * ...
+ * &lt;/restrictions&gt;
+ * </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>
+ * &lt;application ... &gt;
+ * &lt;meta-data android:name="android.content.APP_RESTRICTIONS"
+ * android:resource="@xml/app_restrictions" /&gt;
+ * ...
+ * &lt;/application&gt;
+ * </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;
+ 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 00000000..d6f7d973
--- /dev/null
+++ b/android/content/SearchRecentSuggestionsProvider.java
@@ -0,0 +1,397 @@
+/*
+ * 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.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">
+ * &lt;!-- Content provider for search suggestions --&gt;
+ * &lt;provider android:name="YourSuggestionProviderClass"
+ * android:authorities="your.suggestion.authority" /&gt;</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;
+ 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 00000000..6ff49002
--- /dev/null
+++ b/android/content/ServiceConnection.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ *
+ * @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) {
+ }
+}
diff --git a/android/content/SharedPreferences.java b/android/content/SharedPreferences.java
new file mode 100644
index 00000000..4b09feda
--- /dev/null
+++ b/android/content/SharedPreferences.java
@@ -0,0 +1,381 @@
+/*
+ * 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><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.
+ *
+ * @param sharedPreferences The {@link SharedPreferences} that received
+ * the change.
+ * @param key The key of the preference that was changed, added, or
+ * removed.
+ */
+ 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 00000000..093fb082
--- /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 00000000..6ef7fd21
--- /dev/null
+++ b/android/content/SyncAdapterType.java
@@ -0,0 +1,245 @@
+/*
+ * 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.text.TextUtils;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * 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;
+ private final boolean userVisible;
+ private final boolean supportsUploading;
+ private final boolean isAlwaysSyncable;
+ private final boolean allowParallelSyncs;
+ 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;
+ }
+
+ 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 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 00000000..ccd79940
--- /dev/null
+++ b/android/content/SyncAdaptersCache.java
@@ -0,0 +1,155 @@
+/*
+ * 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.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.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A cache of services that export the {@link android.content.ISyncAdapter} interface.
+ * @hide
+ */
+public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
+ private static final String TAG = "Account";
+
+ private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
+ private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
+ private static final String ATTRIBUTES_NAME = "sync-adapter";
+ private static final MySerializer sSerializer = new MySerializer();
+
+ @GuardedBy("mServicesLock")
+ private SparseArray<ArrayMap<String,String[]>> mAuthorityToSyncAdapters
+ = new SparseArray<>();
+
+ 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 00000000..cc914c0c
--- /dev/null
+++ b/android/content/SyncContext.java
@@ -0,0 +1,80 @@
+/*
+ * 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.RemoteException;
+import android.os.SystemClock;
+import android.os.IBinder;
+
+public class SyncContext {
+ private ISyncContext mSyncContext;
+ private long mLastHeartbeatSendTime;
+
+ private static final long HEARTBEAT_SEND_INTERVAL_IN_MS = 1000;
+
+ /**
+ * @hide
+ */
+ 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
+ */
+ 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 00000000..ab3c30bb
--- /dev/null
+++ b/android/content/SyncInfo.java
@@ -0,0 +1,112 @@
+/*
+ * 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.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 */
+ 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 */
+ 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 */
+ SyncInfo(Parcel parcel) {
+ authorityId = parcel.readInt();
+ account = parcel.readParcelable(Account.class.getClassLoader());
+ authority = parcel.readString();
+ startTime = parcel.readLong();
+ }
+
+ /** @hide */
+ public static final 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 00000000..74d2f114
--- /dev/null
+++ b/android/content/SyncRequest.java
@@ -0,0 +1,547 @@
+/*
+ * 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.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. */
+ private final Account mAccountToSync;
+ /** Authority string that corresponds to a ContentProvider. */
+ private final String mAuthority;
+ /** Bundle containing user info as well as sync settings. */
+ 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.
+ */
+ private final long mSyncRunTimeSecs;
+ /** Periodic versus one-off. */
+ 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 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 00000000..4f86af98
--- /dev/null
+++ b/android/content/SyncResult.java
@@ -0,0 +1,325 @@
+/*
+ * 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 at least this many seconds.
+ */
+ 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 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 00000000..b7f2a852
--- /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 frmo 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 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 00000000..abf9cc91
--- /dev/null
+++ b/android/content/SyncStatusInfo.java
@@ -0,0 +1,254 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/** @hide */
+public class SyncStatusInfo implements Parcelable {
+ private static final String TAG = "Sync";
+
+ static final int VERSION = 4;
+
+ private static final int MAX_EVENT_COUNT = 10;
+
+ public final int authorityId;
+ public long totalElapsedTime;
+ public int numSyncs;
+ public int numSourcePoll;
+ public int numSourceServer;
+ public int numSourceLocal;
+ public int numSourceUser;
+ public int numSourcePeriodic;
+ public long lastSuccessTime;
+ public int lastSuccessSource;
+ public long lastFailureTime;
+ public int lastFailureSource;
+ public String lastFailureMesg;
+ public long initialFailureTime;
+ public boolean pending;
+ public boolean initialize;
+
+ // Warning: It is up to the external caller to ensure there are
+ // no race conditions when accessing this list
+ private ArrayList<Long> periodicSyncTimes;
+
+ private final ArrayList<Long> mLastEventTimes = new ArrayList<>();
+ private final ArrayList<String> mLastEvents = new ArrayList<>();
+
+ public SyncStatusInfo(int authorityId) {
+ this.authorityId = authorityId;
+ }
+
+ 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);
+ parcel.writeLong(totalElapsedTime);
+ parcel.writeInt(numSyncs);
+ parcel.writeInt(numSourcePoll);
+ parcel.writeInt(numSourceServer);
+ parcel.writeInt(numSourceLocal);
+ parcel.writeInt(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));
+ }
+ parcel.writeInt(numSourcePeriodic);
+ }
+
+ public SyncStatusInfo(Parcel parcel) {
+ int version = parcel.readInt();
+ if (version != VERSION && version != 1) {
+ Log.w("SyncStatusInfo", "Unknown version: " + version);
+ }
+ authorityId = parcel.readInt();
+ totalElapsedTime = parcel.readLong();
+ numSyncs = parcel.readInt();
+ numSourcePoll = parcel.readInt();
+ numSourceServer = parcel.readInt();
+ numSourceLocal = parcel.readInt();
+ 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.
+ numSourcePeriodic = numSyncs - numSourceLocal - numSourcePoll - numSourceServer
+ - numSourceUser;
+ if (numSourcePeriodic < 0) { // Sanity check.
+ numSourcePeriodic = 0;
+ }
+ } else {
+ numSourcePeriodic = parcel.readInt();
+ }
+ }
+
+ public SyncStatusInfo(SyncStatusInfo other) {
+ authorityId = other.authorityId;
+ totalElapsedTime = other.totalElapsedTime;
+ numSyncs = other.numSyncs;
+ numSourcePoll = other.numSourcePoll;
+ numSourceServer = other.numSourceServer;
+ numSourceLocal = other.numSourceLocal;
+ numSourceUser = other.numSourceUser;
+ numSourcePeriodic = other.numSourcePeriodic;
+ 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);
+ }
+
+ 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);
+ }
+
+ public long getPeriodicSyncTime(int index) {
+ if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
+ return periodicSyncTimes.get(index);
+ } else {
+ return 0;
+ }
+ }
+
+ public void removePeriodicSyncTime(int index) {
+ if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
+ periodicSyncTimes.remove(index);
+ }
+ }
+
+ /** */
+ 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);
+ }
+
+ public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
+ public SyncStatusInfo createFromParcel(Parcel in) {
+ return new SyncStatusInfo(in);
+ }
+
+ public SyncStatusInfo[] newArray(int size) {
+ return new SyncStatusInfo[size];
+ }
+ };
+
+ 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);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/android/content/SyncStatusObserver.java b/android/content/SyncStatusObserver.java
new file mode 100644
index 00000000..663378a7
--- /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 00000000..fb21641e
--- /dev/null
+++ b/android/content/UndoManager.java
@@ -0,0 +1,937 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableParcel;
+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;
+
+ 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)}.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public boolean isInUndo() {
+ return mInUndo;
+ }
+
+ 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;
+ }
+
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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}.
+ */
+ 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}.
+ */
+ 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}.
+ */
+ 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.
+ */
+ 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 00000000..1ff32d4a
--- /dev/null
+++ b/android/content/UndoOperation.java
@@ -0,0 +1,112 @@
+/*
+ * 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.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}.
+ */
+ public UndoOperation(UndoOwner owner) {
+ mOwner = owner;
+ }
+
+ /**
+ * Construct from a Parcel.
+ */
+ 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 00000000..fd257abf
--- /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 00000000..444edd0d
--- /dev/null
+++ b/android/content/UriMatcher.java
@@ -0,0 +1,279 @@
+/*
+ * 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.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()
+ {
+ mCode = NO_MATCH;
+ mWhich = -1;
+ mChildren = new ArrayList<UriMatcher>();
+ mText = null;
+ }
+
+ /**
+ * 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 = new UriMatcher();
+ if (token.equals("#")) {
+ child.mWhich = NUMBER;
+ } else if (token.equals("*")) {
+ child.mWhich = TEXT;
+ } else {
+ child.mWhich = EXACT;
+ }
+ child.mText = token;
+ node.mChildren.add(child);
+ node = child;
+ }
+ }
+ node.mCode = code;
+ }
+
+ /**
+ * 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 int mWhich;
+ private String mText;
+ private ArrayList<UriMatcher> mChildren;
+}
diff --git a/android/content/UriPermission.java b/android/content/UriPermission.java
new file mode 100644
index 00000000..df9200d7
--- /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 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/om/OverlayInfo.java b/android/content/om/OverlayInfo.java
new file mode 100644
index 00000000..1a207ba0
--- /dev/null
+++ b/android/content/om/OverlayInfo.java
@@ -0,0 +1,263 @@
+/*
+ * 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.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Immutable overlay information about a package. All PackageInfos that
+ * represent an overlay package will have a corresponding OverlayInfo.
+ *
+ * @hide
+ */
+public final class OverlayInfo implements Parcelable {
+ /**
+ * 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.
+ */
+ public static final int STATE_UNKNOWN = -1;
+
+ /**
+ * The target package of the overlay is not installed. The overlay cannot be enabled.
+ */
+ public static final int STATE_MISSING_TARGET = 0;
+
+ /**
+ * Creation of idmap file failed (e.g. no matching resources). The overlay
+ * cannot be enabled.
+ */
+ public static final int STATE_NO_IDMAP = 1;
+
+ /**
+ * The overlay is currently disabled. It can be enabled.
+ *
+ * @see IOverlayManager.setEnabled
+ */
+ public static final int STATE_DISABLED = 2;
+
+ /**
+ * The overlay is currently enabled. It can be disabled.
+ *
+ * @see IOverlayManager.setEnabled
+ */
+ public static final int STATE_ENABLED = 3;
+
+ /**
+ * Package name of the overlay package
+ */
+ public final String packageName;
+
+ /**
+ * Package name of the target package
+ */
+ public final String targetPackageName;
+
+ /**
+ * Full path to the base APK for this overlay package
+ */
+ public final String baseCodePath;
+
+ /**
+ * The state of this OverlayInfo as defined by the STATE_* constants in this class.
+ *
+ * @see #STATE_MISSING_TARGET
+ * @see #STATE_NO_IDMAP
+ * @see #STATE_DISABLED
+ * @see #STATE_ENABLED
+ */
+ public final int state;
+
+ /**
+ * User handle for which this overlay applies
+ */
+ public final int userId;
+
+ /**
+ * 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
+ */
+ public OverlayInfo(@NonNull OverlayInfo source, int state) {
+ this(source.packageName, source.targetPackageName, source.baseCodePath, state,
+ source.userId);
+ }
+
+ public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
+ @NonNull String baseCodePath, int state, int userId) {
+ this.packageName = packageName;
+ this.targetPackageName = targetPackageName;
+ this.baseCodePath = baseCodePath;
+ this.state = state;
+ this.userId = userId;
+ ensureValidState();
+ }
+
+ public OverlayInfo(Parcel source) {
+ packageName = source.readString();
+ targetPackageName = source.readString();
+ baseCodePath = source.readString();
+ state = source.readInt();
+ userId = source.readInt();
+ ensureValidState();
+ }
+
+ 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:
+ 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(baseCodePath);
+ dest.writeInt(state);
+ dest.writeInt(userId);
+ }
+
+ public static final 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.
+ */
+ public boolean isEnabled() {
+ switch (state) {
+ case STATE_ENABLED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Translate a state to a human readable string. Only intended for
+ * debugging purposes.
+ *
+ * @see #STATE_MISSING_TARGET
+ * @see #STATE_NO_IDMAP
+ * @see #STATE_DISABLED
+ * @see #STATE_ENABLED
+ *
+ * @return a human readable String representing the state.
+ */
+ public static String stateToString(int state) {
+ 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";
+ 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 + ((baseCodePath == null) ? 0 : baseCodePath.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;
+ }
+ 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 (!baseCodePath.equals(other.baseCodePath)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "OverlayInfo { overlay=" + packageName + ", target=" + targetPackageName + ", state="
+ + state + " (" + stateToString(state) + "), userId=" + userId + " }";
+ }
+}
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
new file mode 100644
index 00000000..48587b36
--- /dev/null
+++ b/android/content/pm/ActivityInfo.java
@@ -0,0 +1,1349 @@
+/*
+ * 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.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 &lt;activity&gt; and
+ * &lt;receiver&gt; 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
+ */
+ 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
+ */
+ 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;
+
+ /**
+ * 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({
+ 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
+ */
+ 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#setVrMode(boolean)}.
+ */
+ 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;
+
+
+ /**
+ * @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.
+ */
+ 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;
+
+ /** @hide */
+ @IntDef({
+ 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,
+ 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
+ */
+ 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;
+ 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;
+ }
+
+ /**
+ * 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's orientation is fixed.
+ * @hide
+ */
+ public boolean isFixedOrientation() {
+ return isFixedOrientationLandscape() || isFixedOrientationPortrait()
+ || screenOrientation == SCREEN_ORIENTATION_LOCKED;
+ }
+
+ /**
+ * Returns true if the specified orientation is considered fixed.
+ * @hide
+ */
+ static public boolean isFixedOrientation(int orientation) {
+ return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
+ }
+
+ /**
+ * 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
+ */
+ public boolean supportsPictureInPicture() {
+ return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
+ }
+
+ /** @hide */
+ 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 || theme != 0) {
+ pw.println(prefix + "launchMode=" + launchMode
+ + " flags=0x" + Integer.toHexString(flags)
+ + " 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);
+ }
+ 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.writeString(permission);
+ dest.writeString(taskAffinity);
+ dest.writeString(targetActivity);
+ dest.writeInt(flags);
+ dest.writeInt(screenOrientation);
+ dest.writeInt(configChanges);
+ dest.writeInt(softInputMode);
+ dest.writeInt(uiOptions);
+ dest.writeString(parentActivityName);
+ dest.writeInt(persistableMode);
+ dest.writeInt(maxRecents);
+ dest.writeInt(lockTaskLaunchMode);
+ if (windowLayout != null) {
+ dest.writeInt(1);
+ dest.writeInt(windowLayout.width);
+ dest.writeFloat(windowLayout.widthFraction);
+ dest.writeInt(windowLayout.height);
+ dest.writeFloat(windowLayout.heightFraction);
+ dest.writeInt(windowLayout.gravity);
+ dest.writeInt(windowLayout.minWidth);
+ dest.writeInt(windowLayout.minHeight);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(resizeMode);
+ dest.writeString(requestedVrComponent);
+ dest.writeInt(rotationAnimation);
+ dest.writeInt(colorMode);
+ dest.writeFloat(maxAspectRatio);
+ }
+
+ /**
+ * Determines whether the {@link Activity} is considered translucent or floating.
+ * @hide
+ */
+ public static boolean isTranslucentOrFloating(TypedArray attributes) {
+ final boolean isTranslucent =
+ attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent,
+ false);
+ final boolean isSwipeToDismiss = !attributes.hasValue(
+ com.android.internal.R.styleable.Window_windowIsTranslucent)
+ && attributes.getBoolean(
+ com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
+ final boolean isFloating =
+ attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating,
+ false);
+
+ return isFloating || isTranslucent || isSwipeToDismiss;
+ }
+
+ public static final 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.readString();
+ taskAffinity = source.readString();
+ targetActivity = source.readString();
+ flags = source.readInt();
+ screenOrientation = source.readInt();
+ configChanges = source.readInt();
+ softInputMode = source.readInt();
+ uiOptions = source.readInt();
+ parentActivityName = source.readString();
+ persistableMode = source.readInt();
+ maxRecents = source.readInt();
+ lockTaskLaunchMode = source.readInt();
+ if (source.readInt() == 1) {
+ windowLayout = new WindowLayout(source);
+ }
+ resizeMode = source.readInt();
+ requestedVrComponent = source.readString();
+ rotationAnimation = source.readInt();
+ colorMode = source.readInt();
+ maxAspectRatio = 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;
+ }
+
+ WindowLayout(Parcel source) {
+ width = source.readInt();
+ widthFraction = source.readFloat();
+ height = source.readInt();
+ heightFraction = source.readFloat();
+ gravity = source.readInt();
+ minWidth = source.readInt();
+ minHeight = source.readInt();
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
new file mode 100644
index 00000000..664bcbca
--- /dev/null
+++ b/android/content/pm/ApplicationInfo.java
@@ -0,0 +1,1577 @@
+/*
+ * 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.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.util.SparseArray;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.Comparator;
+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
+ * &lt;application&gt; 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
+ */
+ public int fullBackupContent = 0;
+
+ /**
+ * 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 &lt;application&gt; 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 &lt;application&gt; 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 &lt;application&gt; 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 &lt;application&gt; 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 &lt;application&gt; 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
+ * accomodate different screen densities. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_anyDensity
+ * android:anyDensity}.
+ */
+ 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 &lt;application&gt; 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 &lt;application&gt; 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 &lt;application&gt; 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 &lt;application&gt; 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} does not honor this flag.
+ *
+ * <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 &lt;application&gt; 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 &lt;application&gt; tag.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_CANT_SAVE_STATE = 1<<1;
+
+ /**
+ * Value for {@link #privateFlags}: Set to true if the application has been
+ * installed using the forward lock option.
+ *
+ * NOTE: DO NOT CHANGE THIS VALUE! It is saved in packages.xml.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_FORWARD_LOCK = 1<<2;
+
+ /**
+ * Value for {@link #privateFlags}: set to {@code true} if the application
+ * is permitted to hold privileged permissions.
+ *
+ * {@hide}
+ */
+ 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;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
+ PRIVATE_FLAG_HIDDEN,
+ PRIVATE_FLAG_CANT_SAVE_STATE,
+ PRIVATE_FLAG_FORWARD_LOCK,
+ PRIVATE_FLAG_PRIVILEGED,
+ PRIVATE_FLAG_HAS_DOMAIN_URLS,
+ PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
+ PRIVATE_FLAG_DIRECT_BOOT_AWARE,
+ PRIVATE_FLAG_INSTANT,
+ PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE,
+ PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION,
+ PRIVATE_FLAG_BACKUP_IN_FOREGROUND,
+ PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
+ PRIVATE_FLAG_ISOLATED_SPLIT_LOADING,
+ PRIVATE_FLAG_VIRTUAL_PRELOAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationInfoPrivateFlags {}
+
+ /**
+ * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
+ * @hide
+ */
+ 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 &lt;supports-screens&gt; 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 &lt;supports-screens&gt; 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 &lt;supports-screens&gt; 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;
+
+ /** @removed */
+ @Deprecated
+ 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} */
+ public String scanSourceDir;
+ /** {@hide} */
+ 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 &lt;manifest&gt; 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}
+ */
+ public String[] resourceDirs;
+
+ /**
+ * String retrieved from the seinfo tag found in selinux policy. This value
+ * can be overridden with a value set through the mac_permissions.xml policy
+ * construct. This value is useful in setting an SELinux security context on
+ * the process as well as its data directory. The String default is being used
+ * here to represent a catchall label when no policy matches.
+ *
+ * {@hide}
+ */
+ public String seInfo = "default";
+
+ /**
+ * 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;
+
+ /**
+ * 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
+ */
+ 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
+ */
+ 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}
+ */
+ 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}
+ */
+ 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 int versionCode;
+
+ /**
+ * 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
+ */
+ public int enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+ /**
+ * For convenient access to package's install location.
+ * @hide
+ */
+ 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
+ */
+ public int targetSandboxVersion;
+
+ /**
+ * 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;
+
+ 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=" + versionCode
+ + " targetSandboxVersion=" + targetSandboxVersion);
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ if (manageSpaceActivityName != null) {
+ pw.println(prefix + "manageSpaceActivityName=" + manageSpaceActivityName);
+ }
+ if (descriptionRes != 0) {
+ pw.println(prefix + "description=0x" + Integer.toHexString(descriptionRes));
+ }
+ if (uiOptions != 0) {
+ pw.println(prefix + "uiOptions=0x" + Integer.toHexString(uiOptions));
+ }
+ pw.println(prefix + "supportsRtl=" + (hasRtlSupport() ? "true" : "false"));
+ if (fullBackupContent > 0) {
+ pw.println(prefix + "fullBackupContent=@xml/" + fullBackupContent);
+ } else {
+ pw.println(prefix + "fullBackupContent="
+ + (fullBackupContent < 0 ? "false" : "true"));
+ }
+ if (networkSecurityConfigRes != 0) {
+ pw.println(prefix + "networkSecurityConfigRes=0x"
+ + Integer.toHexString(networkSecurityConfigRes));
+ }
+ if (category != CATEGORY_UNDEFINED) {
+ pw.println(prefix + "category=" + category);
+ }
+ }
+ super.dumpBack(pw, prefix);
+ }
+
+ /**
+ * @return true if "supportsRtl" has been set to true in the AndroidManifest
+ * @hide
+ */
+ 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());
+ }
+
+ private final Collator sCollator = Collator.getInstance();
+ 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;
+ dataDir = orig.dataDir;
+ deviceProtectedDataDir = orig.deviceProtectedDataDir;
+ credentialProtectedDataDir = orig.credentialProtectedDataDir;
+ uid = orig.uid;
+ minSdkVersion = orig.minSdkVersion;
+ targetSdkVersion = orig.targetSdkVersion;
+ versionCode = orig.versionCode;
+ enabled = orig.enabled;
+ enabledSetting = orig.enabledSetting;
+ installLocation = orig.installLocation;
+ manageSpaceActivityName = orig.manageSpaceActivityName;
+ descriptionRes = orig.descriptionRes;
+ uiOptions = orig.uiOptions;
+ backupAgentName = orig.backupAgentName;
+ fullBackupContent = orig.fullBackupContent;
+ networkSecurityConfigRes = orig.networkSecurityConfigRes;
+ category = orig.category;
+ targetSandboxVersion = orig.targetSandboxVersion;
+ classLoaderName = orig.classLoaderName;
+ splitClassLoaderNames = orig.splitClassLoaderNames;
+ }
+
+ 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) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeString(taskAffinity);
+ dest.writeString(permission);
+ dest.writeString(processName);
+ dest.writeString(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.writeString(scanSourceDir);
+ dest.writeString(scanPublicSourceDir);
+ dest.writeString(sourceDir);
+ dest.writeString(publicSourceDir);
+ dest.writeStringArray(splitNames);
+ dest.writeStringArray(splitSourceDirs);
+ dest.writeStringArray(splitPublicSourceDirs);
+ dest.writeSparseArray((SparseArray) splitDependencies);
+ dest.writeString(nativeLibraryDir);
+ dest.writeString(secondaryNativeLibraryDir);
+ dest.writeString(nativeLibraryRootDir);
+ dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0);
+ dest.writeString(primaryCpuAbi);
+ dest.writeString(secondaryCpuAbi);
+ dest.writeStringArray(resourceDirs);
+ dest.writeString(seInfo);
+ dest.writeString(seInfoUser);
+ dest.writeStringArray(sharedLibraryFiles);
+ dest.writeString(dataDir);
+ dest.writeString(deviceProtectedDataDir);
+ dest.writeString(credentialProtectedDataDir);
+ dest.writeInt(uid);
+ dest.writeInt(minSdkVersion);
+ dest.writeInt(targetSdkVersion);
+ dest.writeInt(versionCode);
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(enabledSetting);
+ dest.writeInt(installLocation);
+ dest.writeString(manageSpaceActivityName);
+ dest.writeString(backupAgentName);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(uiOptions);
+ dest.writeInt(fullBackupContent);
+ dest.writeInt(networkSecurityConfigRes);
+ dest.writeInt(category);
+ dest.writeInt(targetSandboxVersion);
+ dest.writeString(classLoaderName);
+ dest.writeStringArray(splitClassLoaderNames);
+ }
+
+ public static final Parcelable.Creator<ApplicationInfo> CREATOR
+ = new Parcelable.Creator<ApplicationInfo>() {
+ public ApplicationInfo createFromParcel(Parcel source) {
+ return new ApplicationInfo(source);
+ }
+ public ApplicationInfo[] newArray(int size) {
+ return new ApplicationInfo[size];
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ private ApplicationInfo(Parcel source) {
+ super(source);
+ taskAffinity = source.readString();
+ permission = source.readString();
+ processName = source.readString();
+ className = source.readString();
+ 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.readString();
+ scanPublicSourceDir = source.readString();
+ sourceDir = source.readString();
+ publicSourceDir = source.readString();
+ splitNames = source.readStringArray();
+ splitSourceDirs = source.readStringArray();
+ splitPublicSourceDirs = source.readStringArray();
+ splitDependencies = source.readSparseArray(null);
+ nativeLibraryDir = source.readString();
+ secondaryNativeLibraryDir = source.readString();
+ nativeLibraryRootDir = source.readString();
+ nativeLibraryRootRequiresIsa = source.readInt() != 0;
+ primaryCpuAbi = source.readString();
+ secondaryCpuAbi = source.readString();
+ resourceDirs = source.readStringArray();
+ seInfo = source.readString();
+ seInfoUser = source.readString();
+ sharedLibraryFiles = source.readStringArray();
+ dataDir = source.readString();
+ deviceProtectedDataDir = source.readString();
+ credentialProtectedDataDir = source.readString();
+ uid = source.readInt();
+ minSdkVersion = source.readInt();
+ targetSdkVersion = source.readInt();
+ versionCode = source.readInt();
+ enabled = source.readInt() != 0;
+ enabledSetting = source.readInt();
+ installLocation = source.readInt();
+ manageSpaceActivityName = source.readString();
+ backupAgentName = source.readString();
+ descriptionRes = source.readInt();
+ uiOptions = source.readInt();
+ fullBackupContent = source.readInt();
+ networkSecurityConfigRes = source.readInt();
+ category = source.readInt();
+ targetSandboxVersion = source.readInt();
+ classLoaderName = source.readString();
+ splitClassLoaderNames = source.readStringArray();
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+ }
+
+ /**
+ * @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();
+ }
+
+ private boolean isPackageUnavailable(PackageManager pm) {
+ try {
+ return pm.getPackageInfo(packageName, 0) == null;
+ } catch (NameNotFoundException ex) {
+ return true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isForwardLocked() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public boolean isSystemApp() {
+ return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public boolean isPrivilegedApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isUpdatedSystemApp() {
+ return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
+ /** @hide */
+ public boolean isInternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+ }
+
+ /** @hide */
+ public boolean isExternalAsec() {
+ return TextUtils.isEmpty(volumeUuid)
+ && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+ }
+
+ /** @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;
+ }
+
+ /** @hide */
+ public boolean isPartiallyDirectBootAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
+ }
+
+ /** @hide */
+ public boolean isEncryptionAware() {
+ return isDirectBootAware() || isPartiallyDirectBootAware();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isInstantApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isRequiredForSystemUser() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 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;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isStaticSharedLibrary() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
+ }
+
+ /**
+ * Returns whether or not this application was installed as a virtual preload.
+ */
+ public boolean isVirtualPreload() {
+ return (privateFlags & PRIVATE_FLAG_VIRTUAL_PRELOAD) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ @Override protected ApplicationInfo getApplicationInfo() {
+ return this;
+ }
+
+ /** {@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 String getCodePath() { return scanSourceDir; }
+ /** {@hide} */ public String getBaseCodePath() { return sourceDir; }
+ /** {@hide} */ public String[] getSplitCodePaths() { return splitSourceDirs; }
+ /** {@hide} */ public String getResourcePath() { return scanPublicSourceDir; }
+ /** {@hide} */ public String getBaseResourcePath() { return publicSourceDir; }
+ /** {@hide} */ public String[] getSplitResourcePaths() { return splitPublicSourceDirs; }
+}
diff --git a/android/content/pm/AppsQueryHelper.java b/android/content/pm/AppsQueryHelper.java
new file mode 100644
index 00000000..6cb7f774
--- /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 00000000..067363d4
--- /dev/null
+++ b/android/content/pm/AuxiliaryResolveInfo.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+/**
+ * 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 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 activity to launch if there's an installation failure. */
+ public final ComponentName installFailureActivity;
+ /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */
+ public final String splitName;
+ /** Whether or not instant resolution needs the second phase */
+ public final boolean needsPhaseTwo;
+ /** Opaque token to track the instant application resolution */
+ public final String token;
+ /** The version code of the package */
+ public final int versionCode;
+ /** An intent to start upon failure to install */
+ public final Intent failureIntent;
+
+ /** Create a response for installing an instant application. */
+ public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo,
+ @NonNull IntentFilter orig,
+ @Nullable String splitName,
+ @NonNull String token,
+ boolean needsPhase2,
+ @Nullable Intent failureIntent) {
+ super(orig);
+ this.resolveInfo = resolveInfo;
+ this.packageName = resolveInfo.getPackageName();
+ this.splitName = splitName;
+ this.token = token;
+ this.needsPhaseTwo = needsPhase2;
+ this.versionCode = resolveInfo.getVersionCode();
+ this.failureIntent = failureIntent;
+ this.installFailureActivity = null;
+ }
+
+ /** Create a response for installing a split on demand. */
+ public AuxiliaryResolveInfo(@NonNull String packageName,
+ @Nullable String splitName,
+ @Nullable ComponentName failureActivity,
+ int versionCode,
+ @Nullable Intent failureIntent) {
+ super();
+ this.packageName = packageName;
+ this.installFailureActivity = failureActivity;
+ this.splitName = splitName;
+ this.versionCode = versionCode;
+ this.resolveInfo = null;
+ this.token = null;
+ this.needsPhaseTwo = false;
+ this.failureIntent = failureIntent;
+ }
+} \ No newline at end of file
diff --git a/android/content/pm/BaseParceledListSlice.java b/android/content/pm/BaseParceledListSlice.java
new file mode 100644
index 00000000..5877a09b
--- /dev/null
+++ b/android/content/pm/BaseParceledListSlice.java
@@ -0,0 +1,211 @@
+/*
+ * 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.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.MAX_IPC_SIZE;
+
+ 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.getName() + " in list of type "
+ + expected.getName());
+ }
+ }
+
+ 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);
+
+ 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 00000000..b78c71d7
--- /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 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 00000000..6b1222f5
--- /dev/null
+++ b/android/content/pm/ComponentInfo.java
@@ -0,0 +1,258 @@
+/*
+ * 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.content.ComponentName;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+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
+ * &lt;activity&gt;, &lt;receiver&gt;, &lt;service&gt;, or
+ * &lt;provider&gt; 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;
+
+ /** @removed */
+ @Deprecated
+ public boolean encryptionAware = 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;
+ encryptionAware = directBootAware = orig.directBootAware;
+ }
+
+ @Override public CharSequence loadLabel(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} */
+ 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);
+ if ((parcelableFlags & Parcelable.PARCELABLE_ELIDE_DUPLICATES) != 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ applicationInfo.writeToParcel(dest, parcelableFlags);
+ }
+ dest.writeString(processName);
+ dest.writeString(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);
+ final boolean hasApplicationInfo = (source.readInt() != 0);
+ if (hasApplicationInfo) {
+ applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+ }
+ processName = source.readString();
+ splitName = source.readString();
+ descriptionRes = source.readInt();
+ enabled = (source.readInt() != 0);
+ exported = (source.readInt() != 0);
+ encryptionAware = 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 00000000..8edd436f
--- /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 &lt;uses-configuration&gt; and &lt;uses-feature&gt; 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 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/EphemeralIntentFilter.java b/android/content/pm/EphemeralIntentFilter.java
new file mode 100644
index 00000000..1dbbf816
--- /dev/null
+++ b/android/content/pm/EphemeralIntentFilter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.IntentFilter;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information about an ephemeral application intent filter.
+ * @hide
+ * @removed
+ */
+@Deprecated
+@SystemApi
+public final class EphemeralIntentFilter implements Parcelable {
+ private final InstantAppIntentFilter mInstantAppIntentFilter;
+
+ public EphemeralIntentFilter(@Nullable String splitName, @NonNull List<IntentFilter> filters) {
+ mInstantAppIntentFilter = new InstantAppIntentFilter(splitName, filters);
+ }
+
+ EphemeralIntentFilter(@NonNull InstantAppIntentFilter intentFilter) {
+ mInstantAppIntentFilter = intentFilter;
+ }
+
+ EphemeralIntentFilter(Parcel in) {
+ mInstantAppIntentFilter = in.readParcelable(null /*loader*/);
+ }
+
+ public String getSplitName() {
+ return mInstantAppIntentFilter.getSplitName();
+ }
+
+ public List<IntentFilter> getFilters() {
+ return mInstantAppIntentFilter.getFilters();
+ }
+
+ /** @hide */
+ InstantAppIntentFilter getInstantAppIntentFilter() {
+ return mInstantAppIntentFilter;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mInstantAppIntentFilter, flags);
+ }
+
+ public static final Parcelable.Creator<EphemeralIntentFilter> CREATOR
+ = new Parcelable.Creator<EphemeralIntentFilter>() {
+ @Override
+ public EphemeralIntentFilter createFromParcel(Parcel in) {
+ return new EphemeralIntentFilter(in);
+ }
+ @Override
+ public EphemeralIntentFilter[] newArray(int size) {
+ return new EphemeralIntentFilter[size];
+ }
+ };
+}
diff --git a/android/content/pm/EphemeralResolveInfo.java b/android/content/pm/EphemeralResolveInfo.java
new file mode 100644
index 00000000..12131a3e
--- /dev/null
+++ b/android/content/pm/EphemeralResolveInfo.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.IntentFilter;
+import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Information about an ephemeral application.
+ * @hide
+ * @removed
+ */
+@Deprecated
+@SystemApi
+public final class EphemeralResolveInfo implements Parcelable {
+ /** Algorithm that will be used to generate the domain digest */
+ public static final String SHA_ALGORITHM = "SHA-256";
+
+ private final InstantAppResolveInfo mInstantAppResolveInfo;
+ @Deprecated
+ private final List<IntentFilter> mLegacyFilters;
+
+ @Deprecated
+ public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName,
+ @NonNull List<IntentFilter> filters) {
+ if (uri == null || packageName == null || filters == null || filters.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+ final List<EphemeralIntentFilter> ephemeralFilters = new ArrayList<>(1);
+ ephemeralFilters.add(new EphemeralIntentFilter(packageName, filters));
+ mInstantAppResolveInfo = new InstantAppResolveInfo(uri.getHost(), packageName,
+ createInstantAppIntentFilterList(ephemeralFilters));
+ mLegacyFilters = new ArrayList<IntentFilter>(filters.size());
+ mLegacyFilters.addAll(filters);
+ }
+
+ @Deprecated
+ public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
+ @Nullable List<EphemeralIntentFilter> filters) {
+ this(digest, packageName, filters, -1 /*versionCode*/);
+ }
+
+ public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
+ @Nullable List<EphemeralIntentFilter> filters, int versionCode) {
+ mInstantAppResolveInfo = new InstantAppResolveInfo(
+ digest.getInstantAppDigest(), packageName,
+ createInstantAppIntentFilterList(filters), versionCode);
+ mLegacyFilters = null;
+ }
+
+ public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName,
+ @Nullable List<EphemeralIntentFilter> filters) {
+ this(new EphemeralDigest(hostName), packageName, filters);
+ }
+
+ EphemeralResolveInfo(Parcel in) {
+ mInstantAppResolveInfo = in.readParcelable(null /*loader*/);
+ mLegacyFilters = new ArrayList<IntentFilter>();
+ in.readList(mLegacyFilters, null /*loader*/);
+ }
+
+ /** @hide */
+ public InstantAppResolveInfo getInstantAppResolveInfo() {
+ return mInstantAppResolveInfo;
+ }
+
+ private static List<InstantAppIntentFilter> createInstantAppIntentFilterList(
+ List<EphemeralIntentFilter> filters) {
+ if (filters == null) {
+ return null;
+ }
+ final int filterCount = filters.size();
+ final List<InstantAppIntentFilter> returnList = new ArrayList<>(filterCount);
+ for (int i = 0; i < filterCount; i++) {
+ returnList.add(filters.get(i).getInstantAppIntentFilter());
+ }
+ return returnList;
+ }
+
+ public byte[] getDigestBytes() {
+ return mInstantAppResolveInfo.getDigestBytes();
+ }
+
+ public int getDigestPrefix() {
+ return mInstantAppResolveInfo.getDigestPrefix();
+ }
+
+ public String getPackageName() {
+ return mInstantAppResolveInfo.getPackageName();
+ }
+
+ public List<EphemeralIntentFilter> getIntentFilters() {
+ final List<InstantAppIntentFilter> filters = mInstantAppResolveInfo.getIntentFilters();
+ final int filterCount = filters.size();
+ final List<EphemeralIntentFilter> returnList = new ArrayList<>(filterCount);
+ for (int i = 0; i < filterCount; i++) {
+ returnList.add(new EphemeralIntentFilter(filters.get(i)));
+ }
+ return returnList;
+ }
+
+ public int getVersionCode() {
+ return mInstantAppResolveInfo.getVersionCode();
+ }
+
+ @Deprecated
+ public List<IntentFilter> getFilters() {
+ return mLegacyFilters;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mInstantAppResolveInfo, flags);
+ out.writeList(mLegacyFilters);
+ }
+
+ public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR
+ = new Parcelable.Creator<EphemeralResolveInfo>() {
+ @Override
+ public EphemeralResolveInfo createFromParcel(Parcel in) {
+ return new EphemeralResolveInfo(in);
+ }
+ @Override
+ public EphemeralResolveInfo[] newArray(int size) {
+ return new EphemeralResolveInfo[size];
+ }
+ };
+
+ /**
+ * Helper class to generate and store each of the digests and prefixes
+ * sent to the Ephemeral Resolver.
+ * <p>
+ * Since intent filters may want to handle multiple hosts within a
+ * domain [eg “*.google.com”], the resolver is presented with multiple
+ * hash prefixes. For example, "a.b.c.d.e" generates digests for
+ * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e".
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class EphemeralDigest implements Parcelable {
+ private final InstantAppDigest mInstantAppDigest;
+
+ public EphemeralDigest(@NonNull String hostName) {
+ this(hostName, -1 /*maxDigests*/);
+ }
+
+ /** @hide */
+ public EphemeralDigest(@NonNull String hostName, int maxDigests) {
+ mInstantAppDigest = new InstantAppDigest(hostName, maxDigests);
+ }
+
+ EphemeralDigest(Parcel in) {
+ mInstantAppDigest = in.readParcelable(null /*loader*/);
+ }
+
+ /** @hide */
+ InstantAppDigest getInstantAppDigest() {
+ return mInstantAppDigest;
+ }
+
+ public byte[][] getDigestBytes() {
+ return mInstantAppDigest.getDigestBytes();
+ }
+
+ public int[] getDigestPrefix() {
+ return mInstantAppDigest.getDigestPrefix();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mInstantAppDigest, flags);
+ }
+
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<EphemeralDigest> CREATOR =
+ new Parcelable.Creator<EphemeralDigest>() {
+ @Override
+ public EphemeralDigest createFromParcel(Parcel in) {
+ return new EphemeralDigest(in);
+ }
+ @Override
+ public EphemeralDigest[] newArray(int size) {
+ return new EphemeralDigest[size];
+ }
+ };
+ }
+}
diff --git a/android/content/pm/FallbackCategoryProvider.java b/android/content/pm/FallbackCategoryProvider.java
new file mode 100644
index 00000000..a0a11aa5
--- /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 00000000..79a6eea9
--- /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 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 00000000..9ee6fa24
--- /dev/null
+++ b/android/content/pm/FeatureInfo.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+/**
+ * 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.writeString(name);
+ dest.writeInt(version);
+ dest.writeInt(reqGlEsVersion);
+ dest.writeInt(flags);
+ }
+
+ public static final 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.readString();
+ 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/InstantAppInfo.java b/android/content/pm/InstantAppInfo.java
new file mode 100644
index 00000000..67afc928
--- /dev/null
+++ b/android/content/pm/InstantAppInfo.java
@@ -0,0 +1,149 @@
+/*
+ * 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.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
+ */
+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 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 00000000..257ab967
--- /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 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 00000000..38f02256
--- /dev/null
+++ b/android/content/pm/InstantAppRequest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.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;
+ /** 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;
+
+ public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
+ String resolvedType, String callingPackage, int userId, Bundle verificationBundle,
+ boolean resolveForStart) {
+ this.responseObj = responseObj;
+ this.origIntent = origIntent;
+ this.resolvedType = resolvedType;
+ this.callingPackage = callingPackage;
+ this.userId = userId;
+ this.verificationBundle = verificationBundle;
+ this.resolveForStart = resolveForStart;
+ }
+}
diff --git a/android/content/pm/InstantAppResolveInfo.java b/android/content/pm/InstantAppResolveInfo.java
new file mode 100644
index 00000000..22e994f4
--- /dev/null
+++ b/android/content/pm/InstantAppResolveInfo.java
@@ -0,0 +1,250 @@
+/*
+ * 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.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Information about an instant application.
+ * @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 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 int mVersionCode;
+
+ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
+ @Nullable List<InstantAppIntentFilter> filters, int versionCode) {
+ // 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<InstantAppIntentFilter>(filters.size());
+ mFilters.addAll(filters);
+ } else {
+ mFilters = null;
+ }
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName,
+ @Nullable List<InstantAppIntentFilter> filters) {
+ this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/);
+ }
+
+ InstantAppResolveInfo(Parcel in) {
+ mDigest = in.readParcelable(null /*loader*/);
+ mPackageName = in.readString();
+ mFilters = new ArrayList<InstantAppIntentFilter>();
+ in.readList(mFilters, null /*loader*/);
+ mVersionCode = in.readInt();
+ }
+
+ public byte[] getDigestBytes() {
+ return mDigest.getDigestBytes()[0];
+ }
+
+ public int getDigestPrefix() {
+ return mDigest.getDigestPrefix()[0];
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public List<InstantAppIntentFilter> getIntentFilters() {
+ return mFilters;
+ }
+
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mDigest, flags);
+ out.writeString(mPackageName);
+ out.writeList(mFilters);
+ out.writeInt(mVersionCode);
+ }
+
+ public static final 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 {
+ private static final int DIGEST_MASK = 0xfffff000;
+ private static final int DIGEST_PREFIX_COUNT = 5;
+ /** Full digest of the domain hashes */
+ private final byte[][] mDigestBytes;
+ /** The first 4 bytes of the domain hashes */
+ private final int[] mDigestPrefix;
+
+ 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 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();
+ }
+
+ public byte[][] getDigestBytes() {
+ return mDigestBytes;
+ }
+
+ public int[] getDigestPrefix() {
+ return mDigestPrefix;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ 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);
+ }
+
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<InstantAppDigest> CREATOR =
+ new Parcelable.Creator<InstantAppDigest>() {
+ @Override
+ public InstantAppDigest createFromParcel(Parcel in) {
+ 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 00000000..3faa9517
--- /dev/null
+++ b/android/content/pm/InstrumentationInfo.java
@@ -0,0 +1,214 @@
+/*
+ * 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 &lt;instrumentation&gt; 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 &lt;manifest&gt; 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} 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;
+ 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.writeString(targetPackage);
+ dest.writeString(targetProcesses);
+ dest.writeString(sourceDir);
+ dest.writeString(publicSourceDir);
+ dest.writeStringArray(splitNames);
+ dest.writeStringArray(splitSourceDirs);
+ dest.writeStringArray(splitPublicSourceDirs);
+ dest.writeSparseArray((SparseArray) splitDependencies);
+ dest.writeString(dataDir);
+ dest.writeString(deviceProtectedDataDir);
+ dest.writeString(credentialProtectedDataDir);
+ dest.writeString(nativeLibraryDir);
+ dest.writeString(secondaryNativeLibraryDir);
+ dest.writeInt((handleProfiling == false) ? 0 : 1);
+ dest.writeInt((functionalTest == false) ? 0 : 1);
+ }
+
+ public static final 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.readString();
+ targetProcesses = source.readString();
+ sourceDir = source.readString();
+ publicSourceDir = source.readString();
+ splitNames = source.readStringArray();
+ splitSourceDirs = source.readStringArray();
+ splitPublicSourceDirs = source.readStringArray();
+ splitDependencies = source.readSparseArray(null);
+ dataDir = source.readString();
+ deviceProtectedDataDir = source.readString();
+ credentialProtectedDataDir = source.readString();
+ nativeLibraryDir = source.readString();
+ secondaryNativeLibraryDir = source.readString();
+ 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.nativeLibraryDir = nativeLibraryDir;
+ ai.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
+ }
+}
diff --git a/android/content/pm/IntentFilterVerificationInfo.java b/android/content/pm/IntentFilterVerificationInfo.java
new file mode 100644
index 00000000..068973b8
--- /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 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 00000000..643db7ef
--- /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 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 00000000..68b0046e
--- /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 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 00000000..e9c95885
--- /dev/null
+++ b/android/content/pm/LauncherActivityInfo.java
@@ -0,0 +1,176 @@
+/*
+ * 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.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;
+
+ 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 00000000..aa9562ff
--- /dev/null
+++ b/android/content/pm/LauncherApps.java
@@ -0,0 +1,1477 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+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.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.AdaptiveIconDrawable;
+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 com.android.internal.util.Preconditions;
+
+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.Collections;
+import java.util.List;
+
+/**
+ * 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} &amp; {@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;
+ private final ILauncherApps mService;
+ private final PackageManager mPm;
+ private final UserManager mUserManager;
+
+ private List<CallbackMessageHandler> mCallbacks
+ = new ArrayList<CallbackMessageHandler>();
+
+ /**
+ * 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.
+ *
+ * @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 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.
+ */
+ 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;
+
+ /** @hide kept for unit tests */
+ @Deprecated
+ public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
+
+ /**
+ * Does not retrieve CHOOSER only shortcuts.
+ * TODO: Add another flag for MATCH_ALL_PINNED
+ * @hide
+ */
+ public static final int FLAG_MATCH_ALL_KINDS =
+ FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
+
+ /** @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,
+ value = {
+ FLAG_MATCH_DYNAMIC,
+ FLAG_MATCH_PINNED,
+ FLAG_MATCH_MANIFEST,
+ FLAG_GET_KEY_FIELDS_ONLY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface QueryFlags {}
+
+ long mChangedSince;
+
+ @Nullable
+ String mPackage;
+
+ @Nullable
+ List<String> mShortcutIds;
+
+ @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&mdash;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, 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_GET_KEY_FIELDS_ONLY}
+ * </ul>
+ */
+ public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
+ mQueryFlags = queryFlags;
+ return this;
+ }
+ }
+
+ /** @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, and the target
+ * user is different from the calling user, in order to help developers to detect it.
+ */
+ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) {
+ if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) {
+ 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 launchable activities that match {@link Intent#ACTION_MAIN} and
+ * {@link Intent#CATEGORY_LAUNCHER}, for a specified user.
+ *
+ * @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.getPackageName(),
+ component, sourceBounds, opts, user);
+ } 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.getPackageName(),
+ 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();
+ }
+ }
+
+ /**
+ * 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 {
+ Preconditions.checkNotNull(packageName, "packageName");
+ Preconditions.checkNotNull(packageName, "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();
+ }
+ }
+
+ /**
+ * Checks if the activity exists and it enabled for a profile.
+ *
+ * @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.
+ *
+ * <p>Only the default launcher can access the shortcut information.
+ *
+ * <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();
+ }
+ }
+
+ /**
+ * 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 {
+ return mService.getShortcuts(mContext.getPackageName(),
+ query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
+ query.mQueryFlags, 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();
+ }
+ }
+
+ /**
+ * @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();
+ }
+ }
+
+ /**
+ * 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);
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+ if (bmp != null) {
+ BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
+ if (shortcut.hasAdaptiveBitmap()) {
+ return new AdaptiveIconDrawable(null, dr);
+ } else {
+ return dr;
+ }
+ }
+ return null;
+ } finally {
+ try {
+ pfd.close();
+ } catch (IOException ignore) {
+ }
+ }
+ } 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 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());
+ }
+
+ 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, 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 current 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 current 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)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackagesSuspended(packageNames, 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;
+ 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);
+ 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, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageNames = packageNames;
+ info.user = user;
+ 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();
+ }
+ }
+
+ /**
+ * 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(value = {REQUEST_TYPE_SHORTCUT})
+ @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.
+ *
+ * @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 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;
+ }
+ }
+}
diff --git a/android/content/pm/LimitedLengthInputStream.java b/android/content/pm/LimitedLengthInputStream.java
new file mode 100644
index 00000000..e7872771
--- /dev/null
+++ b/android/content/pm/LimitedLengthInputStream.java
@@ -0,0 +1,94 @@
+package android.content.pm;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * 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;
+ Arrays.checkOffsetAndCount(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 00000000..11f4b947
--- /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/PackageBackwardCompatibility.java b/android/content/pm/PackageBackwardCompatibility.java
new file mode 100644
index 00000000..cee25994
--- /dev/null
+++ b/android/content/pm/PackageBackwardCompatibility.java
@@ -0,0 +1,89 @@
+/*
+ * 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.pm.PackageParser.Package;
+import android.os.Build;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Modifies {@link Package} in order to maintain backwards compatibility.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class PackageBackwardCompatibility {
+
+ private static final String ANDROID_TEST_MOCK = "android.test.mock";
+
+ private static final String ANDROID_TEST_RUNNER = "android.test.runner";
+
+ private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy";
+
+ /**
+ * Modify the shared libraries in the supplied {@link Package} to maintain backwards
+ * compatibility.
+ *
+ * @param pkg the {@link Package} to modify.
+ */
+ @VisibleForTesting
+ public static void modifySharedLibraries(Package pkg) {
+ ArrayList<String> usesLibraries = pkg.usesLibraries;
+ ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries;
+
+ // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library
+ // to be accessible so this maintains backward compatibility by adding the
+ // org.apache.http.legacy library to those packages.
+ if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) {
+ boolean apacheHttpLegacyPresent = isLibraryPresent(
+ usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY);
+ if (!apacheHttpLegacyPresent) {
+ usesLibraries = ArrayUtils.add(usesLibraries, APACHE_HTTP_LEGACY);
+ }
+ }
+
+ // android.test.runner has a dependency on android.test.mock so if android.test.runner
+ // is present but android.test.mock is not then add android.test.mock.
+ boolean androidTestMockPresent = isLibraryPresent(
+ usesLibraries, usesOptionalLibraries, ANDROID_TEST_MOCK);
+ if (ArrayUtils.contains(usesLibraries, ANDROID_TEST_RUNNER) && !androidTestMockPresent) {
+ usesLibraries.add(ANDROID_TEST_MOCK);
+ }
+ if (ArrayUtils.contains(usesOptionalLibraries, ANDROID_TEST_RUNNER)
+ && !androidTestMockPresent) {
+ usesOptionalLibraries.add(ANDROID_TEST_MOCK);
+ }
+
+ pkg.usesLibraries = usesLibraries;
+ pkg.usesOptionalLibraries = usesOptionalLibraries;
+ }
+
+ private static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(Package pkg) {
+ int targetSdkVersion = pkg.applicationInfo.targetSdkVersion;
+ return targetSdkVersion <= Build.VERSION_CODES.O_MR1;
+ }
+
+ private static boolean isLibraryPresent(ArrayList<String> usesLibraries,
+ ArrayList<String> usesOptionalLibraries, String apacheHttpLegacy) {
+ return ArrayUtils.contains(usesLibraries, apacheHttpLegacy)
+ || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy);
+ }
+}
diff --git a/android/content/pm/PackageCleanItem.java b/android/content/pm/PackageCleanItem.java
new file mode 100644
index 00000000..e1656d6f
--- /dev/null
+++ b/android/content/pm/PackageCleanItem.java
@@ -0,0 +1,85 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PackageCleanItem implements Parcelable {
+ public final int userId;
+ public final String packageName;
+ public final boolean andCode;
+
+ public PackageCleanItem(int userId, String packageName, boolean andCode) {
+ this.userId = userId;
+ this.packageName = packageName;
+ this.andCode = andCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ try {
+ if (obj != null) {
+ PackageCleanItem other = (PackageCleanItem)obj;
+ return userId == other.userId && packageName.equals(other.packageName)
+ && andCode == other.andCode;
+ }
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + userId;
+ result = 31 * result + packageName.hashCode();
+ result = 31 * result + (andCode ? 1 : 0);
+ return result;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(userId);
+ dest.writeString(packageName);
+ dest.writeInt(andCode ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<PackageCleanItem> CREATOR
+ = new Parcelable.Creator<PackageCleanItem>() {
+ public PackageCleanItem createFromParcel(Parcel source) {
+ return new PackageCleanItem(source);
+ }
+
+ public PackageCleanItem[] newArray(int size) {
+ return new PackageCleanItem[size];
+ }
+ };
+
+ private PackageCleanItem(Parcel source) {
+ userId = source.readInt();
+ packageName = source.readString();
+ andCode = source.readInt() != 0;
+ }
+}
diff --git a/android/content/pm/PackageInfo.java b/android/content/pm/PackageInfo.java
new file mode 100644
index 00000000..ba488f6a
--- /dev/null
+++ b/android/content/pm/PackageInfo.java
@@ -0,0 +1,417 @@
+/*
+ * 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;
+
+/**
+ * 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 &lt;manifest&gt; tag's "name"
+ * attribute.
+ */
+ public String packageName;
+
+ /**
+ * The names of any installed split APKs for this package.
+ */
+ public String[] splitNames;
+
+ /**
+ * The version number of this package, as specified by the &lt;manifest&gt;
+ * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
+ * attribute.
+ */
+ public int versionCode;
+
+ /**
+ * The version name of this package, as specified by the &lt;manifest&gt;
+ * 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
+ * &lt;manifest&gt; 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 &lt;manifest&gt; 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 &lt;manifest&gt;
+ * 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 &lt;manifest&gt;
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
+ * attribute.
+ */
+ public int sharedUserLabel;
+
+ /**
+ * Information collected from the &lt;application&gt; 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
+ * &lt;activity&gt;} tags included under &lt;application&gt;,
+ * 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
+ * &lt;receiver&gt;} tags included under &lt;application&gt;,
+ * 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
+ * &lt;service&gt;} tags included under &lt;application&gt;,
+ * 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
+ * &lt;provider&gt;} tags included under &lt;application&gt;,
+ * 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
+ * &lt;instrumentation&gt;} tags included under &lt;manifest&gt;,
+ * 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
+ * &lt;permission&gt;} tags included under &lt;manifest&gt;,
+ * 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
+ * &lt;uses-permission&gt;} tags included under &lt;manifest&gt;,
+ * 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
+ * &lt;uses-permission&gt;} tags included under &lt;manifest&gt;,
+ * 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 singed 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.
+ */
+ public Signature[] signatures;
+
+ /**
+ * Application specified preferred configuration
+ * {@link android.R.styleable#AndroidManifestUsesConfiguration
+ * &lt;uses-configuration&gt;} tags included under &lt;manifest&gt;,
+ * 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
+ */
+ 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 */
+ 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
+ */
+ public String overlayTarget;
+
+ /** @hide */
+ public int overlayPriority;
+
+ /** @hide */
+ public boolean isStaticOverlay;
+
+ public PackageInfo() {
+ }
+
+ @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) {
+ dest.writeString(packageName);
+ dest.writeStringArray(splitNames);
+ dest.writeInt(versionCode);
+ dest.writeString(versionName);
+ dest.writeInt(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
+ dest.writeString(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 | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ dest.writeTypedArray(receivers, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ dest.writeTypedArray(services, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ dest.writeTypedArray(providers, parcelableFlags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
+ dest.writeTypedArray(instrumentation, parcelableFlags);
+ dest.writeTypedArray(permissions, parcelableFlags);
+ dest.writeStringArray(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.writeString(restrictedAccountType);
+ dest.writeString(requiredAccountType);
+ dest.writeString(overlayTarget);
+ dest.writeInt(isStaticOverlay ? 1 : 0);
+ dest.writeInt(overlayPriority);
+ }
+
+ public static final 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];
+ }
+ };
+
+ private PackageInfo(Parcel source) {
+ packageName = source.readString();
+ splitNames = source.createStringArray();
+ versionCode = source.readInt();
+ versionName = source.readString();
+ baseRevisionCode = source.readInt();
+ splitRevisionCodes = source.createIntArray();
+ sharedUserId = source.readString();
+ 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.createStringArray();
+ 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.readString();
+ requiredAccountType = source.readString();
+ overlayTarget = source.readString();
+ isStaticOverlay = source.readInt() != 0;
+ overlayPriority = source.readInt();
+
+ // The component lists were flattened with the redundant ApplicationInfo
+ // instances omitted. Distribute the canonical one here as appropriate.
+ if (applicationInfo != null) {
+ propagateApplicationInfo(applicationInfo, activities);
+ propagateApplicationInfo(applicationInfo, receivers);
+ propagateApplicationInfo(applicationInfo, services);
+ propagateApplicationInfo(applicationInfo, providers);
+ }
+ }
+
+ private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) {
+ if (components != null) {
+ for (ComponentInfo ci : components) {
+ ci.applicationInfo = appInfo;
+ }
+ }
+ }
+}
diff --git a/android/content/pm/PackageInfoLite.java b/android/content/pm/PackageInfoLite.java
new file mode 100644
index 00000000..1efe082b
--- /dev/null
+++ b/android/content/pm/PackageInfoLite.java
@@ -0,0 +1,128 @@
+/*
+ * 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 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 &lt;manifest&gt; 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.
+ */
+ public int 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;
+
+ /**
+ * 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
+ * {@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(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
+ dest.writeInt(recommendedInstallLocation);
+ dest.writeInt(installLocation);
+ dest.writeInt(multiArch ? 1 : 0);
+
+ if (verifiers == null || verifiers.length == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(verifiers.length);
+ dest.writeTypedArray(verifiers, parcelableFlags);
+ }
+ }
+
+ public static final 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();
+ baseRevisionCode = source.readInt();
+ splitRevisionCodes = source.createIntArray();
+ recommendedInstallLocation = source.readInt();
+ installLocation = source.readInt();
+ multiArch = (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 00000000..f4fdcaa4
--- /dev/null
+++ b/android/content/pm/PackageInstaller.java
@@ -0,0 +1,1718 @@
+/*
+ * 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.Manifest;
+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.app.ActivityManager;
+import android.app.AppGlobals;
+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.FileBridge;
+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.ParcelableException;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.ExceptionUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 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.
+ * <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>
+ */
+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";
+
+ /** {@hide} */
+ public static final String
+ ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS";
+
+ /**
+ * 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";
+
+ /**
+ * 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;
+
+ 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 {
+ return new Session(mInstaller.openSession(sessionId));
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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 statusReceiver Where to deliver the result.
+ */
+ 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. 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 statusReceiver Where to deliver the result.
+ */
+ 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) {
+ Preconditions.checkNotNull(versionedPackage, "versionedPackage cannot be null");
+ try {
+ mInstaller.uninstall(versionedPackage, mInstallerPackageName,
+ flags, statusReceiver, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
+ public void setPermissionsResult(int sessionId, boolean accepted) {
+ try {
+ mInstaller.setPermissionsResult(sessionId, accepted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Events for observing session lifecycle.
+ * <p>
+ * A typical session lifecycle looks like this:
+ * <ul>
+ * <li>An installer creates a session to indicate pending app delivery. All
+ * install details are available at this point.
+ * <li>The installer opens the session to deliver APK data. Note that a
+ * session may be opened and closed multiple times as network connectivity
+ * changes. The installer may deliver periodic progress updates.
+ * <li>The installer commits or abandons the session, resulting in the
+ * session being finished.
+ * </ul>
+ */
+ public static abstract class SessionCallback {
+ /**
+ * New session has been created. Details about the session can be
+ * obtained from {@link PackageInstaller#getSessionInfo(int)}.
+ */
+ public abstract void onCreated(int sessionId);
+
+ /**
+ * Badging details for an existing session has changed. For example, the
+ * app icon or label has been updated.
+ */
+ public abstract void onBadgingChanged(int sessionId);
+
+ /**
+ * Active state for session has been changed.
+ * <p>
+ * A session is considered active whenever there is ongoing forward
+ * progress being made, such as the installer holding an open
+ * {@link Session} instance while streaming data into place, or the
+ * system optimizing code as the result of
+ * {@link Session#commit(IntentSender)}.
+ * <p>
+ * If the installer closes the {@link Session} without committing, the
+ * session is considered inactive until the installer opens the session
+ * again.
+ */
+ public abstract void onActiveChanged(int sessionId, boolean active);
+
+ /**
+ * Progress for given session has been updated.
+ * <p>
+ * Note that this progress may not directly correspond to the value
+ * reported by
+ * {@link PackageInstaller.Session#setStagingProgress(float)}, as the
+ * system may carve out a portion of the overall progress to represent
+ * its own internal installation work.
+ */
+ public abstract void onProgressChanged(int sessionId, float progress);
+
+ /**
+ * Session has completely finished, either with success or failure.
+ */
+ public abstract void onFinished(int sessionId, boolean success);
+ }
+
+ /** {@hide} */
+ private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements
+ Handler.Callback {
+ 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 Handler mHandler;
+
+ public SessionCallbackDelegate(SessionCallback callback, Looper looper) {
+ mCallback = callback;
+ mHandler = new Handler(looper, this);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ final int sessionId = msg.arg1;
+ switch (msg.what) {
+ case MSG_SESSION_CREATED:
+ mCallback.onCreated(sessionId);
+ return true;
+ case MSG_SESSION_BADGING_CHANGED:
+ mCallback.onBadgingChanged(sessionId);
+ return true;
+ case MSG_SESSION_ACTIVE_CHANGED:
+ final boolean active = msg.arg2 != 0;
+ mCallback.onActiveChanged(sessionId, active);
+ return true;
+ case MSG_SESSION_PROGRESS_CHANGED:
+ mCallback.onProgressChanged(sessionId, (float) msg.obj);
+ return true;
+ case MSG_SESSION_FINISHED:
+ mCallback.onFinished(sessionId, msg.arg2 != 0);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onSessionCreated(int sessionId) {
+ mHandler.obtainMessage(MSG_SESSION_CREATED, sessionId, 0).sendToTarget();
+ }
+
+ @Override
+ public void onSessionBadgingChanged(int sessionId) {
+ mHandler.obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, 0).sendToTarget();
+ }
+
+ @Override
+ public void onSessionActiveChanged(int sessionId, boolean active) {
+ mHandler.obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, active ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onSessionProgressChanged(int sessionId, float progress) {
+ mHandler.obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, 0, progress)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onSessionFinished(int sessionId, boolean success) {
+ mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0)
+ .sendToTarget();
+ }
+ }
+
+ /** {@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,
+ handler.getLooper());
+ 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.
+ */
+ public static class Session implements Closeable {
+ private 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} */
+ 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();
+ }
+
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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. If the device reboots
+ * before the session has been finalized, you may commit the session again.
+ *
+ * @throws SecurityException if streams opened through
+ * {@link #openWrite(String, long, long)} are still open.
+ */
+ 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 Callbacks called when the state of the session changes.
+ *
+ * @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 {
+ Preconditions.checkNotNull(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();
+ }
+ }
+ }
+
+ /**
+ * 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;
+
+ /** {@hide} */
+ public static final int UID_UNKNOWN = -1;
+
+ /** {@hide} */
+ public int mode = MODE_INVALID;
+ /** {@hide} */
+ public int installFlags;
+ /** {@hide} */
+ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ /** {@hide} */
+ public @InstallReason int installReason = PackageManager.INSTALL_REASON_UNKNOWN;
+ /** {@hide} */
+ public long sizeBytes = -1;
+ /** {@hide} */
+ public String appPackageName;
+ /** {@hide} */
+ public Bitmap appIcon;
+ /** {@hide} */
+ public String appLabel;
+ /** {@hide} */
+ public long appIconLastModified = -1;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public int originatingUid = UID_UNKNOWN;
+ /** {@hide} */
+ public Uri referrerUri;
+ /** {@hide} */
+ public String abiOverride;
+ /** {@hide} */
+ public String volumeUuid;
+ /** {@hide} */
+ public String[] grantedRuntimePermissions;
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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_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 package installation. This is informational
+ * and may be used as a signal for anti-malware purposes.
+ *
+ * @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
+ */
+ public void setOriginatingUid(int originatingUid) {
+ this.originatingUid = originatingUid;
+ }
+
+ /**
+ * Optionally set the URI that referred you to install this package. This is
+ * informational and may be used as a signal for anti-malware purposes.
+ *
+ * @see Intent#EXTRA_REFERRER
+ */
+ public void setReferrerUri(@Nullable Uri referrerUri) {
+ this.referrerUri = referrerUri;
+ }
+
+ /**
+ * Sets which runtime permissions to be granted to the package at installation.
+ *
+ * @param permissions The permissions to grant or null to grant all runtime
+ * permissions.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS)
+ public void setGrantedRuntimePermissions(String[] permissions) {
+ installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
+ this.grantedRuntimePermissions = permissions;
+ }
+
+ /** {@hide} */
+ public void setInstallFlagsInternal() {
+ installFlags |= PackageManager.INSTALL_INTERNAL;
+ installFlags &= ~PackageManager.INSTALL_EXTERNAL;
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public void setAllowDowngrade(boolean allowDowngrade) {
+ if (allowDowngrade) {
+ installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
+ }
+ }
+
+ /** {@hide} */
+ public void setInstallFlagsExternal() {
+ installFlags |= PackageManager.INSTALL_EXTERNAL;
+ installFlags &= ~PackageManager.INSTALL_INTERNAL;
+ }
+
+ /** {@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.
+ */
+ public void setInstallReason(@InstallReason int installReason) {
+ this.installReason = installReason;
+ }
+
+ /** {@hide} */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE)
+ public void setAllocateAggressive(boolean allocateAggressive) {
+ if (allocateAggressive) {
+ installFlags |= PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
+ }
+ }
+
+ /** {@hide} */
+ 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.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);
+ }
+
+ 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 {
+
+ /** {@hide} */
+ public int sessionId;
+ /** {@hide} */
+ public String installerPackageName;
+ /** {@hide} */
+ public String resolvedBaseCodePath;
+ /** {@hide} */
+ public float progress;
+ /** {@hide} */
+ public boolean sealed;
+ /** {@hide} */
+ public boolean active;
+
+ /** {@hide} */
+ public int mode;
+ /** {@hide} */
+ public @InstallReason int installReason;
+ /** {@hide} */
+ public long sizeBytes;
+ /** {@hide} */
+ public String appPackageName;
+ /** {@hide} */
+ public Bitmap appIcon;
+ /** {@hide} */
+ 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 int installFlags;
+
+ /** {@hide} */
+ public SessionInfo() {
+ }
+
+ /** {@hide} */
+ public SessionInfo(Parcel source) {
+ sessionId = 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();
+ installFlags = source.readInt();
+ }
+
+ /**
+ * Return the ID for this session.
+ */
+ public int getSessionId() {
+ return sessionId;
+ }
+
+ /**
+ * 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)}.
+ */
+ 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)}
+ */
+ 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#setAllowDowngrade(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllowDowngrade() {
+ return (installFlags & PackageManager.INSTALL_ALLOW_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;
+ }
+
+ /**
+ * 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();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(sessionId);
+ 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.writeInt(installFlags);
+ }
+
+ 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 00000000..11830c29
--- /dev/null
+++ b/android/content/pm/PackageItemInfo.java
@@ -0,0 +1,431 @@
+/*
+ * 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.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.Html;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.Printer;
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * 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 {
+ private static final float MAX_LABEL_SIZE_PX = 500f;
+ /**
+ * 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 be the one of 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 CharSequence loadLabel(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;
+ }
+
+ /**
+ * Same as {@link #loadLabel(PackageManager)} with the addition that
+ * the returned label is safe for being presented in the UI since it
+ * will not contain new lines and the length will be limited to a
+ * reasonable amount. This prevents a malicious party to influence UI
+ * layout via the app label misleading the user into performing a
+ * detrimental for them action. If the label is too long it will be
+ * truncated and ellipsized at the end.
+ *
+ * @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.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
+ // loadLabel() always returns non-null
+ String label = loadLabel(pm).toString();
+ // strip HTML tags to avoid <br> and other tags overwriting original message
+ String labelStr = Html.fromHtml(label).toString();
+
+ // If the label contains new line characters it may push the UI
+ // down to hide a part of it. Labels shouldn't have new line
+ // characters, so just truncate at the first time one is seen.
+ final int labelLength = labelStr.length();
+ int offset = 0;
+ while (offset < labelLength) {
+ final int codePoint = labelStr.codePointAt(offset);
+ final int type = Character.getType(codePoint);
+ if (type == Character.LINE_SEPARATOR
+ || type == Character.CONTROL
+ || type == Character.PARAGRAPH_SEPARATOR) {
+ labelStr = labelStr.substring(0, offset);
+ break;
+ }
+ // replace all non-break space to " " in order to be trimmed
+ if (type == Character.SPACE_SEPARATOR) {
+ labelStr = labelStr.substring(0, offset) + " " + labelStr.substring(offset +
+ Character.charCount(codePoint));
+ }
+ offset += Character.charCount(codePoint);
+ }
+
+ labelStr = labelStr.trim();
+ if (labelStr.isEmpty()) {
+ return packageName;
+ }
+ TextPaint paint = new TextPaint();
+ paint.setTextSize(42);
+
+ return TextUtils.ellipsize(labelStr, paint, MAX_LABEL_SIZE_PX,
+ TextUtils.TruncateAt.END);
+ }
+
+ /**
+ * 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.writeString(name);
+ dest.writeString(packageName);
+ dest.writeInt(labelRes);
+ TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
+ dest.writeInt(icon);
+ dest.writeInt(logo);
+ dest.writeBundle(metaData);
+ dest.writeInt(banner);
+ dest.writeInt(showUserIcon);
+ }
+
+ protected PackageItemInfo(Parcel source) {
+ name = source.readString();
+ packageName = source.readString();
+ 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 00000000..ef8f84bd
--- /dev/null
+++ b/android/content/pm/PackageManager.java
@@ -0,0 +1,5875 @@
+/*
+ * 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.PackageDeleteObserver;
+import android.app.PackageInstallObserver;
+import android.app.admin.DevicePolicyManager;
+import android.app.usage.StorageStatsManager;
+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.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+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.List;
+
+/**
+ * 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
+ 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_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,
+ GET_DISABLED_COMPONENTS,
+ GET_DISABLED_UNTIL_USED_COMPONENTS,
+ GET_UNINSTALLED_PACKAGES,
+ })
+ @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,
+ })
+ @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_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_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 = {
+ 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.
+ */
+ 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 DONT_DELETE_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
+ 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
+ */
+ 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;
+
+ /**
+ * Internal flag used to indicate that a system component has done their
+ * homework and verified that they correctly handle packages and components
+ * that come and go over time. In particular:
+ * <ul>
+ * <li>Apps installed on external storage, which will appear to be
+ * uninstalled while the the device is ejected.
+ * <li>Apps with encryption unaware components, which will appear to not
+ * exist while the device is locked.
+ * </ul>
+ *
+ * @see #MATCH_UNINSTALLED_PACKAGES
+ * @see #MATCH_DIRECT_BOOT_AWARE
+ * @see #MATCH_DIRECT_BOOT_UNAWARE
+ * @hide
+ */
+ public static final int MATCH_DEBUG_TRIAGED_MISSING = 0x10000000;
+
+ /**
+ * 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(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 */
+ @IntDef(flag = true, prefix = { "INSTALL_" }, value = {
+ INSTALL_FORWARD_LOCK,
+ INSTALL_REPLACE_EXISTING,
+ INSTALL_ALLOW_TEST,
+ INSTALL_EXTERNAL,
+ INSTALL_INTERNAL,
+ INSTALL_FROM_ADB,
+ INSTALL_ALL_USERS,
+ INSTALL_ALLOW_DOWNGRADE,
+ INSTALL_GRANT_RUNTIME_PERMISSIONS,
+ INSTALL_FORCE_VOLUME_UUID,
+ INSTALL_FORCE_PERMISSION_PROMPT,
+ INSTALL_INSTANT_APP,
+ INSTALL_DONT_KILL_APP,
+ INSTALL_FORCE_SDK,
+ INSTALL_FULL_APP,
+ INSTALL_ALLOCATE_AGGRESSIVE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallFlags {}
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * should be installed as forward locked, i.e. only the app itself should
+ * have access to its code and non-resource assets.
+ *
+ * @deprecated new installs into ASEC containers are no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final int INSTALL_FORWARD_LOCK = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that you want to
+ * replace an already installed package, if one exists.
+ *
+ * @hide
+ */
+ 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 an ASEC on a {@link VolumeInfo#TYPE_PUBLIC}.
+ *
+ * @deprecated new installs into ASEC containers are no longer supported;
+ * use adoptable storage instead.
+ * @hide
+ */
+ @Deprecated
+ public static final int INSTALL_EXTERNAL = 0x00000008;
+
+ /**
+ * 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 it is okay
+ * to install an update to an app where the newly installed app has a lower
+ * version code than the currently installed app. This is permitted only if
+ * the currently installed app is marked debuggable.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALLOW_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;
+
+ /** {@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 an
+ * upgrade to a package that refers to the SDK via release letter.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FORCE_SDK = 0x00002000;
+
+ /**
+ * 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;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "DONT_KILL_APP" }, value = {
+ DONT_KILL_APP
+ })
+ @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;
+
+ /** @hide */
+ @IntDef(prefix = { "INSTALL_REASON_" }, value = {
+ INSTALL_REASON_UNKNOWN,
+ INSTALL_REASON_POLICY,
+ INSTALL_REASON_DEVICE_RESTORE,
+ INSTALL_REASON_DEVICE_SETUP,
+ INSTALL_REASON_USER
+ })
+ @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;
+
+ /**
+ * Installation return code: this is passed to the
+ * {@link IPackageInstallObserver} on success.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_SUCCEEDED = 1;
+
+ /**
+ * Installation return code: this is passed to the
+ * {@link IPackageInstallObserver} if the package is already installed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
+
+ /**
+ * Installation return code: this is passed to the
+ * {@link IPackageInstallObserver} if the package archive file is invalid.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INVALID_APK = -2;
+
+ /**
+ * Installation return code: this is passed to the
+ * {@link IPackageInstallObserver} if the URI passed in is invalid.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_INVALID_URI = -3;
+
+ /**
+ * Installation return code: this is passed to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 parse return code: this is passed to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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: this is passed to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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 to the
+ * {@link IPackageInstallObserver} 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
+ */
+ 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;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "DELETE_" }, value = {
+ DELETE_KEEP_DATA,
+ DELETE_ALL_USERS,
+ DELETE_SYSTEM_APP,
+ DELETE_DONT_KILL_APP,
+ })
+ @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;
+
+ /**
+ * 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 since its forward locked.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_FORWARD_LOCKED = -4;
+
+ /**
+ * 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
+ 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
+ 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, such as speakers, audio jack or streaming over bluetooth
+ */
+ @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}: The device is capable of communicating with
+ * consumer IR devices.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+
+ /** {@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 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 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 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 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)}: The version of this feature indicates 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>
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+
+ /**
+ * 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 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 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 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.
+ * TODO(b/35851809): Make this public.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
+
+ /**
+ * 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 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";
+
+ /**
+ * @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 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} */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FILE_BASED_ENCRYPTION
+ = "android.software.file_based_encryption";
+
+ /**
+ * 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}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VR_MODE = "android.software.vr.mode";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device implements {@link #FEATURE_VR_MODE} but additionally meets extra CDD requirements
+ * to provide a high-quality VR experience. In general, devices declaring this feature will
+ * additionally:
+ * <ul>
+ * <li>Deliver consistent performance at a high framerate over an extended period of time
+ * for typical VR application CPU/GPU workloads with a minimal number of frame drops for VR
+ * applications that have called
+ * {@link android.view.Window#setSustainedPerformanceMode}.</li>
+ * <li>Implement {@link #FEATURE_HIFI_SENSORS} and have a low sensor latency.</li>
+ * <li>Include optimizations to lower display persistence while running VR applications.</li>
+ * <li>Implement an optimized render path to minimize latency to draw to the device's main
+ * display.</li>
+ * <li>Include the following EGL extensions: EGL_ANDROID_create_native_client_buffer,
+ * EGL_ANDROID_front_buffer_auto_refresh, EGL_EXT_protected_content,
+ * EGL_KHR_mutable_render_buffer, EGL_KHR_reusable_sync, and EGL_KHR_wait_sync.</li>
+ * <li>Provide at least one CPU core that is reserved for use solely by the top, foreground
+ * VR application process for critical render threads while such an application is
+ * running.</li>
+ * </ul>
+ */
+ @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";
+
+ /**
+ * Action to external storage service to clean out removed apps.
+ * @hide
+ */
+ public static final String ACTION_CLEAN_EXTERNAL_STORAGE
+ = "android.content.pm.CLEAN_EXTERNAL_STORAGE";
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_VERSION_CODE
+ = "android.content.pm.extra.VERIFICATION_VERSION_CODE";
+
+ /**
+ * 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
+ public static final int FLAG_PERMISSION_USER_SET = 1 << 0;
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * by the user and it is fixed, i.e. apps can no longer request
+ * this permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_USER_FIXED = 1 << 1;
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * by device policy and neither apps nor the user can change
+ * its state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_POLICY_FIXED = 1 << 2;
+
+ /**
+ * Permission flag: The permission is set in a granted state but
+ * access to resources it guards is restricted by other means to
+ * enable revoking a permission on legacy apps that do not support
+ * runtime permissions. If this permission is upgraded to runtime
+ * because the app was updated to support runtime permissions, the
+ * the permission will be revoked in the upgrade process.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 1 << 3;
+
+ /**
+ * Permission flag: The permission is set in its current state
+ * because the app is a component that is a part of the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4;
+
+ /**
+ * Permission flag: The permission is granted by default because it
+ * enables app functionality that is expected to work out-of-the-box
+ * for providing a smooth user experience. For example, the phone app
+ * is expected to have the phone permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5;
+
+ /**
+ * Permission flag: The permission has to be reviewed before any of
+ * the app components can run.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 1 << 6;
+
+ /**
+ * Mask for all permission flags.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MASK_PERMISSION_FLAGS = 0xFF;
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ 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
+ */
+ 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;
+
+ /**
+ * 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 DONT_DELETE_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract PackageInfo getPackageInfo(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 DONT_DELETE_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract PackageInfo getPackageInfo(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 DONT_DELETE_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)
+ public abstract PackageInfo getPackageInfoAsUser(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 names 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(String[] names);
+
+ /**
+ * Map from a packages canonical name to the current name in use on the device.
+ * @param names 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(String[] names);
+
+ /**
+ * 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 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(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(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
+ */
+ public abstract int getPackageUidAsUser(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
+ */
+ public abstract int getPackageUidAsUser(String packageName, @PackageInfoFlags int flags,
+ @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular permission.
+ *
+ * @param name 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(String name, @PermissionInfoFlags int flags)
+ throws NameNotFoundException;
+
+ /**
+ * Query for all of the permissions associated with a particular group.
+ *
+ * @param group 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.
+ */
+ public abstract List<PermissionInfo> queryPermissionsByGroup(String group,
+ @PermissionInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Returns true if Permission Review Mode is enabled, false otherwise.
+ *
+ * @hide
+ */
+ @TestApi
+ public abstract boolean isPermissionReviewModeEnabled();
+
+ /**
+ * Retrieve all of the information we know about a particular group of
+ * permissions.
+ *
+ * @param name 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.
+ */
+ public abstract PermissionGroupInfo getPermissionGroupInfo(String name,
+ @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.
+ */
+ 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 DONT_DELETE_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ */
+ public abstract ApplicationInfo getApplicationInfo(String packageName,
+ @ApplicationInfoFlags int flags) throws NameNotFoundException;
+
+ /** {@hide} */
+ public abstract ApplicationInfo getApplicationInfoAsUser(String packageName,
+ @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * 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.
+ */
+ public abstract ActivityInfo getActivityInfo(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.
+ */
+ public abstract ActivityInfo getReceiverInfo(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.
+ */
+ public abstract ServiceInfo getServiceInfo(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.
+ */
+ public abstract ProviderInfo getProviderInfo(ComponentName component,
+ @ComponentInfoFlags int flags) throws NameNotFoundException;
+
+ /**
+ * Return a List of all packages that are installed on the device.
+ *
+ * @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 DONT_DELETE_DATA} flag set).
+ */
+ 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 DONT_DELETE_DATA} flag set).
+ */
+ public abstract List<PackageInfo> getPackagesHoldingPermissions(
+ 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 DONT_DELETE_DATA} flag set).
+ * @hide
+ */
+ @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 pkgName 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
+ public abstract @PermissionResult int checkPermission(String permName, String pkgName);
+
+ /**
+ * 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 pkgName 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 pkgName);
+
+ /**
+ * Gets the package name of the component controlling runtime permissions.
+ *
+ * @return The package name.
+ *
+ * @hide
+ */
+ @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
+ * &lt;permission-tree&gt;} 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(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(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 name 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(String name);
+
+ /**
+ * 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
+ })
+ @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 permissionName The permission name to grant.
+ * @param user The user for which to grant the permission.
+ *
+ * @see #revokeRuntimePermission(String, String, android.os.UserHandle)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ public abstract void grantRuntimePermission(@NonNull String packageName,
+ @NonNull String permissionName, @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 permissionName The permission name to revoke.
+ * @param user The user for which to revoke the permission.
+ *
+ * @see #grantRuntimePermission(String, String, android.os.UserHandle)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+ public abstract void revokeRuntimePermission(@NonNull String packageName,
+ @NonNull String permissionName, @NonNull UserHandle user);
+
+ /**
+ * Gets the state flags associated with a permission.
+ *
+ * @param permissionName 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
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ })
+ public abstract @PermissionFlags int getPermissionFlags(String permissionName,
+ 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 permissionName 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
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ })
+ public abstract void updatePermissionFlags(String permissionName,
+ String packageName, @PermissionFlags int flagMask, @PermissionFlags int flagValues,
+ @NonNull UserHandle user);
+
+ /**
+ * 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 permission A permission your app wants to request.
+ * @return Whether you can show permission rationale UI.
+ *
+ * @hide
+ */
+ public abstract boolean shouldShowRequestPermissionRationale(String permission);
+
+ /**
+ * 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
+ */
+ 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 pkg1 First package name whose signature will be compared.
+ * @param pkg2 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
+ public abstract @SignatureResult int checkSignatures(String pkg1, String pkg2);
+
+ /**
+ * 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
+ */
+ 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
+ */
+ public abstract int getUidForSharedUser(String sharedUserName)
+ throws NameNotFoundException;
+
+ /**
+ * Return a List of all application packages that are installed on the
+ * device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
+ * applications including those deleted with {@code DONT_DELETE_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 DONT_DELETE_DATA} flag set).
+ */
+ 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 DONT_DELETE_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 DONT_DELETE_DATA} flag set).
+ * @hide
+ */
+ 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(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.
+ *
+ */
+ 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 name of the package hosting the services shared library.
+ *
+ * @return The library host package.
+ *
+ * @hide
+ */
+ public abstract @NonNull String getServicesSystemSharedLibraryPackageName();
+
+ /**
+ * Get the name of the package hosting the shared components shared library.
+ *
+ * @return The library host package.
+ *
+ * @hide
+ */
+ 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 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(!!).
+ */
+ 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(String name);
+
+ /**
+ * 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(String name, 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.
+ */
+ public abstract ResolveInfo resolveActivity(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
+ */
+ public abstract ResolveInfo resolveActivityAsUser(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.
+ */
+ public abstract List<ResolveInfo> queryIntentActivities(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
+ */
+ public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+ /**
+ * 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.
+ */
+ public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
+ @Nullable Intent[] specifics, 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.
+ */
+ public abstract List<ResolveInfo> queryBroadcastReceivers(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
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent,
+ @ResolveInfoFlags int flags, UserHandle userHandle) {
+ return queryBroadcastReceiversAsUser(intent, flags, userHandle.getIdentifier());
+ }
+
+ /**
+ * @hide
+ */
+ public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent,
+ @ResolveInfoFlags int flags, @UserIdInt int userId);
+
+
+ /** {@hide} */
+ @Deprecated
+ public List<ResolveInfo> queryBroadcastReceivers(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.
+ */
+ public abstract ResolveInfo resolveService(Intent intent, @ResolveInfoFlags int flags);
+
+ /**
+ * 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.
+ */
+ public abstract List<ResolveInfo> queryIntentServices(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
+ */
+ public abstract List<ResolveInfo> queryIntentServicesAsUser(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 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
+ */
+ public abstract List<ResolveInfo> queryIntentContentProvidersAsUser(
+ 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.
+ * @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.
+ */
+ public abstract List<ResolveInfo> queryIntentContentProviders(Intent intent,
+ @ResolveInfoFlags int flags);
+
+ /**
+ * Find a single content provider by its base path name.
+ *
+ * @param name The name 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.
+ */
+ public abstract ProviderInfo resolveContentProvider(String name,
+ @ComponentInfoFlags int flags);
+
+ /**
+ * Find a single content provider by its base path name.
+ *
+ * @param name 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
+ */
+ public abstract ProviderInfo resolveContentProviderAsUser(String name,
+ @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>
+ */
+ public abstract List<ProviderInfo> queryContentProviders(
+ 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
+ */
+ public List<ProviderInfo> queryContentProviders(
+ 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.
+ */
+ public abstract InstrumentationInfo getInstrumentationInfo(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.
+ */
+ public abstract List<InstrumentationInfo> queryInstrumentation(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.
+ */
+ public abstract Drawable getDrawable(String packageName, @DrawableRes int resid,
+ 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)
+ */
+ public abstract Drawable getActivityIcon(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)
+ */
+ public abstract Drawable getActivityIcon(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)
+ */
+ public abstract Drawable getActivityBanner(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)
+ */
+ public abstract Drawable getActivityBanner(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.
+ */
+ 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)
+ */
+ public abstract Drawable getApplicationIcon(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)
+ */
+ public abstract Drawable getApplicationIcon(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)
+ */
+ public abstract Drawable getApplicationBanner(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)
+ */
+ public abstract Drawable getApplicationBanner(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)
+ */
+ public abstract Drawable getActivityLogo(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)
+ */
+ public abstract Drawable getActivityLogo(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)
+ */
+ public abstract Drawable getApplicationLogo(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)
+ */
+ public abstract Drawable getApplicationLogo(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 icon The icon to badge.
+ * @param user The target user.
+ * @return A drawable that combines the original icon and a badge as
+ * determined by the system.
+ */
+ public abstract Drawable getUserBadgedIcon(Drawable icon, 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.
+ */
+ public abstract Drawable getUserBadgedDrawableForDensity(Drawable drawable,
+ UserHandle user, 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
+ */
+ public abstract Drawable getUserBadgeForDensity(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
+ */
+ public abstract Drawable getUserBadgeForDensityNoBackground(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.
+ */
+ public abstract CharSequence getUserBadgedLabel(CharSequence label, 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.
+ */
+ public abstract CharSequence getText(String packageName, @StringRes int resid,
+ 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.
+ */
+ public abstract XmlResourceParser getXml(String packageName, @XmlRes int resid,
+ ApplicationInfo appInfo);
+
+ /**
+ * Return the label to use for this application.
+ *
+ * @return Returns the label associated with this application, or null if
+ * it could not be found for any reason.
+ * @param info The application to get the label of.
+ */
+ public abstract CharSequence getApplicationLabel(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)
+ */
+ public abstract Resources getResourcesForActivity(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).
+ */
+ public abstract Resources getResourcesForApplication(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 appPackageName 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)
+ */
+ public abstract Resources getResourcesForApplication(String appPackageName)
+ throws NameNotFoundException;
+
+ /** @hide */
+ public abstract Resources getResourcesForApplicationAsUser(String appPackageName,
+ @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.
+ */
+ public PackageInfo getPackageArchiveInfo(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, 0);
+ }
+ PackageUserState state = new PackageUserState();
+ return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
+ } catch (PackageParserException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated replaced by {@link PackageInstaller}
+ * @hide
+ */
+ @Deprecated
+ public abstract void installPackage(
+ Uri packageURI,
+ IPackageInstallObserver observer,
+ @InstallFlags int flags,
+ String installerPackageName);
+ /**
+ * @deprecated replaced by {@link PackageInstaller}
+ * @hide
+ */
+ @Deprecated
+ public abstract void installPackage(
+ Uri packageURI,
+ PackageInstallObserver observer,
+ @InstallFlags int flags,
+ String installerPackageName);
+
+ /**
+ * 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
+ */
+ @SystemApi
+ public abstract int installExistingPackage(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
+ */
+ @SystemApi
+ public abstract int installExistingPackage(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
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ public abstract int installExistingPackageAsUser(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,
+ 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(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(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
+ */
+ @SystemApi
+ public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+ 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
+ */
+ @SystemApi
+ public abstract List<IntentFilter> getAllIntentFilters(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
+ */
+ @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(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(String targetPackage,
+ String installerPackageName);
+
+ /** @hide */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public abstract void setUpdateAvailable(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)
+ public abstract void deletePackage(String packageName, 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})
+ public abstract void deletePackageAsUser(@NonNull String packageName,
+ 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
+ */
+ public abstract String getInstallerPackageName(String packageName);
+
+ /**
+ * 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
+ */
+ public abstract void clearApplicationUserData(String packageName,
+ 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
+ */
+ public abstract void deleteApplicationCacheFiles(String packageName,
+ 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
+ */
+ public abstract void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+ 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
+ */
+ public void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer) {
+ freeStorageAndNotify(null, freeStorageSize, observer);
+ }
+
+ /** {@hide} */
+ public abstract void freeStorageAndNotify(String volumeUuid, long freeStorageSize,
+ 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
+ */
+ public void freeStorage(long freeStorageSize, IntentSender pi) {
+ freeStorage(null, freeStorageSize, pi);
+ }
+
+ /** {@hide} */
+ public abstract void freeStorage(String volumeUuid, long freeStorageSize, 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
+ public abstract void getPackageSizeInfoAsUser(String packageName, @UserIdInt int userId,
+ IPackageStatsObserver observer);
+
+ /**
+ * Like {@link #getPackageSizeInfoAsUser(String, int, IPackageStatsObserver)}, but
+ * returns the size for the calling user.
+ *
+ * @deprecated use {@link StorageStatsManager} instead.
+ * @hide
+ */
+ @Deprecated
+ public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) {
+ getPackageSizeInfoAsUser(packageName, UserHandle.myUserId(), observer);
+ }
+
+ /**
+ * @deprecated This function no longer does anything; it was an old
+ * approach to managing preferred activities, which has been superseded
+ * by (and conflicts with) the modern activity-based preferences.
+ */
+ @Deprecated
+ public abstract void addPackageToPreferred(String packageName);
+
+ /**
+ * @deprecated This function no longer does anything; it was an old
+ * approach to managing preferred activities, which has been superseded
+ * by (and conflicts with) the modern activity-based preferences.
+ */
+ @Deprecated
+ public abstract void removePackageFromPreferred(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.
+ */
+ public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags);
+
+ /**
+ * @deprecated This is a protected API that should not have been available
+ * to third party applications. It is the platform's responsibility for
+ * assigning preferred activities and this cannot be directly modified.
+ *
+ * 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
+ public abstract void addPreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity);
+
+ /**
+ * Same as {@link #addPreferredActivity(IntentFilter, int,
+ ComponentName[], ComponentName)}, but with a specific userId to apply the preference
+ to.
+ * @hide
+ */
+ public void addPreferredActivityAsUser(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity, @UserIdInt int userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * @deprecated This is a protected API that should not have been available
+ * to third party applications. It is the platform's responsibility for
+ * assigning preferred activities and this cannot be directly modified.
+ *
+ * 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
+ public abstract void replacePreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity);
+
+ /**
+ * @hide
+ */
+ @Deprecated
+ public void replacePreferredActivityAsUser(IntentFilter filter, int match,
+ ComponentName[] set, 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.
+ */
+ public abstract void clearPackagePreferredActivities(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.
+ */
+ public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
+ @NonNull List<ComponentName> outActivities, String packageName);
+
+ /**
+ * Ask for the set of available 'home' activities and the current explicit
+ * default, if any.
+ * @hide
+ */
+ public abstract ComponentName getHomeActivities(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.
+ */
+ public abstract void setComponentEnabledSetting(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(
+ ComponentName componentName);
+
+ /**
+ * 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.
+ */
+ public abstract void setApplicationEnabledSetting(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(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
+ */
+ public abstract void flushPackageRestrictionsAsUser(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
+ */
+ public abstract boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
+ UserHandle userHandle);
+
+ /**
+ * Returns the hidden state of a package.
+ * @see #setApplicationHiddenSettingAsUser(String, boolean, UserHandle)
+ * @hide
+ */
+ public abstract boolean getApplicationHiddenSettingAsUser(String packageName,
+ UserHandle userHandle);
+
+ /**
+ * 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
+ @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
+ public abstract void addOnPermissionsChangeListener(OnPermissionsChangedListener listener);
+
+ /**
+ * Remvoes a listener for permission changes for installed packages.
+ *
+ * @param listener The listener to remove.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
+ public abstract void removeOnPermissionsChangeListener(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
+ */
+ public abstract KeySet getKeySetByAlias(String packageName, String alias);
+
+ /** Return the signing {@link KeySet} for this application.
+ * @hide
+ */
+ public abstract KeySet getSigningKeySet(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
+ */
+ public abstract boolean isSignedBy(String packageName, 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
+ */
+ public abstract boolean isSignedByExactly(String packageName, KeySet ks);
+
+ /**
+ * Puts the package in a suspended state, where attempts at starting activities are denied.
+ *
+ * <p>It doesn't remove the data or the actual package file. The application notifications
+ * will be hidden, the application will not show up in recents, will not be able to show
+ * toasts or dialogs or ring the device.
+ *
+ * <p>The package must already be installed. If the package is uninstalled while suspended
+ * the package will no longer be suspended.
+ *
+ * @param packageNames The names of the packages to set the suspended status.
+ * @param suspended If set to {@code true} than the packages will be suspended, if set to
+ * {@code false} the packages will be unsuspended.
+ * @param userId The user id.
+ *
+ * @return an array of package names for which the suspended status is not set as requested in
+ * this method.
+ *
+ * @hide
+ */
+ public abstract String[] setPackagesSuspendedAsUser(
+ String[] packageNames, boolean suspended, @UserIdInt int userId);
+
+ /**
+ * @see #setPackageSuspendedAsUser(String, boolean, int)
+ * @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 or could not be found.
+ * @hide
+ */
+ public abstract boolean isPackageSuspendedForUser(String packageName, int userId);
+
+ /**
+ * 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} */
+ public abstract int getMoveStatus(int moveId);
+
+ /** {@hide} */
+ public abstract void registerMoveCallback(MoveCallback callback, Handler handler);
+ /** {@hide} */
+ public abstract void unregisterMoveCallback(MoveCallback callback);
+
+ /** {@hide} */
+ public abstract int movePackage(String packageName, VolumeInfo vol);
+ /** {@hide} */
+ public abstract @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app);
+ /** {@hide} */
+ public abstract @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app);
+
+ /** {@hide} */
+ public abstract int movePrimaryStorage(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
+ */
+ public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
+
+ /**
+ * Returns true if the device is upgrading, such as first boot after OTA.
+ *
+ * @hide
+ */
+ public abstract boolean isUpgrade();
+
+ /**
+ * 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
+ */
+ public abstract void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId,
+ 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
+ */
+ public abstract void clearCrossProfileIntentFilters(int sourceUserId);
+
+ /**
+ * @hide
+ */
+ public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+
+ /**
+ * @hide
+ */
+ public abstract Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+
+ /** {@hide} */
+ public abstract boolean isPackageAvailable(String packageName);
+
+ /** {@hide} */
+ public static String installStatusToString(int status, String msg) {
+ final String str = installStatusToString(status);
+ if (msg != null) {
+ return str + ": " + msg;
+ } else {
+ return str;
+ }
+ }
+
+ /** {@hide} */
+ 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";
+ 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_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;
+ default: return PackageInstaller.STATUS_FAILURE;
+ }
+ }
+
+ /** {@hide} */
+ public static String deleteStatusToString(int status, String msg) {
+ final String str = deleteStatusToString(status);
+ if (msg != null) {
+ return str + ": " + msg;
+ } else {
+ return str;
+ }
+ }
+
+ /** {@hide} */
+ 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} */
+ 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_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE";
+ case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
+ case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
+ default: return Integer.toString(flag);
+ }
+ }
+
+ /** {@hide} */
+ public static class LegacyPackageInstallObserver extends PackageInstallObserver {
+ private final IPackageInstallObserver mLegacy;
+
+ public LegacyPackageInstallObserver(IPackageInstallObserver legacy) {
+ mLegacy = legacy;
+ }
+
+ @Override
+ public void onPackageInstalled(String basePackageName, int returnCode, String msg,
+ Bundle extras) {
+ if (mLegacy == null) return;
+ try {
+ mLegacy.packageInstalled(basePackageName, returnCode);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ /** {@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
+ public abstract @InstallReason int getInstallReason(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
+ */
+ @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
+ */
+ @SystemApi
+ public abstract ComponentName getInstantAppInstallerComponent();
+
+ /**
+ * Return the Android Id for a given Instant App.
+ *
+ * @see {@link android.provider.Settings.Secure#ANDROID_ID}
+ * @hide
+ */
+ public abstract String getInstantAppAndroidId(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(String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback);
+}
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
new file mode 100644
index 00000000..4c981cdb
--- /dev/null
+++ b/android/content/pm/PackageManagerInternal.java
@@ -0,0 +1,386 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Intent;
+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.os.Bundle;
+import android.util.SparseArray;
+
+import java.util.List;
+
+/**
+ * Package manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class PackageManagerInternal {
+
+ /**
+ * Provider for package names.
+ */
+ public interface PackagesProvider {
+
+ /**
+ * Gets the packages for a given user.
+ * @param userId The user id.
+ * @return The package names.
+ */
+ public String[] getPackages(int userId);
+ }
+
+ /**
+ * Provider for package names.
+ */
+ public interface SyncAdapterPackagesProvider {
+
+ /**
+ * Gets the sync adapter packages for given authority and user.
+ * @param authority The authority.
+ * @param userId The user id.
+ * @return The package names.
+ */
+ public String[] getPackages(String authority, int userId);
+ }
+
+ /**
+ * Sets the location provider packages provider.
+ * @param provider The packages provider.
+ */
+ public abstract void setLocationPackagesProvider(PackagesProvider provider);
+
+ /**
+ * Sets the voice interaction packages provider.
+ * @param provider The packages provider.
+ */
+ public abstract void setVoiceInteractionPackagesProvider(PackagesProvider provider);
+
+ /**
+ * Sets the SMS packages provider.
+ * @param provider The packages provider.
+ */
+ public abstract void setSmsAppPackagesProvider(PackagesProvider provider);
+
+ /**
+ * Sets the dialer packages provider.
+ * @param provider The packages provider.
+ */
+ public abstract void setDialerAppPackagesProvider(PackagesProvider provider);
+
+ /**
+ * Sets the sim call manager packages provider.
+ * @param provider The packages provider.
+ */
+ public abstract void setSimCallManagerPackagesProvider(PackagesProvider provider);
+
+ /**
+ * Sets the sync adapter packages provider.
+ * @param provider The provider.
+ */
+ public abstract void setSyncAdapterPackagesprovider(SyncAdapterPackagesProvider provider);
+
+ /**
+ * Requests granting of the default permissions to the current default SMS app.
+ * @param packageName The default SMS package name.
+ * @param userId The user for which to grant the permissions.
+ */
+ public abstract void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId);
+
+ /**
+ * Requests granting of the default permissions to the current default dialer app.
+ * @param packageName The default dialer package name.
+ * @param userId The user for which to grant the permissions.
+ */
+ public abstract void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId);
+
+ /**
+ * Requests granting of the default permissions to the current default sim call manager.
+ * @param packageName The default sim call manager package name.
+ * @param userId The user for which to grant the permissions.
+ */
+ public abstract void grantDefaultPermissionsToDefaultSimCallManager(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 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 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,
+ @ResolveInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
+ */
+ public abstract ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
+ int userId);
+
+ /**
+ * 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);
+
+ /**
+ * 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 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);
+
+ /**
+ * Grants a runtime permission
+ * @param packageName The package name.
+ * @param name The name of the permission.
+ * @param userId The userId for which to grant the permission.
+ * @param overridePolicy If true, grant this permission even if it is fixed by policy.
+ */
+ public abstract void grantRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy);
+
+ /**
+ * Revokes a runtime permission
+ * @param packageName The package name.
+ * @param name The name of the permission.
+ * @param userId The userId for which to revoke the permission.
+ * @param overridePolicy If true, revoke this permission even if it is fixed by policy.
+ */
+ public abstract void revokeRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy);
+
+ /**
+ * 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 callingPackage The name of the package requesting the ephemeral application
+ * @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 callingPackage,
+ Bundle verificationBundle, int userId);
+
+ /**
+ * Grants access to the package metadata for an ephemeral application.
+ * <p>
+ * When an ephemeral application explicitly tries to interact with a full
+ * install application [via an activity, service or provider that has been
+ * exposed using the {@code visibleToInstantApp} attribute], the normal
+ * application must be able to see metadata about the connecting ephemeral
+ * app. If the ephemeral application uses an implicit intent [ie action VIEW,
+ * category BROWSABLE], it remains hidden from the launched activity.
+ * <p>
+ * If the {@code sourceUid} is not for an ephemeral app or {@code targetUid}
+ * is not for a fully installed app, this method will be a no-op.
+ *
+ * @param userId the user
+ * @param intent the intent that triggered the grant
+ * @param targetAppId The app ID of the fully installed application
+ * @param ephemeralAppId The app ID of the ephemeral application
+ */
+ public abstract void grantEphemeralAccess(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId);
+
+ 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();
+
+ /**
+ * @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.
+ * @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);
+
+ /**
+ * Resolves an activity intent, allowing instant apps to be resolved.
+ */
+ public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
+ int flags, int userId);
+
+ /**
+ * Resolves a service intent, allowing instant apps to be resolved.
+ */
+ public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
+ int flags, int userId, int callingUid);
+
+ /**
+ * Track the creator of a new isolated uid.
+ * @param isolatedUid The newly created isolated uid.
+ * @param ownerUid The uid of the app that created the isolated process.
+ */
+ public abstract void addIsolatedUid(int isolatedUid, int ownerUid);
+
+ /**
+ * Track removal of an isolated uid.
+ * @param isolatedUid isolated uid that is no longer being used.
+ */
+ public abstract void removeIsolatedUid(int isolatedUid);
+
+ /**
+ * Return the taget SDK version for the app with the given UID.
+ */
+ public abstract int getUidTargetSdkVersion(int uid);
+
+ /** Whether the binder caller can access instant apps. */
+ public abstract boolean canAccessInstantApps(int callingUid, 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);
+}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
new file mode 100644
index 00000000..8b54b0d8
--- /dev/null
+++ b/android/content/pm/PackageParser.java
@@ -0,0 +1,7606 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.PackageManager.INSTALL_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_CERTIFICATE_ENCODING;
+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_NO_CERTIFICATES;
+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.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageParserCacheHelper.ReadHelper;
+import android.content.pm.PackageParserCacheHelper.WriteHelper;
+import android.content.pm.split.DefaultSplitAssetLoader;
+import android.content.pm.split.SplitAssetDependencyLoader;
+import android.content.pm.split.SplitAssetLoader;
+import android.content.res.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.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructStat;
+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.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.jar.StrictJarFile;
+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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+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;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.ZipEntry;
+
+/**
+ * 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 {
+ private static final boolean DEBUG_JAR = false;
+ private static final boolean DEBUG_PARSER = false;
+ private static final boolean DEBUG_BACKUP = false;
+ private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
+ private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
+
+ private static final String PROPERTY_CHILD_PACKAGES_ENABLED =
+ "persist.sys.child_packages_enabled";
+
+ private static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE &&
+ SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false);
+
+ public static final int APK_SIGNING_UNKNOWN = 0;
+ public static final int APK_SIGNING_V1 = 1;
+ public static final int APK_SIGNING_V2 = 2;
+
+ private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
+ // TODO: switch outError users to PackageParserException
+ // TODO: refactor "codePath" to "apkPath"
+
+ /** File name in an APK for the Android manifest. */
+ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+
+ /** Path prefix for apps on expanded storage */
+ private static final String MNT_EXPAND = "/mnt/expand/";
+
+ private static final String TAG_MANIFEST = "manifest";
+ private static final String TAG_APPLICATION = "application";
+ private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
+ private static final String TAG_OVERLAY = "overlay";
+ private static final String TAG_KEY_SETS = "key-sets";
+ private static final String TAG_PERMISSION_GROUP = "permission-group";
+ private static final String TAG_PERMISSION = "permission";
+ private static final String TAG_PERMISSION_TREE = "permission-tree";
+ private static final String TAG_USES_PERMISSION = "uses-permission";
+ private static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";
+ private static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23";
+ private static final String TAG_USES_CONFIGURATION = "uses-configuration";
+ private static final String TAG_USES_FEATURE = "uses-feature";
+ private static final String TAG_FEATURE_GROUP = "feature-group";
+ private static final String TAG_USES_SDK = "uses-sdk";
+ private static final String TAG_SUPPORT_SCREENS = "supports-screens";
+ private static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";
+ private static final String TAG_INSTRUMENTATION = "instrumentation";
+ private static final String TAG_ORIGINAL_PACKAGE = "original-package";
+ private static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";
+ private static final String TAG_USES_GL_TEXTURE = "uses-gl-texture";
+ private static final String TAG_COMPATIBLE_SCREENS = "compatible-screens";
+ private static final String TAG_SUPPORTS_INPUT = "supports-input";
+ private static final String TAG_EAT_COMMENT = "eat-comment";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_RESTRICT_UPDATE = "restrict-update";
+ private static final String TAG_USES_SPLIT = "uses-split";
+
+ // [b/36551762] STOPSHIP remove the ability to expose components via meta-data
+ // Temporary workaround; allow meta-data to expose components to instant apps
+ private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed";
+
+ private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
+
+ /**
+ * 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
+ private static final Set<String> CHILD_PACKAGE_TAGS = new ArraySet<>();
+ static {
+ CHILD_PACKAGE_TAGS.add(TAG_APPLICATION);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_M);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_23);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_CONFIGURATION);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_FEATURE);
+ CHILD_PACKAGE_TAGS.add(TAG_FEATURE_GROUP);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_SDK);
+ CHILD_PACKAGE_TAGS.add(TAG_SUPPORT_SCREENS);
+ CHILD_PACKAGE_TAGS.add(TAG_INSTRUMENTATION);
+ CHILD_PACKAGE_TAGS.add(TAG_USES_GL_TEXTURE);
+ CHILD_PACKAGE_TAGS.add(TAG_COMPATIBLE_SCREENS);
+ CHILD_PACKAGE_TAGS.add(TAG_SUPPORTS_INPUT);
+ CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT);
+ }
+
+ private 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
+ private static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
+ static {
+ SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
+ }
+
+ /** @hide */
+ public static class NewPermissionInfo {
+ public final String name;
+ 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;
+ }
+ }
+
+ /** @hide */
+ public static class SplitPermissionInfo {
+ public final String rootPerm;
+ public final String[] newPerms;
+ public final int targetSdk;
+
+ public SplitPermissionInfo(String rootPerm, String[] newPerms, int targetSdk) {
+ this.rootPerm = rootPerm;
+ this.newPerms = newPerms;
+ this.targetSdk = targetSdk;
+ }
+ }
+
+ /**
+ * 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
+ */
+ 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)
+ };
+
+ /**
+ * List of permissions that have been split into more granular or dependent
+ * permissions.
+ * @hide
+ */
+ public static final PackageParser.SplitPermissionInfo SPLIT_PERMISSIONS[] =
+ new PackageParser.SplitPermissionInfo[] {
+ // READ_EXTERNAL_STORAGE is always required when an app requests
+ // WRITE_EXTERNAL_STORAGE, because we can't have an app that has
+ // write access without read access. The hack here with the target
+ // target SDK version ensures that this grant is always done.
+ new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE },
+ android.os.Build.VERSION_CODES.CUR_DEVELOPMENT+1),
+ new PackageParser.SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS,
+ new String[] { android.Manifest.permission.READ_CALL_LOG },
+ android.os.Build.VERSION_CODES.JELLY_BEAN),
+ new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_CONTACTS,
+ new String[] { android.Manifest.permission.WRITE_CALL_LOG },
+ android.os.Build.VERSION_CODES.JELLY_BEAN)
+ };
+
+ /**
+ * @deprecated callers should move to explicitly passing around source path.
+ */
+ @Deprecated
+ private String mArchiveSourcePath;
+
+ private String[] mSeparateProcesses;
+ private boolean mOnlyCoreApps;
+ private DisplayMetrics mMetrics;
+ private Callback mCallback;
+ private File mCacheDir;
+
+ private static final int SDK_VERSION = Build.VERSION.SDK_INT;
+ private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+
+ private int mParseError = PackageManager.INSTALL_SUCCEEDED;
+
+ private static boolean sCompatibilityModeEnabled = true;
+ private static final int PARSE_DEFAULT_INSTALL_LOCATION =
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ private 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 {
+ public final String packageName;
+ public final int versionCode;
+ 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.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 revisionCode;
+ public final int installLocation;
+ public final VerifierInfo[] verifiers;
+ public final Signature[] signatures;
+ public final Certificate[][] certificates;
+ 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 ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
+ String configForSplit, String usesSplitName, int versionCode, int revisionCode,
+ int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
+ Certificate[][] certificates, boolean coreApp, boolean debuggable,
+ boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs,
+ boolean isolatedSplits) {
+ this.codePath = codePath;
+ this.packageName = packageName;
+ this.splitName = splitName;
+ this.isFeatureSplit = isFeatureSplit;
+ this.configForSplit = configForSplit;
+ this.usesSplitName = usesSplitName;
+ this.versionCode = versionCode;
+ this.revisionCode = revisionCode;
+ this.installLocation = installLocation;
+ this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
+ this.signatures = signatures;
+ this.certificates = certificates;
+ this.coreApp = coreApp;
+ this.debuggable = debuggable;
+ this.multiArch = multiArch;
+ this.use32bitAbi = use32bitAbi;
+ this.extractNativeLibs = extractNativeLibs;
+ this.isolatedSplits = isolatedSplits;
+ }
+ }
+
+ /**
+ * 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. */
+ private static final boolean RIGID_PARSER = false;
+
+ private static final String TAG = "PackageParser";
+
+ public PackageParser() {
+ mMetrics = new DisplayMetrics();
+ mMetrics.setToDefaults();
+ }
+
+ 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);
+ String[] getOverlayPaths(String targetPackageName, String targetPath);
+ String[] getOverlayApks(String targetPackageName);
+ }
+
+ /**
+ * 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);
+ }
+
+ @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) {
+ return null;
+ }
+
+ @Override public String[] getOverlayApks(String targetPackageName) {
+ return null;
+ }
+ }
+
+ /**
+ * 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");
+ }
+
+ /**
+ * Generate and return the {@link PackageInfo} for a parsed package.
+ *
+ * @param p the parsed package.
+ * @param flags indicating which optional information is included.
+ */
+ 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());
+ }
+
+ /**
+ * 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) {
+ // 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);
+ }
+
+ public static boolean isAvailable(PackageUserState state) {
+ return checkUseInstalledOrHidden(0, state, null);
+ }
+
+ public static PackageInfo generatePackageInfo(PackageParser.Package p,
+ 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.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.overlayPriority = p.mOverlayPriority;
+ pi.isStaticOverlay = p.mIsStaticOverlay;
+ 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)) {
+ 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 ((flags&PackageManager.GET_SIGNATURES) != 0) {
+ int N = (p.mSignatures != null) ? p.mSignatures.length : 0;
+ if (N > 0) {
+ pi.signatures = new Signature[N];
+ System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N);
+ }
+ }
+ return pi;
+ }
+
+ private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
+ throws PackageParserException {
+ InputStream is = null;
+ try {
+ // We must read the stream for the JarEntry to retrieve
+ // its certificates.
+ is = jarFile.getInputStream(entry);
+ readFullyIgnoringContents(is);
+ return jarFile.getCertificateChains(entry);
+ } catch (IOException | RuntimeException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed reading " + entry.getName() + " in " + jarFile, e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+
+ public final static int PARSE_IS_SYSTEM = 1<<0;
+ public final static int PARSE_CHATTY = 1<<1;
+ public final static int PARSE_MUST_BE_APK = 1<<2;
+ public final static int PARSE_IGNORE_PROCESSES = 1<<3;
+ public final static int PARSE_FORWARD_LOCK = 1<<4;
+ public final static int PARSE_EXTERNAL_STORAGE = 1<<5;
+ public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
+ public final static int PARSE_IS_PRIVILEGED = 1<<7;
+ public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
+ public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
+ public final static int PARSE_ENFORCE_CODE = 1<<10;
+ /** @deprecated remove when fixing b/34761192 */
+ @Deprecated
+ public final static int PARSE_IS_EPHEMERAL = 1<<11;
+ public final static int PARSE_FORCE_SDK = 1<<12;
+
+ private 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)
+ */
+ 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");
+ }
+
+ 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, int)}.
+ *
+ * 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)
+ */
+ public Package parsePackage(File packageFile, int flags, boolean useCaches)
+ throws PackageParserException {
+ Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
+ if (parsed != null) {
+ return parsed;
+ }
+
+ long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
+ if (packageFile.isDirectory()) {
+ parsed = parseClusterPackage(packageFile, flags);
+ } else {
+ parsed = parseMonolithicPackage(packageFile, flags);
+ }
+
+ long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
+ cacheResult(packageFile, flags, parsed);
+ if (LOG_PARSE_TIMINGS) {
+ parseTime = cacheTime - parseTime;
+ cacheTime = SystemClock.uptimeMillis() - cacheTime;
+ if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
+ Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
+ + "ms, update_cache=" + cacheTime + " ms");
+ }
+ }
+ return parsed;
+ }
+
+ /**
+ * Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}.
+ */
+ public Package parsePackage(File packageFile, int flags) throws PackageParserException {
+ return parsePackage(packageFile, flags, false /* useCaches */);
+ }
+
+ /**
+ * Returns the cache key for a specificied {@code packageFile} and {@code flags}.
+ */
+ private String getCacheKey(File packageFile, int flags) {
+ StringBuilder sb = new StringBuilder(packageFile.getName());
+ sb.append('-');
+ sb.append(flags);
+
+ return sb.toString();
+ }
+
+ @VisibleForTesting
+ protected Package fromCacheEntry(byte[] bytes) {
+ return fromCacheEntryStatic(bytes);
+ }
+
+ /** static version of {@link #fromCacheEntry} for unit tests. */
+ @VisibleForTesting
+ public static Package fromCacheEntryStatic(byte[] bytes) {
+ final Parcel p = Parcel.obtain();
+ p.unmarshall(bytes, 0, bytes.length);
+ p.setDataPosition(0);
+
+ final ReadHelper helper = new ReadHelper(p);
+ helper.startAndInstall();
+
+ PackageParser.Package pkg = new PackageParser.Package(p);
+
+ p.recycle();
+
+ sCachedPackageReadCount.incrementAndGet();
+
+ return pkg;
+ }
+
+ @VisibleForTesting
+ protected byte[] toCacheEntry(Package pkg) {
+ return toCacheEntryStatic(pkg);
+
+ }
+
+ /** static version of {@link #toCacheEntry} for unit tests. */
+ @VisibleForTesting
+ public static byte[] toCacheEntryStatic(Package pkg) {
+ final Parcel p = Parcel.obtain();
+ final WriteHelper helper = new WriteHelper(p);
+
+ pkg.writeToParcel(p, 0 /* flags */);
+
+ helper.finishAndUninstall();
+
+ byte[] serialized = p.marshall();
+ p.recycle();
+
+ return serialized;
+ }
+
+ /**
+ * Given a {@code packageFile} and a {@code cacheFile} returns whether the
+ * cache file is up to date based on the mod-time of both files.
+ */
+ private static boolean isCacheUpToDate(File packageFile, File cacheFile) {
+ try {
+ // NOTE: We don't use the File.lastModified API because it has the very
+ // non-ideal failure mode of returning 0 with no excepions thrown.
+ // The nio2 Files API is a little better but is considerably more expensive.
+ final StructStat pkg = android.system.Os.stat(packageFile.getAbsolutePath());
+ final StructStat cache = android.system.Os.stat(cacheFile.getAbsolutePath());
+ return pkg.st_mtime < cache.st_mtime;
+ } catch (ErrnoException ee) {
+ // The most common reason why stat fails is that a given cache file doesn't
+ // exist. We ignore that here. It's easy to reason that it's safe to say the
+ // cache isn't up to date if we see any sort of exception here.
+ //
+ // (1) Exception while stating the package file : This should never happen,
+ // and if it does, we do a full package parse (which is likely to throw the
+ // same exception).
+ // (2) Exception while stating the cache file : If the file doesn't exist, the
+ // cache is obviously out of date. If the file *does* exist, we can't read it.
+ // We will attempt to delete and recreate it after parsing the package.
+ if (ee.errno != OsConstants.ENOENT) {
+ Slog.w("Error while stating package cache : ", ee);
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
+ * or {@code null} if no cached result exists.
+ */
+ private Package getCachedResult(File packageFile, int flags) {
+ if (mCacheDir == null) {
+ return null;
+ }
+
+ final String cacheKey = getCacheKey(packageFile, flags);
+ final File cacheFile = new File(mCacheDir, cacheKey);
+
+ try {
+ // If the cache is not up to date, return null.
+ if (!isCacheUpToDate(packageFile, cacheFile)) {
+ return null;
+ }
+
+ final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
+ Package p = fromCacheEntry(bytes);
+ if (mCallback != null) {
+ String[] overlayApks = mCallback.getOverlayApks(p.packageName);
+ if (overlayApks != null && overlayApks.length > 0) {
+ for (String overlayApk : overlayApks) {
+ // If a static RRO is updated, return null.
+ if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {
+ return null;
+ }
+ }
+ }
+ }
+ return p;
+ } catch (Throwable e) {
+ Slog.w(TAG, "Error reading package cache: ", e);
+
+ // If something went wrong while reading the cache entry, delete the cache file
+ // so that we regenerate it the next time.
+ cacheFile.delete();
+ return null;
+ }
+ }
+
+ /**
+ * Caches the parse result for {@code packageFile} with flags {@code flags}.
+ */
+ private void cacheResult(File packageFile, int flags, Package parsed) {
+ if (mCacheDir == null) {
+ return;
+ }
+
+ try {
+ final String cacheKey = getCacheKey(packageFile, flags);
+ final File cacheFile = new File(mCacheDir, cacheKey);
+
+ if (cacheFile.exists()) {
+ if (!cacheFile.delete()) {
+ Slog.e(TAG, "Unable to delete cache file: " + cacheFile);
+ }
+ }
+
+ final byte[] cacheEntry = toCacheEntry(parsed);
+
+ if (cacheEntry == null) {
+ return;
+ }
+
+ try (FileOutputStream fos = new FileOutputStream(cacheFile)) {
+ fos.write(cacheEntry);
+ } catch (IOException ioe) {
+ Slog.w(TAG, "Error writing cache entry.", ioe);
+ cacheFile.delete();
+ }
+ } catch (Throwable e) {
+ Slog.w(TAG, "Error saving package cache.", e);
+ }
+ }
+
+ /**
+ * 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, int)}.
+ */
+ 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(packageDir.getAbsolutePath());
+ 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, int)}.
+ *
+ * @deprecated external callers should move to
+ * {@link #parsePackage(File, int)}. Eventually this method will
+ * be marked private.
+ */
+ @Deprecated
+ public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
+ final AssetManager assets = newConfiguredAssetManager();
+ final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
+ if (mOnlyCoreApps) {
+ if (!lite.coreApp) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Not a coreApp: " + apkFile);
+ }
+ }
+
+ try {
+ final Package pkg = parseBaseApk(apkFile, assets, flags);
+ pkg.setCodePath(apkFile.getAbsolutePath());
+ pkg.setUse32bitAbi(lite.use32bitAbi);
+ return pkg;
+ } finally {
+ IoUtils.closeQuietly(assets);
+ }
+ }
+
+ private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
+ throws PackageParserException {
+ if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + apkPath);
+ }
+
+ // The AssetManager guarantees uniqueness for asset paths, so if this asset path
+ // already exists in the AssetManager, addAssetPath will only return the cookie
+ // assigned to it.
+ int cookie = assets.addAssetPath(apkPath);
+ if (cookie == 0) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
+ return cookie;
+ }
+
+ 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);
+
+ final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
+
+ Resources res = null;
+ XmlResourceParser parser = null;
+ try {
+ res = new Resources(assets, mMetrics, null);
+ parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+
+ 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.setSignatures(null);
+
+ 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 int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
+
+ final Resources res;
+ XmlResourceParser parser = null;
+ try {
+ res = new Resources(assets, mMetrics, null);
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+
+ 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;
+ }
+
+ public static int getApkSigningVersion(Package pkg) {
+ try {
+ if (ApkSignatureSchemeV2Verifier.hasSignature(pkg.baseCodePath)) {
+ return APK_SIGNING_V2;
+ }
+ return APK_SIGNING_V1;
+ } catch (IOException e) {
+ }
+ return APK_SIGNING_UNKNOWN;
+ }
+
+ /**
+ * Populates the correct packages fields with the given certificates.
+ * <p>
+ * This is useful when we've already processed the certificates [such as during package
+ * installation through an installer session]. We don't re-process the archive and
+ * simply populate the correct fields.
+ */
+ public static void populateCertificates(Package pkg, Certificate[][] certificates)
+ throws PackageParserException {
+ pkg.mCertificates = null;
+ pkg.mSignatures = null;
+ pkg.mSigningKeys = null;
+
+ pkg.mCertificates = certificates;
+ try {
+ pkg.mSignatures = convertToSignatures(certificates);
+ } catch (CertificateEncodingException e) {
+ // certificates weren't encoded properly; something went wrong
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + pkg.baseCodePath, e);
+ }
+ pkg.mSigningKeys = new ArraySet<>(certificates.length);
+ for (int i = 0; i < certificates.length; i++) {
+ Certificate[] signerCerts = certificates[i];
+ Certificate signerCert = signerCerts[0];
+ pkg.mSigningKeys.add(signerCert.getPublicKey());
+ }
+ // add signatures to child packages
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ Package childPkg = pkg.childPackages.get(i);
+ childPkg.mCertificates = pkg.mCertificates;
+ childPkg.mSignatures = pkg.mSignatures;
+ childPkg.mSigningKeys = pkg.mSigningKeys;
+ }
+ }
+
+ /**
+ * Collect certificates from all the APKs described in the given package,
+ * populating {@link Package#mSignatures}. Also asserts that all APK
+ * contents are signed correctly and consistently.
+ */
+ public static void collectCertificates(Package pkg, int parseFlags)
+ throws PackageParserException {
+ collectCertificatesInternal(pkg, parseFlags);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ Package childPkg = pkg.childPackages.get(i);
+ childPkg.mCertificates = pkg.mCertificates;
+ childPkg.mSignatures = pkg.mSignatures;
+ childPkg.mSigningKeys = pkg.mSigningKeys;
+ }
+ }
+
+ private static void collectCertificatesInternal(Package pkg, int parseFlags)
+ throws PackageParserException {
+ pkg.mCertificates = null;
+ pkg.mSignatures = null;
+ pkg.mSigningKeys = null;
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags);
+
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ collectCertificates(pkg, new File(pkg.splitCodePaths[i]), parseFlags);
+ }
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ private static void collectCertificates(Package pkg, File apkFile, int parseFlags)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ // Try to verify the APK using APK Signature Scheme v2.
+ boolean verified = false;
+ {
+ Certificate[][] allSignersCerts = null;
+ Signature[] signatures = null;
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
+ allSignersCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
+ signatures = convertToSignatures(allSignersCerts);
+ // APK verified using APK Signature Scheme v2.
+ verified = true;
+ } catch (ApkSignatureSchemeV2Verifier.SignatureNotFoundException e) {
+ // No APK Signature Scheme v2 signature found
+ if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v2 signature in ephemeral package " + apkPath,
+ e);
+ }
+ // Static shared libraries must use only the V2 signing scheme
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Static shared libs must use v2 signature scheme " + apkPath);
+ }
+ } catch (Exception e) {
+ // APK Signature Scheme v2 signature was found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v2",
+ e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ if (verified) {
+ if (pkg.mCertificates == null) {
+ pkg.mCertificates = allSignersCerts;
+ pkg.mSignatures = signatures;
+ pkg.mSigningKeys = new ArraySet<>(allSignersCerts.length);
+ for (int i = 0; i < allSignersCerts.length; i++) {
+ Certificate[] signerCerts = allSignersCerts[i];
+ Certificate signerCert = signerCerts[0];
+ pkg.mSigningKeys.add(signerCert.getPublicKey());
+ }
+ } else {
+ if (!Signature.areExactMatch(pkg.mSignatures, signatures)) {
+ throw new PackageParserException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ apkPath + " has mismatched certificates");
+ }
+ }
+ // Not yet done, because we need to confirm that AndroidManifest.xml exists and,
+ // if requested, that classes.dex exists.
+ }
+ }
+
+ StrictJarFile jarFile = null;
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
+ // Ignore signature stripping protections when verifying APKs from system partition.
+ // For those APKs we only care about extracting signer certificates, and don't care
+ // about verifying integrity.
+ boolean signatureSchemeRollbackProtectionsEnforced =
+ (parseFlags & PARSE_IS_SYSTEM_DIR) == 0;
+ jarFile = new StrictJarFile(
+ apkPath,
+ !verified, // whether to verify JAR signature
+ signatureSchemeRollbackProtectionsEnforced);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+ // Always verify manifest, regardless of source
+ final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
+ if (manifestEntry == null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Package " + apkPath + " has no manifest");
+ }
+
+ // Optimization: early termination when APK already verified
+ if (verified) {
+ return;
+ }
+
+ // APK's integrity needs to be verified using JAR signature scheme.
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV1");
+ final List<ZipEntry> toVerify = new ArrayList<>();
+ toVerify.add(manifestEntry);
+
+ // If we're parsing an untrusted package, verify all contents
+ if ((parseFlags & PARSE_IS_SYSTEM_DIR) == 0) {
+ final Iterator<ZipEntry> i = jarFile.iterator();
+ while (i.hasNext()) {
+ final ZipEntry entry = i.next();
+
+ if (entry.isDirectory()) continue;
+
+ final String entryName = entry.getName();
+ if (entryName.startsWith("META-INF/")) continue;
+ if (entryName.equals(ANDROID_MANIFEST_FILENAME)) continue;
+
+ toVerify.add(entry);
+ }
+ }
+
+ // Verify that entries are signed consistently with the first entry
+ // we encountered. Note that for splits, certificates may have
+ // already been populated during an earlier parse of a base APK.
+ for (ZipEntry entry : toVerify) {
+ final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
+ if (ArrayUtils.isEmpty(entryCerts)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Package " + apkPath + " has no certificates at entry "
+ + entry.getName());
+ }
+ final Signature[] entrySignatures = convertToSignatures(entryCerts);
+
+ if (pkg.mCertificates == null) {
+ pkg.mCertificates = entryCerts;
+ pkg.mSignatures = entrySignatures;
+ pkg.mSigningKeys = new ArraySet<PublicKey>();
+ for (int i=0; i < entryCerts.length; i++) {
+ pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
+ }
+ } else {
+ if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {
+ throw new PackageParserException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath
+ + " has mismatched certificates at entry "
+ + entry.getName());
+ }
+ }
+ }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ } catch (GeneralSecurityException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
+ "Failed to collect certificates from " + apkPath, e);
+ } catch (IOException | RuntimeException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath, e);
+ } finally {
+ closeQuietly(jarFile);
+ }
+ }
+
+ private static Signature[] convertToSignatures(Certificate[][] certs)
+ throws CertificateEncodingException {
+ final Signature[] res = new Signature[certs.length];
+ for (int i = 0; i < certs.length; i++) {
+ res[i] = new Signature(certs[i]);
+ }
+ return res;
+ }
+
+ 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 {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ AssetManager assets = null;
+ XmlResourceParser parser = null;
+ try {
+ assets = newConfiguredAssetManager();
+ int cookie = assets.addAssetPath(apkPath);
+ if (cookie == 0) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Failed to parse " + apkPath);
+ }
+
+ final DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+
+ parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+
+ final Signature[] signatures;
+ final Certificate[][] certificates;
+ if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
+ // TODO: factor signature related items out of Package object
+ final Package tempPkg = new Package((String) null);
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ collectCertificates(tempPkg, apkFile, flags);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ signatures = tempPkg.mSignatures;
+ certificates = tempPkg.mCertificates;
+ } else {
+ signatures = null;
+ certificates = null;
+ }
+
+ final AttributeSet attrs = parser;
+ return parseApkLite(apkPath, parser, attrs, signatures, certificates);
+
+ } 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);
+ IoUtils.closeQuietly(assets);
+ }
+ }
+
+ private 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";
+ }
+
+ private 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,
+ Signature[] signatures, Certificate[][] certificates)
+ throws IOException, XmlPullParserException, PackageParserException {
+ final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
+
+ int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
+ int versionCode = 0;
+ int revisionCode = 0;
+ boolean coreApp = false;
+ boolean debuggable = false;
+ boolean multiArch = false;
+ boolean use32bitAbi = false;
+ boolean extractNativeLibs = true;
+ boolean isolatedSplits = false;
+ boolean isFeatureSplit = false;
+ String configForSplit = null;
+ String usesSplitName = 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("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);
+ }
+ }
+
+ // Only search the tree when the tag is directly below <manifest>
+ 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);
+ }
+ }
+ } 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");
+ }
+ }
+ }
+
+ return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+ configForSplit, usesSplitName, versionCode, revisionCode, installLocation,
+ verifiers, signatures, certificates, coreApp, debuggable, multiArch, use32bitAbi,
+ extractNativeLibs, isolatedSplits);
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ if (mCallback != null) {
+ String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
+ if (overlayPaths != null && overlayPaths.length > 0) {
+ for (String overlayPath : overlayPaths) {
+ res.getAssets().addOverlayPath(overlayPath);
+ }
+ }
+ }
+
+ final Package pkg = new Package(pkgName);
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifest);
+
+ pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
+ 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);
+
+ 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) {
+ if ((flags & PARSE_IS_EPHEMERAL) != 0) {
+ outError[0] = "sharedUserId not allowed in ephemeral application";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
+ return null;
+ }
+ String nameError = validateName(str, true, false);
+ 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 "forward lock" flag */
+ if ((flags & PARSE_FORWARD_LOCK) != 0) {
+ pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK;
+ }
+
+ /* Set the global "on SD card" flag */
+ if ((flags & PARSE_EXTERNAL_STORAGE) != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+ }
+
+ if (sa.getBoolean(com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false)) {
+ pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+ }
+
+ // 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.mOverlayPriority = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
+ 0);
+ pkg.mIsStaticOverlay = 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 (!checkOverlayRequiredSystemProperty(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);
+ return null;
+ }
+
+ 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) {
+ targetCode = minCode = val.string.toString();
+ } else {
+ // If it's not a string, it's an integer.
+ targetVers = 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;
+ }
+ }
+
+ 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_VERSION, 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 && (flags&PARSE_IS_SYSTEM) != 0) {
+ 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 implicitPerms = 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 (implicitPerms == null) {
+ implicitPerms = new StringBuilder(128);
+ implicitPerms.append(pkg.packageName);
+ implicitPerms.append(": compat added ");
+ } else {
+ implicitPerms.append(' ');
+ }
+ implicitPerms.append(npi.name);
+ pkg.requestedPermissions.add(npi.name);
+ }
+ }
+ if (implicitPerms != null) {
+ Slog.i(TAG, implicitPerms.toString());
+ }
+
+ final int NS = PackageParser.SPLIT_PERMISSIONS.length;
+ for (int is=0; is<NS; is++) {
+ final PackageParser.SplitPermissionInfo spi
+ = PackageParser.SPLIT_PERMISSIONS[is];
+ if (pkg.applicationInfo.targetSdkVersion >= spi.targetSdk
+ || !pkg.requestedPermissions.contains(spi.rootPerm)) {
+ continue;
+ }
+ for (int in=0; in<spi.newPerms.length; in++) {
+ final String perm = spi.newPerms[in];
+ if (!pkg.requestedPermissions.contains(perm)) {
+ pkg.requestedPermissions.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;
+ }
+
+ private boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
+
+ if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
+ if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
+ // malformed condition - incomplete
+ Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
+ + "=" + propValue + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue to be specified.");
+ return false;
+ }
+ // no valid condition set - so no exclusion criteria, overlay will be included.
+ return true;
+ }
+
+ // check property value - make sure it is both set and equal to expected value
+ final String currValue = SystemProperties.get(propName);
+ return (currValue != null && currValue.equals(propValue));
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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 platformSdkVersion platform SDK version number, typically
+ * Build.VERSION.SDK_INT
+ * @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, @IntRange(from = 1) int platformSdkVersion,
+ @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 (ArrayUtils.contains(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 (ArrayUtils.contains(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) {
+ 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.appendInt(
+ 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;
+ }
+
+ private 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;
+ }
+
+ private 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));
+ }
+
+ private 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 {
+ PermissionGroup perm = new PermissionGroup(owner);
+
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup);
+ 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);
+ if (perm.info.priority > 0 && (flags&PARSE_IS_SYSTEM) == 0) {
+ perm.info.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);
+
+ Permission perm = new Permission(owner);
+ 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);
+
+ 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.protectionLevel&PermissionInfo.PROTECTION_MASK_FLAGS) != 0) {
+ if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_INSTANT) == 0
+ && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0
+ && (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) !=
+ PermissionInfo.PROTECTION_SIGNATURE) {
+ outError[0] = "<permission> protectionLevel specifies a non-instnat flag but is "
+ + "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);
+
+ 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.
+ */
+ 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);
+
+ 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 ((flags&PARSE_IS_SYSTEM) != 0) {
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_persistent,
+ false)) {
+ // Check if persistence is based on a feature being present
+ final String requiredFeature = sa.getNonResourceString(
+ com.android.internal.R.styleable.
+ AndroidManifestApplication_persistentWhenFeatureAvailable);
+ if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
+ ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
+ }
+ }
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers,
+ false)) {
+ owner.mRequiredForAllUsers = true;
+ }
+
+ String restrictedAccountType = sa.getString(com.android.internal.R.styleable
+ .AndroidManifestApplication_restrictedAccountType);
+ if (restrictedAccountType != null && restrictedAccountType.length() > 0) {
+ owner.mRestrictedAccountType = restrictedAccountType;
+ }
+
+ String requiredAccountType = sa.getString(com.android.internal.R.styleable
+ .AndroidManifestApplication_requiredAccountType);
+ if (requiredAccountType != null && requiredAccountType.length() > 0) {
+ owner.mRequiredAccountType = requiredAccountType;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_vmSafeMode,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE;
+ }
+
+ owner.baseHardwareAccelerated = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
+ owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
+ if (owner.baseHardwareAccelerated) {
+ ai.flags |= ApplicationInfo.FLAG_HARDWARE_ACCELERATED;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_HAS_CODE;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowTaskReparenting,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowClearUserData,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA;
+ }
+
+ // The parent package controls installation, hence specify test only installs.
+ if (owner.parentPackage == null) {
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_testOnly,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_TEST_ONLY;
+ }
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_largeHeap,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_LARGE_HEAP;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_usesCleartextTraffic,
+ true)) {
+ 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_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;
+ }
+
+ ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 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);
+
+ 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 (false) {
+ 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 != 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;
+ }
+
+ 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;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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);
+
+ 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();
+ 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 (!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;
+ }
+ }
+ }
+
+ // 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);
+
+ PackageBackwardCompatibility.modifySharedLibraries(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 {
+ outInfo.name
+ = buildClassName(owner.applicationInfo.packageName, name, outError);
+ if (outInfo.name == null) {
+ return false;
+ }
+ }
+
+ final boolean useRoundIcon =
+ Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ int roundIconVal = useRoundIcon ? 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;
+ }
+
+ 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,
+ ActivityManager.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*/));
+ }
+
+ a.info.lockTaskLaunchMode =
+ sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
+
+ a.info.encryptionAware = 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_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;
+ }
+
+ } 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;
+ if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
+ Slog.w(TAG, "Activity exported request ignored due to singleUser: "
+ + a.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ a.info.exported = false;
+ setExported = true;
+ }
+ }
+
+ a.info.encryptionAware = 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.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;
+ }
+ // we don't have an attribute [or it's false], but, we have meta-data
+ if (!visibleToEphemeral && a.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
+ visibleToEphemeral = true; // set in case there are more intent filters
+ a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ a.info.flags &= ~ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
+ owner.visibleToInstantApps = true;
+ // cycle through any filters already seen
+ for (int i = a.intents.size() - 1; i >= 0; --i) {
+ a.intents.get(i)
+ .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
+ }
+ if (owner.preferredActivityFilters != null) {
+ for (int i = owner.preferredActivityFilters.size() - 1; i >= 0; --i) {
+ owner.preferredActivityFilters.get(i)
+ .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
+ }
+ }
+ }
+ } else if (!receiver && parser.getName().equals("layout")) {
+ parseLayout(res, parser, a);
+ } else {
+ 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;
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * @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);
+ }
+
+ 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.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.encryptionAware = 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.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.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;
+ if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
+ Slog.w(TAG, "Provider exported request ignored due to singleUser: "
+ + p.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ p.info.exported = false;
+ }
+ }
+
+ p.info.encryptionAware = 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, owner, p, outError)) {
+ return null;
+ }
+
+ return p;
+ }
+
+ private boolean parseProviderTags(Resources res, XmlResourceParser parser,
+ boolean visibleToEphemeral, Package owner, 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.intents.add(intent);
+
+ } else if (parser.getName().equals("meta-data")) {
+ if ((outInfo.metaData=parseMetaData(res, parser,
+ outInfo.metaData, outError)) == null) {
+ return false;
+ }
+ // we don't have an attribute [or it's false], but, we have meta-data
+ if (!visibleToEphemeral && outInfo.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
+ visibleToEphemeral = true; // set in case there are more intent filters
+ outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ owner.visibleToInstantApps = true;
+ // cycle through any filters already seen
+ for (int i = outInfo.intents.size() - 1; i >= 0; --i) {
+ outInfo.intents.get(i)
+ .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
+ }
+ }
+
+ } else if (parser.getName().equals("grant-uri-permission")) {
+ TypedArray sa = res.obtainAttributes(parser,
+ 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.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_singleUser,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
+ if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
+ Slog.w(TAG, "Service exported request ignored due to singleUser: "
+ + s.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ s.info.exported = false;
+ setExported = true;
+ }
+ }
+
+ s.info.encryptionAware = 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.intents.add(intent);
+ } else if (parser.getName().equals("meta-data")) {
+ if ((s.metaData=parseMetaData(res, parser, s.metaData,
+ outError)) == null) {
+ return null;
+ }
+ // we don't have an attribute [or it's false], but, we have meta-data
+ if (!visibleToEphemeral && s.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
+ visibleToEphemeral = true; // set in case there are more intent filters
+ s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+ owner.visibleToInstantApps = true;
+ // cycle through any filters already seen
+ for (int i = s.intents.size() - 1; i >= 0; --i) {
+ s.intents.get(i)
+ .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
+ }
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG, "Unknown element under <service>: "
+ + 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;
+ }
+
+ private 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);
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_label);
+ if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
+ outInfo.nonLocalizedLabel = v.coerceToString();
+ }
+
+ final boolean useRoundIcon =
+ Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ int roundIconVal = useRoundIcon ? 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;
+ }
+
+ /**
+ * 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.
+ */
+ public final static class Package implements Parcelable {
+
+ 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.
+ public ApplicationInfo applicationInfo = new ApplicationInfo();
+
+ public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
+ public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
+ public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
+ public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
+ public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
+ public final ArrayList<Service> services = new ArrayList<Service>(0);
+ public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
+
+ public final ArrayList<String> requestedPermissions = new ArrayList<String>();
+
+ public ArrayList<String> protectedBroadcasts;
+
+ public Package parentPackage;
+ public ArrayList<Package> childPackages;
+
+ public String staticSharedLibName = null;
+ public int staticSharedLibVersion = 0;
+ public ArrayList<String> libraryNames = null;
+ public ArrayList<String> usesLibraries = null;
+ public ArrayList<String> usesStaticLibraries = null;
+ public int[] usesStaticLibrariesVersions = null;
+ public String[][] usesStaticLibrariesCertDigests = null;
+ public ArrayList<String> usesOptionalLibraries = null;
+ public String[] usesLibraryFiles = 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
+ public Bundle mAppMetaData = null;
+
+ // The version code declared for this package.
+ public int mVersionCode;
+
+ // The version name declared for this package.
+ public String mVersionName;
+
+ // The shared user id that this package wants to use.
+ public String mSharedUserId;
+
+ // The shared user label that this package wants to use.
+ public int mSharedUserLabel;
+
+ // Signatures that were read from the package.
+ public Signature[] mSignatures;
+ public Certificate[][] mCertificates;
+
+ // For use by package manager service for quick lookup of
+ // preferred up order.
+ 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.
+ public Object mExtras;
+
+ // Applications hardware preferences
+ public ArrayList<ConfigurationInfo> configPreferences = null;
+
+ // Applications requested features
+ public ArrayList<FeatureInfo> reqFeatures = null;
+
+ // Applications requested feature groups
+ public ArrayList<FeatureGroupInfo> featureGroups = null;
+
+ 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 int mOverlayPriority;
+ public boolean mIsStaticOverlay;
+ public boolean mTrustedOverlay;
+
+ /**
+ * Data used to feed the KeySetManagerService
+ */
+ public ArraySet<PublicKey> mSigningKeys;
+ public ArraySet<String> mUpgradeKeySets;
+ 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;
+
+ 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);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+
+ 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
+ }
+
+ 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;
+ }
+ }
+ }
+
+ public void setSignatures(Signature[] signatures) {
+ this.mSignatures = signatures;
+ if (childPackages != null) {
+ final int packageCount = childPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ childPackages.get(i).mSignatures = signatures;
+ }
+ }
+ }
+
+ 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;
+ }
+
+ 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 isForwardLocked() {
+ return applicationInfo.isForwardLocked();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isSystemApp() {
+ return applicationInfo.isSystemApp();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isPrivilegedApp() {
+ return applicationInfo.isPrivilegedApp();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isUpdatedSystemApp() {
+ return applicationInfo.isUpdatedSystemApp();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean canHaveOatDir() {
+ // The following app types CANNOT have oat directory
+ // - non-updated system apps
+ // - forward-locked apps or apps installed in ASEC containers
+ return (!isSystemApp() || isUpdatedSystemApp())
+ && !isForwardLocked() && !applicationInfo.isExternalAsec();
+ }
+
+ public boolean isMatch(int flags) {
+ if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+ return isSystemApp();
+ }
+ 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);
+ 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.readInt();
+ libraryNames = dest.createStringArrayList();
+ internStringArrayList(libraryNames);
+ usesLibraries = dest.createStringArrayList();
+ internStringArrayList(usesLibraries);
+ usesOptionalLibraries = dest.createStringArrayList();
+ internStringArrayList(usesOptionalLibraries);
+ usesLibraryFiles = dest.readStringArray();
+
+ final int libCount = dest.readInt();
+ if (libCount > 0) {
+ usesStaticLibraries = new ArrayList<>(libCount);
+ dest.readStringList(usesStaticLibraries);
+ internStringArrayList(usesStaticLibraries);
+ usesStaticLibrariesVersions = new int[libCount];
+ dest.readIntArray(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();
+ mVersionName = dest.readString();
+ if (mVersionName != null) {
+ mVersionName = mVersionName.intern();
+ }
+ mSharedUserId = dest.readString();
+ if (mSharedUserId != null) {
+ mSharedUserId = mSharedUserId.intern();
+ }
+ mSharedUserLabel = dest.readInt();
+
+ mSignatures = (Signature[]) dest.readParcelableArray(boot, Signature.class);
+ mCertificates = (Certificate[][]) dest.readSerializable();
+
+ 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();
+ mOverlayPriority = dest.readInt();
+ mIsStaticOverlay = (dest.readInt() == 1);
+ mTrustedOverlay = (dest.readInt() == 1);
+ mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
+ 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.
+ */
+ private 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(protectedBroadcasts);
+
+ // TODO: This doesn't work: b/64295061
+ dest.writeParcelable(parentPackage, flags);
+ dest.writeParcelableList(childPackages, flags);
+
+ dest.writeString(staticSharedLibName);
+ dest.writeInt(staticSharedLibVersion);
+ dest.writeStringList(libraryNames);
+ dest.writeStringList(usesLibraries);
+ dest.writeStringList(usesOptionalLibraries);
+ dest.writeStringArray(usesLibraryFiles);
+
+ if (ArrayUtils.isEmpty(usesStaticLibraries)) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(usesStaticLibraries.size());
+ dest.writeStringList(usesStaticLibraries);
+ dest.writeIntArray(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.writeString(mVersionName);
+ dest.writeString(mSharedUserId);
+ dest.writeInt(mSharedUserLabel);
+
+ dest.writeParcelableArray(mSignatures, flags);
+ dest.writeSerializable(mCertificates);
+
+ 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.writeInt(mOverlayPriority);
+ dest.writeInt(mIsStaticOverlay ? 1 : 0);
+ dest.writeInt(mTrustedOverlay ? 1 : 0);
+ dest.writeArraySet(mSigningKeys);
+ 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> {
+ public final ArrayList<II> intents;
+ public final String className;
+
+ public Bundle metaData;
+ public Package owner;
+
+ ComponentName componentName;
+ String componentShortName;
+
+ public Component(Package _owner) {
+ owner = _owner;
+ intents = null;
+ 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;
+ }
+
+ 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 {
+ public final PermissionInfo info;
+ public boolean tree;
+ public PermissionGroup group;
+
+ public Permission(Package _owner) {
+ super(_owner);
+ info = new PermissionInfo();
+ }
+
+ 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);
+ }
+
+ 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 {
+ public final PermissionGroupInfo info;
+
+ public PermissionGroup(Package _owner) {
+ super(_owner);
+ info = new PermissionGroupInfo();
+ }
+
+ 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 (p.staticSharedLibName != null) {
+ return true;
+ }
+ return false;
+ }
+
+ 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.overlayPaths;
+ }
+
+ 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;
+ }
+ 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;
+ }
+
+ 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;
+ }
+
+ 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 {
+ public final ActivityInfo info;
+ private boolean mHasMaxAspectRatio;
+
+ private boolean hasMaxAspectRatio() {
+ return mHasMaxAspectRatio;
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+
+ private Activity(Parcel in) {
+ super(in);
+ info = in.readParcelable(Object.class.getClassLoader());
+ mHasMaxAspectRatio = in.readBoolean();
+
+ for (ActivityIntentInfo aii : intents) {
+ aii.activity = this;
+ }
+
+ 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];
+ }
+ };
+ }
+
+ 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 {
+ 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;
+ }
+
+ 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];
+ }
+ };
+ }
+
+ 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 {
+ public final ProviderInfo info;
+ public boolean syncable;
+
+ public Provider(final ParseComponentArgs args, final ProviderInfo _info) {
+ super(args, _info);
+ info = _info;
+ info.applicationInfo = args.owner.applicationInfo;
+ syncable = false;
+ }
+
+ 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];
+ }
+ };
+ }
+
+ 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 {
+ 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];
+ }
+ };
+ }
+
+ 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 {
+ public boolean hasDefault;
+ public int labelRes;
+ public CharSequence nonLocalizedLabel;
+ public int icon;
+ public int logo;
+ public int banner;
+ public int preferred;
+
+ 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 {
+ 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 {
+ 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 {
+ 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
+ */
+ public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
+ sCompatibilityModeEnabled = compatibilityModeEnabled;
+ }
+
+ private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>();
+
+ public static long readFullyIgnoringContents(InputStream in) throws IOException {
+ byte[] buffer = sBuffer.getAndSet(null);
+ if (buffer == null) {
+ buffer = new byte[4096];
+ }
+
+ int n = 0;
+ int count = 0;
+ while ((n = in.read(buffer, 0, buffer.length)) != -1) {
+ count += n;
+ }
+
+ sBuffer.set(buffer);
+ return count;
+ }
+
+ public static void closeQuietly(StrictJarFile jarFile) {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ 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 00000000..44def332
--- /dev/null
+++ b/android/content/pm/PackageParserCacheHelper.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+ @Override
+ public String readString(Parcel p) {
+ return mStrings.get(p.readInt());
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ 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);
+ }
+ }
+
+ /**
+ * 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/PackageStats.java b/android/content/pm/PackageStats.java
new file mode 100644
index 00000000..27b3506f
--- /dev/null
+++ b/android/content/pm/PackageStats.java
@@ -0,0 +1,211 @@
+/*
+ * 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.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 */
+ 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 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 00000000..069b2d4e
--- /dev/null
+++ b/android/content/pm/PackageUserState.java
@@ -0,0 +1,252 @@
+/*
+ * 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.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+
+/**
+ * Per-user state information about a package.
+ * @hide
+ */
+public class PackageUserState {
+ public long ceDataInode;
+ public boolean installed;
+ public boolean stopped;
+ public boolean notLaunched;
+ public boolean hidden; // Is the app restricted by owner / admin
+ public boolean suspended;
+ 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 ArraySet<String> disabledComponents;
+ public ArraySet<String> enabledComponents;
+
+ public String[] overlayPaths;
+
+ 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;
+ }
+
+ public PackageUserState(PackageUserState o) {
+ ceDataInode = o.ceDataInode;
+ installed = o.installed;
+ stopped = o.stopped;
+ notLaunched = o.notLaunched;
+ hidden = o.hidden;
+ suspended = o.suspended;
+ instantApp = o.instantApp;
+ virtualPreload = o.virtualPreload;
+ enabled = o.enabled;
+ lastDisableAppCaller = o.lastDisableAppCaller;
+ domainVerificationStatus = o.domainVerificationStatus;
+ appLinkGeneration = o.appLinkGeneration;
+ categoryHint = o.categoryHint;
+ installReason = o.installReason;
+ disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
+ enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
+ overlayPaths =
+ o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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(ComponentInfo componentInfo, int flags) {
+ final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp();
+ final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
+ if (!isAvailable(flags)
+ && !(isSystemApp && matchUninstalled)) return false;
+ if (!isEnabled(componentInfo, flags)) return false;
+
+ if ((flags & MATCH_SYSTEM_ONLY) != 0) {
+ if (!isSystemApp) {
+ return false;
+ }
+ }
+
+ final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
+ && !componentInfo.directBootAware;
+ final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
+ && componentInfo.directBootAware;
+ return matchesUnaware || matchesAware;
+ }
+
+ /**
+ * Test if the given component is considered enabled.
+ */
+ public boolean isEnabled(ComponentInfo componentInfo, 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;
+ }
+ case COMPONENT_ENABLED_STATE_DEFAULT:
+ if (!componentInfo.applicationInfo.enabled) {
+ return false;
+ }
+ case COMPONENT_ENABLED_STATE_ENABLED:
+ break;
+ }
+
+ // Check if component has explicit state before falling through to
+ // the manifest default
+ if (ArrayUtils.contains(this.enabledComponents, componentInfo.name)) {
+ return true;
+ }
+ if (ArrayUtils.contains(this.disabledComponents, componentInfo.name)) {
+ return false;
+ }
+
+ return componentInfo.enabled;
+ }
+
+ @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 (suspended != oldState.suspended) {
+ 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 ((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;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/android/content/pm/ParceledListSlice.java b/android/content/pm/ParceledListSlice.java
new file mode 100644
index 00000000..d12e8846
--- /dev/null
+++ b/android/content/pm/ParceledListSlice.java
@@ -0,0 +1,88 @@
+/*
+ * 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 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> {
+ 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
+ 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")
+ 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 00000000..7e49d7df
--- /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 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 00000000..7c4478d0
--- /dev/null
+++ b/android/content/pm/PermissionGroupInfo.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.annotation.StringRes;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information you can retrieve about a particular security permission
+ * group known to the system. This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;permission-group&gt; 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 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;
+
+ /**
+ * 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 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;
+
+ /**
+ * Additional flags about this group as given by
+ * {@link android.R.attr#permissionGroupFlags}.
+ */
+ public int flags;
+
+ /**
+ * Prioritization of this group, for visually sorting with other groups.
+ */
+ public int priority;
+
+ public PermissionGroupInfo() {
+ }
+
+ public PermissionGroupInfo(PermissionGroupInfo orig) {
+ super(orig);
+ descriptionRes = orig.descriptionRes;
+ requestRes = orig.requestRes;
+ 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 CharSequence loadDescription(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);
+ TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ dest.writeInt(flags);
+ dest.writeInt(priority);
+ }
+
+ public static final 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();
+ 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 00000000..b84c1b93
--- /dev/null
+++ b/android/content/pm/PermissionInfo.java
@@ -0,0 +1,348 @@
+/*
+ * 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.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information you can retrieve about a particular security permission
+ * known to the system. This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;permission&gt; 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;
+
+ /**
+ * 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;
+
+ /**
+ * Mask for {@link #protectionLevel}: the basic protection type.
+ */
+ public static final int PROTECTION_MASK_BASE = 0xf;
+
+ /**
+ * Mask for {@link #protectionLevel}: additional flag bits.
+ */
+ public static final int PROTECTION_MASK_FLAGS = 0xfff0;
+
+ /**
+ * The level of access this permission is protecting, as per
+ * {@link android.R.attr#protectionLevel}. Values may be
+ * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or
+ * {@link #PROTECTION_SIGNATURE}. May also include the additional
+ * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT}
+ * (which only make sense in combination with the base
+ * {@link #PROTECTION_SIGNATURE}.
+ */
+ public int protectionLevel;
+
+ /**
+ * The group this permission is a part of, as per
+ * {@link android.R.attr#permissionGroup}.
+ */
+ public String group;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>costsMoney</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ */
+ public static final int FLAG_COSTS_MONEY = 1<<0;
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>removed</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_REMOVED = 1<<1;
+
+ /**
+ * Flag for {@link #flags}, indicating that this permission has been
+ * installed into the system's globally defined permissions.
+ */
+ public static final int FLAG_INSTALLED = 1<<30;
+
+ /**
+ * Additional flags about this permission as given by
+ * {@link android.R.attr#permissionFlags}.
+ */
+ public 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 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 int requestRes;
+
+ /**
+ * 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 CharSequence nonLocalizedDescription;
+
+ /** @hide */
+ public static int fixProtectionLevel(int level) {
+ if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
+ level = PROTECTION_SIGNATURE | PROTECTION_FLAG_PRIVILEGED;
+ }
+ return level;
+ }
+
+ /** @hide */
+ public static 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";
+ }
+ return protLevel;
+ }
+
+ public PermissionInfo() {
+ }
+
+ public PermissionInfo(PermissionInfo orig) {
+ super(orig);
+ protectionLevel = orig.protectionLevel;
+ flags = orig.flags;
+ group = orig.group;
+ 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 CharSequence loadDescription(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 "PermissionInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeInt(protectionLevel);
+ dest.writeInt(flags);
+ dest.writeString(group);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(requestRes);
+ TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ }
+
+ public static final Creator<PermissionInfo> CREATOR =
+ new Creator<PermissionInfo>() {
+ public PermissionInfo createFromParcel(Parcel source) {
+ return new PermissionInfo(source);
+ }
+ public PermissionInfo[] newArray(int size) {
+ return new PermissionInfo[size];
+ }
+ };
+
+ private PermissionInfo(Parcel source) {
+ super(source);
+ protectionLevel = source.readInt();
+ flags = source.readInt();
+ group = source.readString();
+ descriptionRes = source.readInt();
+ requestRes = source.readInt();
+ nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+}
diff --git a/android/content/pm/ProviderInfo.java b/android/content/pm/ProviderInfo.java
new file mode 100644
index 00000000..379b7833
--- /dev/null
+++ b/android/content/pm/ProviderInfo.java
@@ -0,0 +1,181 @@
+/*
+ * 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 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;
+ 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.writeString(authority);
+ out.writeString(readPermission);
+ out.writeString(writePermission);
+ out.writeInt(grantUriPermissions ? 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 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.readString();
+ readPermission = in.readString();
+ writePermission = in.readString();
+ grantUriPermissions = 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/RegisteredServicesCache.java b/android/content/pm/RegisteredServicesCache.java
new file mode 100644
index 00000000..aea843ad
--- /dev/null
+++ b/android/content/pm/RegisteredServicesCache.java
@@ -0,0 +1,782 @@
+/*
+ * 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.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 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;
+
+import libcore.io.IoUtils;
+
+/**
+ * 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")
+ 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;
+
+ 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 final 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(new Runnable() {
+ public void run() {
+ listener2.onServiceChanged(type, userId, removed);
+ }
+ });
+ }
+
+ /**
+ * Value type that describes a Service. The information within can be used
+ * to bind to the service.
+ */
+ public static class ServiceInfo<V> {
+ public final V type;
+ public final ComponentInfo componentInfo;
+ public final ComponentName componentName;
+ 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) {
+ int 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);
+ }
+ }
+
+ @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();
+ return pm.queryIntentServicesAsUser(new Intent(mInterfaceName),
+ PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ 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 00000000..df795445
--- /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 00000000..79931670
--- /dev/null
+++ b/android/content/pm/ResolveInfo.java
@@ -0,0 +1,479 @@
+/*
+ * 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.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
+ * &lt;intent&gt; tags.
+ */
+public class ResolveInfo implements Parcelable {
+ private static final String TAG = "ResolveInfo";
+
+ /**
+ * 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;
+
+ /** @removed */
+ @Deprecated
+ public boolean instantAppAvailable;
+
+ /**
+ * 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
+ */
+ 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?
+ */
+ public boolean system;
+
+ /**
+ * @hide Does the associated IntentFilter comes from a Browser ?
+ */
+ public boolean handleAllWebDataURI;
+
+ /** {@hide} */
+ 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;
+ }
+
+ /**
+ * 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(UserHandle.myUserId()));
+ }
+ 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);
+ }
+ }
+
+ 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;
+ instantAppAvailable = 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.writeString(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 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.readString();
+ targetUserId = source.readInt();
+ system = source.readInt() != 0;
+ noResourceId = source.readInt() != 0;
+ iconResourceId = source.readInt();
+ handleAllWebDataURI = source.readInt() != 0;
+ instantAppAvailable = 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 00000000..025c0fe3
--- /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 00000000..91f884cc
--- /dev/null
+++ b/android/content/pm/ServiceInfo.java
@@ -0,0 +1,132 @@
+/*
+ * 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.Printer;
+
+/**
+ * Information you can retrieve about a particular application
+ * service. This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;service&gt; 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} 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;
+
+ public ServiceInfo() {
+ }
+
+ public ServiceInfo(ServiceInfo orig) {
+ super(orig);
+ permission = orig.permission;
+ flags = orig.flags;
+ }
+
+ 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.writeString(permission);
+ dest.writeInt(flags);
+ }
+
+ public static final 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.readString();
+ flags = source.readInt();
+ }
+}
diff --git a/android/content/pm/SharedLibraryInfo.java b/android/content/pm/SharedLibraryInfo.java
new file mode 100644
index 00000000..7d301a31
--- /dev/null
+++ b/android/content/pm/SharedLibraryInfo.java
@@ -0,0 +1,228 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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 - updatable non backwards-compatible emulating static linking.
+ */
+public final class SharedLibraryInfo implements Parcelable {
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ 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 mName;
+
+ // TODO: Make long when we change the paltform to use longs
+ private final int mVersion;
+ private final @Type int mType;
+ private final VersionedPackage mDeclaringPackage;
+ private final List<VersionedPackage> mDependentPackages;
+
+ /**
+ * Creates a new instance.
+ *
+ * @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 name, int version, int type,
+ VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages) {
+ mName = name;
+ mVersion = version;
+ mType = type;
+ mDeclaringPackage = declaringPackage;
+ mDependentPackages = dependentPackages;
+ }
+
+ private SharedLibraryInfo(Parcel parcel) {
+ this(parcel.readString(), parcel.readInt(), parcel.readInt(),
+ parcel.readParcelable(null), parcel.readArrayList(null));
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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) int getVersion() {
+ 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.writeString(mName);
+ parcel.writeInt(mVersion);
+ parcel.writeInt(mType);
+ parcel.writeParcelable(mDeclaringPackage, flags);
+ parcel.writeList(mDependentPackages);
+ }
+
+ 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 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 00000000..d3a3560c
--- /dev/null
+++ b/android/content/pm/ShortcutInfo.java
@@ -0,0 +1,1941 @@
+/*
+ * 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.UserIdInt;
+import android.app.TaskStackBuilder;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+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.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 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.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;
+
+ /** @hide */
+ @IntDef(flag = true,
+ 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,
+ })
+ @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_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;
+
+ /** @hide */
+ public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT
+ | CLONE_REMOVE_RES_NAMES;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ CLONE_REMOVE_ICON,
+ CLONE_REMOVE_INTENT,
+ CLONE_REMOVE_NON_KEY_INFO,
+ CLONE_REMOVE_RES_NAMES,
+ CLONE_REMOVE_FOR_CREATOR,
+ CLONE_REMOVE_FOR_LAUNCHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CloneFlags {}
+
+ /**
+ * 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;
+
+ 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.
+ @Nullable
+ private String mBitmapPath;
+
+ private final int mUserId;
+
+ 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();
+ mRank = b.mRank;
+ mExtras = b.mExtras;
+ 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;
+ }
+
+ /**
+ * 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) {
+ Preconditions.checkNotNull(mActivity, "Activity must be provided");
+ }
+ if (mTitle == null && mTitleResId == 0) {
+ throw new IllegalArgumentException("Short label must be provided");
+ }
+ Preconditions.checkNotNull(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;
+
+ // 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;
+ }
+
+ 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_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
+ */
+ public void ensureUpdatableWith(ShortcutInfo source) {
+ 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");
+ 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);
+
+ if (source.mActivity != null) {
+ mActivity = source.mActivity;
+ }
+
+ if (source.mIcon != null) {
+ mIcon = source.mIcon;
+
+ mIconResId = 0;
+ mIconResName = null;
+ mBitmapPath = 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.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;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static Icon validateIcon(Icon icon) {
+ switch (icon.getType()) {
+ case Icon.TYPE_RESOURCE:
+ case Icon.TYPE_BITMAP:
+ case Icon.TYPE_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 int mRank = RANK_NOT_SET;
+
+ private PersistableBundle mExtras;
+
+ /**
+ * 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 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&mdash;ones that define the {@link Intent#ACTION_MAIN}
+ * and {@link Intent#CATEGORY_LAUNCHER} intent filters&mdash;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 = Preconditions.checkNotNull(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) {
+ Preconditions.checkNotNull(intents, "intents cannot be null");
+ Preconditions.checkNotNull(intents.length, "intents cannot be empty");
+ for (Intent intent : intents) {
+ Preconditions.checkNotNull(intent, "intents cannot contain null");
+ Preconditions.checkNotNull(intent.getAction(), "intent's action must be set");
+ }
+ // Make sure always clone incoming intents.
+ mIntents = cloneIntents(intents);
+ 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;
+ }
+
+ /**
+ * 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
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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>Because static shortcuts and dynamic shortcuts have overlapping ranks,
+ * when a launcher app shows shortcuts for an activity, it should first show
+ * the static shortcuts, followed by the dynamic shortcuts. Within each of those categories,
+ * shortcuts should be sorted by rank in ascending order.
+ *
+ * <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);
+ }
+
+ /** 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 but neither static nor dynamic.
+ * @hide
+ */
+ public boolean isFloating() {
+ return isPinned() && !(isDynamic() || isManifestShortcut());
+ }
+
+ /** @hide */
+ public boolean isOriginallyFromManifest() {
+ return hasFlags(FLAG_IMMUTABLE);
+ }
+
+ /**
+ * 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);
+ }
+
+ /** @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);
+ }
+
+ /** @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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 {
+ Preconditions.checkNotNull(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.readString();
+ mPackageName = source.readString();
+ mActivity = source.readParcelable(cl);
+ mFlags = source.readInt();
+ mIconResId = source.readInt();
+ mLastChangedTimestamp = source.readLong();
+
+ 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.readString();
+
+ mIconResName = source.readString();
+ mTitleResName = source.readString();
+ mTextResName = source.readString();
+ mDisabledMessageResName = source.readString();
+
+ int N = source.readInt();
+ if (N == 0) {
+ mCategories = null;
+ } else {
+ mCategories = new ArraySet<>(N);
+ for (int i = 0; i < N; i++) {
+ mCategories.add(source.readString().intern());
+ }
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mUserId);
+ dest.writeString(mId);
+ dest.writeString(mPackageName);
+ dest.writeParcelable(mActivity, flags);
+ dest.writeInt(mFlags);
+ dest.writeInt(mIconResId);
+ dest.writeLong(mLastChangedTimestamp);
+
+ 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.writeString(mBitmapPath);
+
+ dest.writeString(mIconResName);
+ dest.writeString(mTitleResName);
+ dest.writeString(mTextResName);
+ dest.writeString(mDisabledMessageResName);
+
+ if (mCategories != null) {
+ final int N = mCategories.size();
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ dest.writeString(mCategories.valueAt(i));
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ public static final 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);
+ }
+
+ /** @hide */
+ public String toInsecureString() {
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+ }
+
+ private String toStringInner(boolean secure, boolean includeInternalData) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("ShortcutInfo {");
+
+ sb.append("id=");
+ sb.append(secure ? "***" : mId);
+
+ sb.append(", flags=0x");
+ sb.append(Integer.toHexString(mFlags));
+ sb.append(" [");
+ if (!isEnabled()) {
+ sb.append("X");
+ }
+ if (isImmutable()) {
+ sb.append("Im");
+ }
+ if (isManifestShortcut()) {
+ sb.append("M");
+ }
+ if (isDynamic()) {
+ sb.append("D");
+ }
+ if (isPinned()) {
+ sb.append("P");
+ }
+ if (hasIconFile()) {
+ sb.append("If");
+ }
+ if (isIconPendingSave()) {
+ sb.append("^");
+ }
+ if (hasIconResource()) {
+ sb.append("Ir");
+ }
+ if (hasKeyFieldsOnly()) {
+ sb.append("K");
+ }
+ if (hasStringResourcesResolved()) {
+ sb.append("Sr");
+ }
+ if (isReturnedByServer()) {
+ sb.append("V");
+ }
+ sb.append("]");
+
+ sb.append(", packageName=");
+ sb.append(mPackageName);
+
+ sb.append(", activity=");
+ sb.append(mActivity);
+
+ sb.append(", shortLabel=");
+ sb.append(secure ? "***" : mTitle);
+ sb.append(", resId=");
+ sb.append(mTitleResId);
+ sb.append("[");
+ sb.append(mTitleResName);
+ sb.append("]");
+
+ sb.append(", longLabel=");
+ sb.append(secure ? "***" : mText);
+ sb.append(", resId=");
+ sb.append(mTextResId);
+ sb.append("[");
+ sb.append(mTextResName);
+ sb.append("]");
+
+ sb.append(", disabledMessage=");
+ sb.append(secure ? "***" : mDisabledMessage);
+ sb.append(", resId=");
+ sb.append(mDisabledMessageResId);
+ sb.append("[");
+ sb.append(mDisabledMessageResName);
+ sb.append("]");
+
+ sb.append(", categories=");
+ sb.append(mCategories);
+
+ sb.append(", icon=");
+ sb.append(mIcon);
+
+ sb.append(", rank=");
+ sb.append(mRank);
+
+ sb.append(", timestamp=");
+ sb.append(mLastChangedTimestamp);
+
+ 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("]");
+ }
+ }
+
+ sb.append(", extras=");
+ sb.append(mExtras);
+
+ if (includeInternalData) {
+
+ sb.append(", iconRes=");
+ sb.append(mIconResId);
+ sb.append("[");
+ sb.append(mIconResName);
+ sb.append("]");
+
+ sb.append(", bitmapPath=");
+ sb.append(mBitmapPath);
+ }
+
+ 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) {
+ 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;
+ }
+}
diff --git a/android/content/pm/ShortcutManager.java b/android/content/pm/ShortcutManager.java
new file mode 100644
index 00000000..61b0eb0b
--- /dev/null
+++ b/android/content/pm/ShortcutManager.java
@@ -0,0 +1,738 @@
+/*
+ * 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.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.os.Build.VERSION_CODES;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users with quick
+ * access to activities other than an app's main activity in the currently-active launcher, provided
+ * that the launcher supports app shortcuts. For example, an email app may publish the "compose new
+ * email" action, which will directly open the compose activity. The {@link ShortcutInfo} class
+ * contains information about each of the shortcuts themselves.
+ *
+ * <p>This page discusses the implementation details of the <code>ShortcutManager</code> class. For
+ * guidance on performing operations on app shortcuts within your app, see the
+ * <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide.
+ *
+ * <h3>Shortcut characteristics</h3>
+ *
+ * This section describes in-depth details about each shortcut type's usage and availability.
+ *
+ * <p class="note"><b>Important security note:</b> All shortcut information is stored in
+ * <a href="/training/articles/direct-boot.html">credential encrypted storage</a>, so your app
+ * cannot access a user's shortcuts until after they've unlocked the device.
+ *
+ * <h4>Static and dynamic shortcuts</h4>
+ *
+ * <p>Static shortcuts and dynamic shortcuts are shown in a supported launcher when the user
+ * performs a specific gesture. On currently-supported launchers, the gesture is a long-press on the
+ * app's launcher icon, but the actual gesture may be different on other launcher apps.
+ *
+ * <p>The {@link LauncherApps} class provides APIs for launcher apps to access shortcuts.
+ *
+ * <h4>Pinned shortcuts</h4>
+ *
+ * <p>Because pinned shortcuts appear in the launcher itself, they're always visible. A pinned
+ * shortcut is removed from the launcher only in the following situations:
+ * <ul>
+ * <li>The user removes it.
+ * <li>The publisher app associated with the shortcut is uninstalled.
+ * <li>The user performs the clear data action on the publisher app from the device's
+ * <b>Settings</b> app.
+ * </ul>
+ *
+ * <p>Because the system performs
+ * <a href="/guide/topics/ui/shortcuts.html#backup-and-restore">backup and restore</a> on pinned
+ * shortcuts automatically, these shortcuts' IDs should contain either stable, constant strings or
+ * server-side identifiers, rather than identifiers generated locally that might not make sense on
+ * other devices.
+ *
+ * <h3>Shortcut display order</h3>
+ *
+ * <p>When the launcher displays an app's shortcuts, they should appear in the following order:
+ *
+ * <ul>
+ * <li>Static shortcuts (if {@link ShortcutInfo#isDeclaredInManifest()} is {@code true}),
+ * and then show dynamic shortcuts (if {@link ShortcutInfo#isDynamic()} is {@code true}).
+ * <li>Within each shortcut type (static and dynamic), sort the shortcuts in order of increasing
+ * rank according to {@link ShortcutInfo#getRank()}.
+ * </ul>
+ *
+ * <p>Shortcut ranks are non-negative, sequential integers that determine the order in which
+ * shortcuts appear, assuming that the shortcuts are all in the same category. You can update ranks
+ * of existing shortcuts when you call {@link #updateShortcuts(List)},
+ * {@link #addDynamicShortcuts(List)}, or {@link #setDynamicShortcuts(List)}.
+ *
+ * <p class="note"><b>Note:</b> Ranks are auto-adjusted so that they're unique for each type of
+ * shortcut (static or dynamic). For example, if there are 3 dynamic shortcuts with ranks 0, 1 and
+ * 2, adding another dynamic shortcut with a rank of 1 represents a request to place this shortcut
+ * at the second position. In response, the third and fourth shortcuts move closer to the bottom of
+ * the shortcut list, with their ranks changing to 2 and 3, respectively.
+ *
+ * <h3>Options for static shortcuts</h3>
+ *
+ * The following list includes descriptions for the different attributes within a static shortcut:
+ * <dl>
+ * <dt>{@code android:shortcutId}</dt>
+ * <dd>Mandatory shortcut ID.
+ * <p>
+ * This must be a string literal.
+ * A resource string, such as <code>@string/foo</code>, cannot be used.
+ * </dd>
+ *
+ * <dt>{@code android:enabled}</dt>
+ * <dd>Default is {@code true}. Can be set to {@code false} in order
+ * to disable a static shortcut that was published in a previous version and set a custom
+ * disabled message. If a custom disabled message is not needed, then a static shortcut can
+ * be simply removed from the XML file rather than keeping it with {@code enabled="false"}.</dd>
+ *
+ * <dt>{@code android:icon}</dt>
+ * <dd>Shortcut icon.</dd>
+ *
+ * <dt>{@code android:shortcutShortLabel}</dt>
+ * <dd>Mandatory shortcut short label.
+ * See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.
+ * <p>
+ * This must be a resource string, such as <code>@string/shortcut_label</code>.
+ * </dd>
+ *
+ * <dt>{@code android:shortcutLongLabel}</dt>
+ * <dd>Shortcut long label.
+ * See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.
+ * <p>
+ * This must be a resource string, such as <code>@string/shortcut_long_label</code>.
+ * </dd>
+ *
+ * <dt>{@code android:shortcutDisabledMessage}</dt>
+ * <dd>When {@code android:enabled} is set to
+ * {@code false}, this attribute is used to display a custom disabled message.
+ * <p>
+ * This must be a resource string, such as <code>@string/shortcut_disabled_message</code>.
+ * </dd>
+ *
+ * <dt>{@code intent}</dt>
+ * <dd>Intent to launch when the user selects the shortcut.
+ * {@code android:action} is mandatory.
+ * See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the
+ * other supported tags.
+ * <p>You can provide multiple intents for a single shortcut so that the last defined activity is
+ * launched with the other activities in the
+ * <a href="/guide/components/tasks-and-back-stack.html">back stack</a>. See
+ * {@link android.app.TaskStackBuilder} for details.
+ * <p><b>Note:</b> String resources may not be used within an {@code <intent>} element.
+ * </dd>
+ * <dt>{@code categories}</dt>
+ * <dd>Specify shortcut categories. Currently only
+ * {@link ShortcutInfo#SHORTCUT_CATEGORY_CONVERSATION} is defined in the framework.
+ * </dd>
+ * </dl>
+ *
+ * <h3>Updating shortcuts</h3>
+ *
+ * <p>As an example, suppose {@link #getMaxShortcutCountPerActivity()} is 5:
+ * <ol>
+ * <li>A chat app publishes 5 dynamic shortcuts for the 5 most recent
+ * conversations (c1, c2, ..., c5).
+ *
+ * <li>The user pins all 5 of the shortcuts.
+ *
+ * <li>Later, the user has started 3 additional conversations (c6, c7, and c8),
+ * so the publisher app
+ * re-publishes its dynamic shortcuts. The new dynamic shortcut list is:
+ * c4, c5, ..., c8.
+ * The publisher app has to remove c1, c2, and c3 because it can't have more than
+ * 5 dynamic shortcuts.
+ *
+ * <li>However, even though c1, c2, and c3 are no longer dynamic shortcuts, the pinned
+ * shortcuts for these conversations are still available and launchable.
+ *
+ * <li>At this point, the user can access a total of 8 shortcuts that link to activities in
+ * the publisher app, including the 3 pinned shortcuts, even though an app can have at most 5
+ * dynamic shortcuts.
+ *
+ * <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
+ * 8 shortcuts, when, for example, the chat peers' icons have changed.
+ * <p>The {@link #addDynamicShortcuts(List)} and {@link #setDynamicShortcuts(List)} methods
+ * can also be used to update existing shortcuts with the same IDs, but they <b>cannot</b> be
+ * used for updating non-dynamic, pinned shortcuts because these 2 methods try to convert the
+ * given lists of shortcuts to dynamic shortcuts.
+ * </ol>
+ *
+ * <h3>Shortcut intents</h3>
+ *
+ * <p>
+ * Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags.
+ * Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other
+ * flags; otherwise, if the app is already running, the app is simply brought to
+ * the foreground, and the target activity may not appear.
+ *
+ * <p>Static shortcuts <b>cannot</b> have custom intent flags.
+ * The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ * and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the app is already running, all
+ * the existing activities in your app will be destroyed when a static shortcut is launched.
+ * If this behavior is not desirable, you can use a <em>trampoline activity</em>, or an invisible
+ * activity that starts another activity in {@link Activity#onCreate}, then calls
+ * {@link Activity#finish()}:
+ * <ol>
+ * <li>In the <code>AndroidManifest.xml</code> file, the trampoline activity should include the
+ * attribute assignment {@code android:taskAffinity=""}.
+ * <li>In the shortcuts resource file, the intent within the static shortcut should point at
+ * the trampoline activity.
+ * </ol>
+ *
+ * <h3>Handling system locale changes</h3>
+ *
+ * <p>Apps should update dynamic and pinned shortcuts when the system locale changes using the
+ * {@link Intent#ACTION_LOCALE_CHANGED} broadcast. When the system locale changes,
+ * <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is reset, so even
+ * background apps can add and update dynamic shortcuts until the rate limit is reached again.
+ *
+ * <h3>Shortcut limits</h3>
+ *
+ * <p>Only main activities&mdash;activities that handle the {@code MAIN} action and the
+ * {@code LAUNCHER} category&mdash;can have shortcuts. If an app has multiple main activities, you
+ * need to define the set of shortcuts for <em>each</em> activity.
+ *
+ * <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
+ * static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts that
+ * an app can create.
+ *
+ * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut,
+ * the pinned shortcut is still visible and launchable. This allows an app to have more than
+ * {@link #getMaxShortcutCountPerActivity()} number of shortcuts.
+ *
+ * <h4>Rate limiting</h4>
+ *
+ * <p>When <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is active,
+ * {@link #isRateLimitingActive()} returns {@code true}.
+ *
+ * <p>Rate limiting is reset upon certain events, so even background apps can call these APIs until
+ * the rate limit is reached again. These events include the following:
+ * <ul>
+ * <li>An app comes to the foreground.
+ * <li>The system locale changes.
+ * <li>The user performs the <strong>inline reply</strong> action on a notification.
+ * </ul>
+ */
+@SystemService(Context.SHORTCUT_SERVICE)
+public class ShortcutManager {
+ private static final String TAG = "ShortcutManager";
+
+ private final Context mContext;
+ 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.getDynamicShortcuts(mContext.getPackageName(), 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.getManifestShortcuts(mContext.getPackageName(), 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();
+ }
+ }
+
+ /**
+ * 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.getPinnedShortcuts(mContext.getPackageName(), 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, see the Javadoc for the {@link ShortcutManager}
+ * class.
+ *
+ * @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, see the Javadoc for the {@link ShortcutManager} class.
+ *
+ * @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>See the class level javadoc for details.
+ *
+ * @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, see the Javadoc for the {@link ShortcutManager} class
+ *
+ * <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 the overview documentation for the {@link ShortcutManager} class.
+ *
+ * @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 UserHandle.myUserId();
+ }
+}
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
new file mode 100644
index 00000000..7b7d8ae4
--- /dev/null
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -0,0 +1,80 @@
+/*
+ * 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.IntentSender;
+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 ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
+ int userId);
+
+ 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);
+
+ public abstract void addListener(@NonNull ShortcutChangeListener listener);
+
+ 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);
+
+ 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);
+}
diff --git a/android/content/pm/Signature.java b/android/content/pm/Signature.java
new file mode 100644
index 00000000..fdc54aed
--- /dev/null
+++ b/android/content/pm/Signature.java
@@ -0,0 +1,306 @@
+/*
+ * 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;
+
+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;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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
+ */
+ 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 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);
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/android/content/pm/StringParceledListSlice.java b/android/content/pm/StringParceledListSlice.java
new file mode 100644
index 00000000..95407449
--- /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/UserInfo.java b/android/content/pm/UserInfo.java
new file mode 100644
index 00000000..f34b590e
--- /dev/null
+++ b/android/content/pm/UserInfo.java
@@ -0,0 +1,295 @@
+/*
+ * 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 android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * Per-user information.
+ * @hide
+ */
+public class UserInfo implements Parcelable {
+
+ /** 16 bits for user type */
+ public static final int FLAG_MASK_USER_TYPE = 0x0000FFFF;
+
+ /**
+ * *************************** 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public static final int FLAG_DEMO = 0x00000200;
+
+ public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
+
+ public int id;
+ public int serialNumber;
+ public String name;
+ public String iconPath;
+ public int flags;
+ public long creationTime;
+ public long lastLoggedInTime;
+ public String lastLoggedInFingerprint;
+ /**
+ * 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}.
+ */
+ public int profileGroupId;
+ public int restrictedProfileParentId;
+ /** Which profile badge color/label to use. */
+ public int profileBadge;
+
+ /** User is only partially created. */
+ public boolean partial;
+ public boolean guestToRemove;
+
+ public UserInfo(int id, String name, int flags) {
+ this(id, name, null, flags);
+ }
+
+ public UserInfo(int id, String name, String iconPath, int flags) {
+ this.id = id;
+ this.name = name;
+ this.flags = flags;
+ this.iconPath = iconPath;
+ this.profileGroupId = NO_PROFILE_GROUP_ID;
+ this.restrictedProfileParentId = NO_PROFILE_GROUP_ID;
+ }
+
+ public boolean isPrimary() {
+ return (flags & FLAG_PRIMARY) == FLAG_PRIMARY;
+ }
+
+ public boolean isAdmin() {
+ return (flags & FLAG_ADMIN) == FLAG_ADMIN;
+ }
+
+ public boolean isGuest() {
+ return (flags & FLAG_GUEST) == FLAG_GUEST;
+ }
+
+ public boolean isRestricted() {
+ return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED;
+ }
+
+ public boolean isManagedProfile() {
+ return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE;
+ }
+
+ 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 (flags & FLAG_DEMO) == FLAG_DEMO;
+ }
+
+ /**
+ * 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;
+ }
+ return !isManagedProfile();
+ }
+
+ /**
+ * @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.isSplitSystemUser();
+ return (!hideSystemUser || id != UserHandle.USER_SYSTEM) && supportsSwitchTo();
+ }
+
+ /* @hide */
+ public boolean canHaveProfile() {
+ if (isManagedProfile() || isGuest() || isRestricted()) {
+ return false;
+ }
+ if (UserManager.isSplitSystemUser()) {
+ return id != UserHandle.USER_SYSTEM;
+ } else {
+ return id == UserHandle.USER_SYSTEM;
+ }
+ }
+
+ public UserInfo() {
+ }
+
+ public UserInfo(UserInfo orig) {
+ name = orig.name;
+ iconPath = orig.iconPath;
+ id = orig.id;
+ flags = orig.flags;
+ serialNumber = orig.serialNumber;
+ creationTime = orig.creationTime;
+ lastLoggedInTime = orig.lastLoggedInTime;
+ lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
+ partial = orig.partial;
+ profileGroupId = orig.profileGroupId;
+ restrictedProfileParentId = orig.restrictedProfileParentId;
+ guestToRemove = orig.guestToRemove;
+ profileBadge = orig.profileBadge;
+ }
+
+ public UserHandle getUserHandle() {
+ return new UserHandle(id);
+ }
+
+ @Override
+ public String toString() {
+ return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(id);
+ dest.writeString(name);
+ dest.writeString(iconPath);
+ dest.writeInt(flags);
+ dest.writeInt(serialNumber);
+ dest.writeLong(creationTime);
+ dest.writeLong(lastLoggedInTime);
+ dest.writeString(lastLoggedInFingerprint);
+ dest.writeInt(partial ? 1 : 0);
+ dest.writeInt(profileGroupId);
+ dest.writeInt(guestToRemove ? 1 : 0);
+ dest.writeInt(restrictedProfileParentId);
+ dest.writeInt(profileBadge);
+ }
+
+ public static final 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.readString();
+ iconPath = source.readString();
+ flags = source.readInt();
+ serialNumber = source.readInt();
+ creationTime = source.readLong();
+ lastLoggedInTime = source.readLong();
+ lastLoggedInFingerprint = source.readString();
+ partial = source.readInt() != 0;
+ profileGroupId = source.readInt();
+ guestToRemove = source.readInt() != 0;
+ restrictedProfileParentId = source.readInt();
+ profileBadge = source.readInt();
+ }
+}
diff --git a/android/content/pm/VerificationParams.java b/android/content/pm/VerificationParams.java
new file mode 100644
index 00000000..f90d295b
--- /dev/null
+++ b/android/content/pm/VerificationParams.java
@@ -0,0 +1,211 @@
+/*
+ * 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.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents verification parameters used to verify packages to be installed.
+ *
+ * @deprecated callers should migrate to {@link PackageInstaller}.
+ * @hide
+ */
+@Deprecated
+public class VerificationParams implements Parcelable {
+ /** A constant used to indicate that a uid value is not present. */
+ public static final int NO_UID = -1;
+
+ /** What we print out first when toString() is called. */
+ private static final String TO_STRING_PREFIX = "VerificationParams{";
+
+ /** The location of the supplementary verification file. */
+ private final Uri mVerificationURI;
+
+ /** URI referencing where the package was downloaded from. */
+ private final Uri mOriginatingURI;
+
+ /** HTTP referrer URI associated with the originatingURI. */
+ private final Uri mReferrer;
+
+ /** UID of the application that the install request originated from. */
+ private final int mOriginatingUid;
+
+ /** UID of application requesting the install */
+ private int mInstallerUid;
+
+ /**
+ * Creates verification specifications for installing with application verification.
+ *
+ * @param verificationURI The location of the supplementary verification
+ * file. This can be a 'file:' or a 'content:' URI. May be {@code null}.
+ * @param originatingURI URI referencing where the package was downloaded
+ * from. May be {@code null}.
+ * @param referrer HTTP referrer URI associated with the originatingURI.
+ * May be {@code null}.
+ * @param originatingUid UID of the application that the install request originated
+ * from, or NO_UID if not present
+ */
+ public VerificationParams(Uri verificationURI, Uri originatingURI, Uri referrer,
+ int originatingUid) {
+ mVerificationURI = verificationURI;
+ mOriginatingURI = originatingURI;
+ mReferrer = referrer;
+ mOriginatingUid = originatingUid;
+ mInstallerUid = NO_UID;
+ }
+
+ public Uri getVerificationURI() {
+ return mVerificationURI;
+ }
+
+ public Uri getOriginatingURI() {
+ return mOriginatingURI;
+ }
+
+ public Uri getReferrer() {
+ return mReferrer;
+ }
+
+ /** return NO_UID if not available */
+ public int getOriginatingUid() {
+ return mOriginatingUid;
+ }
+
+ /** @return NO_UID when not set */
+ public int getInstallerUid() {
+ return mInstallerUid;
+ }
+
+ public void setInstallerUid(int uid) {
+ mInstallerUid = uid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof VerificationParams)) {
+ return false;
+ }
+
+ final VerificationParams other = (VerificationParams) o;
+
+ if (mVerificationURI == null) {
+ if (other.mVerificationURI != null) {
+ return false;
+ }
+ } else if (!mVerificationURI.equals(other.mVerificationURI)) {
+ return false;
+ }
+
+ if (mOriginatingURI == null) {
+ if (other.mOriginatingURI != null) {
+ return false;
+ }
+ } else if (!mOriginatingURI.equals(other.mOriginatingURI)) {
+ return false;
+ }
+
+ if (mReferrer == null) {
+ if (other.mReferrer != null) {
+ return false;
+ }
+ } else if (!mReferrer.equals(other.mReferrer)) {
+ return false;
+ }
+
+ if (mOriginatingUid != other.mOriginatingUid) {
+ return false;
+ }
+
+ if (mInstallerUid != other.mInstallerUid) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+
+ hash += 5 * (mVerificationURI == null ? 1 : mVerificationURI.hashCode());
+ hash += 7 * (mOriginatingURI == null ? 1 : mOriginatingURI.hashCode());
+ hash += 11 * (mReferrer == null ? 1 : mReferrer.hashCode());
+ hash += 13 * mOriginatingUid;
+ hash += 17 * mInstallerUid;
+
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX);
+
+ sb.append("mVerificationURI=");
+ sb.append(mVerificationURI.toString());
+ sb.append(",mOriginatingURI=");
+ sb.append(mOriginatingURI.toString());
+ sb.append(",mReferrer=");
+ sb.append(mReferrer.toString());
+ sb.append(",mOriginatingUid=");
+ sb.append(mOriginatingUid);
+ sb.append(",mInstallerUid=");
+ sb.append(mInstallerUid);
+ sb.append('}');
+
+ return sb.toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mVerificationURI, 0);
+ dest.writeParcelable(mOriginatingURI, 0);
+ dest.writeParcelable(mReferrer, 0);
+ dest.writeInt(mOriginatingUid);
+ dest.writeInt(mInstallerUid);
+ }
+
+
+ private VerificationParams(Parcel source) {
+ mVerificationURI = source.readParcelable(Uri.class.getClassLoader());
+ mOriginatingURI = source.readParcelable(Uri.class.getClassLoader());
+ mReferrer = source.readParcelable(Uri.class.getClassLoader());
+ mOriginatingUid = source.readInt();
+ mInstallerUid = source.readInt();
+ }
+
+ public static final Parcelable.Creator<VerificationParams> CREATOR =
+ new Parcelable.Creator<VerificationParams>() {
+ public VerificationParams createFromParcel(Parcel source) {
+ return new VerificationParams(source);
+ }
+
+ public VerificationParams[] newArray(int size) {
+ return new VerificationParams[size];
+ }
+ };
+}
diff --git a/android/content/pm/VerifierDeviceIdentity.java b/android/content/pm/VerifierDeviceIdentity.java
new file mode 100644
index 00000000..a8cdb6ae
--- /dev/null
+++ b/android/content/pm/VerifierDeviceIdentity.java
@@ -0,0 +1,240 @@
+/*
+ * 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 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
+ */
+ 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 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 00000000..0a2b2832
--- /dev/null
+++ b/android/content/pm/VerifierInfo.java
@@ -0,0 +1,83 @@
+/*
+ * 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 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.
+ */
+ 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 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 00000000..29c5efe7
--- /dev/null
+++ b/android/content/pm/VersionedPackage.java
@@ -0,0 +1,100 @@
+/*
+ * 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 int 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;
+ }
+
+ private VersionedPackage(Parcel parcel) {
+ mPackageName = parcel.readString();
+ mVersionCode = parcel.readInt();
+ }
+
+ /**
+ * Gets the package name.
+ *
+ * @return The package name.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Gets the version code.
+ *
+ * @return The version code.
+ */
+ public @VersionCode int getVersionCode() {
+ return mVersionCode;
+ }
+
+ @Override
+ public String toString() {
+ return "VersionedPackage[" + mPackageName + "/" + mVersionCode + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeInt(mVersionCode);
+ }
+
+ public static final 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 00000000..20cb61c2
--- /dev/null
+++ b/android/content/pm/XmlSerializerAndParser.java
@@ -0,0 +1,29 @@
+/*
+ * 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 org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public interface XmlSerializerAndParser<T> {
+ void writeAsXml(T item, XmlSerializer out) throws IOException;
+ T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException;
+}
diff --git a/android/content/pm/permission/RuntimePermissionPresentationInfo.java b/android/content/pm/permission/RuntimePermissionPresentationInfo.java
new file mode 100644
index 00000000..352e8ad1
--- /dev/null
+++ b/android/content/pm/permission/RuntimePermissionPresentationInfo.java
@@ -0,0 +1,110 @@
+/*
+ * 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
+ */
+@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 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/RuntimePermissionPresenter.java b/android/content/pm/permission/RuntimePermissionPresenter.java
new file mode 100644
index 00000000..02d0a6d8
--- /dev/null
+++ b/android/content/pm/permission/RuntimePermissionPresenter.java
@@ -0,0 +1,289 @@
+/*
+ * 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.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.permissionpresenterservice.RuntimePermissionPresenterService;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class provides information about runtime permissions for a specific
+ * app or all apps. This information is dedicated for presentation purposes
+ * and does not necessarily reflect the individual permissions requested/
+ * granted to an app as the platform may be grouping permissions to improve
+ * presentation and help the user make an informed choice. For example, all
+ * runtime permissions in the same permission group may be presented as a
+ * single permission in the UI.
+ *
+ * @hide
+ */
+public final class RuntimePermissionPresenter {
+ private static final String TAG = "RuntimePermPresenter";
+
+ /**
+ * The key for retrieving the result from the returned bundle.
+ *
+ * @hide
+ */
+ public static final String KEY_RESULT =
+ "android.content.pm.permission.RuntimePermissionPresenter.key.result";
+
+ /**
+ * Listener for delivering a result.
+ */
+ public static abstract class OnResultCallback {
+ /**
+ * The result for {@link #getAppPermissions(String, OnResultCallback, Handler)}.
+ * @param permissions The permissions list.
+ */
+ public void onGetAppPermissions(@NonNull
+ List<RuntimePermissionPresentationInfo> permissions) {
+ /* do nothing - stub */
+ }
+ }
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static RuntimePermissionPresenter sInstance;
+
+ private final RemoteService mRemoteService;
+
+ /**
+ * Gets the singleton runtime permission presenter.
+ *
+ * @param context Context for accessing resources.
+ * @return The singleton instance.
+ */
+ public static RuntimePermissionPresenter getInstance(@NonNull Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new RuntimePermissionPresenter(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+ }
+
+ private RuntimePermissionPresenter(Context context) {
+ mRemoteService = new RemoteService(context);
+ }
+
+ /**
+ * Gets the runtime permissions for an app.
+ *
+ * @param packageName The package for which to query.
+ * @param callback Callback to receive the result.
+ * @param handler Handler on which to invoke the callback.
+ */
+ public void getAppPermissions(@NonNull String packageName,
+ @NonNull OnResultCallback callback, @Nullable Handler handler) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = packageName;
+ args.arg2 = callback;
+ args.arg3 = handler;
+ Message message = mRemoteService.obtainMessage(
+ RemoteService.MSG_GET_APP_PERMISSIONS, args);
+ mRemoteService.processMessage(message);
+ }
+
+ /**
+ * Revoke the permission {@code permissionName} for app {@code packageName}
+ *
+ * @param packageName The package for which to revoke
+ * @param permissionName The permission to revoke
+ */
+ public void revokeRuntimePermission(String packageName, String permissionName) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = packageName;
+ args.arg2 = permissionName;
+
+ Message message = mRemoteService.obtainMessage(
+ RemoteService.MSG_REVOKE_APP_PERMISSIONS, args);
+ mRemoteService.processMessage(message);
+ }
+
+ private static final class RemoteService
+ extends Handler implements ServiceConnection {
+ private static final long UNBIND_TIMEOUT_MILLIS = 10000;
+
+ public static final int MSG_GET_APP_PERMISSIONS = 1;
+ public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
+ public static final int MSG_UNBIND = 3;
+ public static final int MSG_REVOKE_APP_PERMISSIONS = 4;
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+
+ @GuardedBy("mLock")
+ private final List<Message> mPendingWork = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private IRuntimePermissionPresenter mRemoteInstance;
+
+ @GuardedBy("mLock")
+ private boolean mBound;
+
+ public RemoteService(Context context) {
+ super(context.getMainLooper(), null, false);
+ mContext = context;
+ }
+
+ public void processMessage(Message message) {
+ synchronized (mLock) {
+ if (!mBound) {
+ Intent intent = new Intent(
+ RuntimePermissionPresenterService.SERVICE_INTERFACE);
+ intent.setPackage(mContext.getPackageManager()
+ .getPermissionControllerPackageName());
+ mBound = mContext.bindService(intent, this,
+ Context.BIND_AUTO_CREATE);
+ }
+ mPendingWork.add(message);
+ scheduleNextMessageIfNeededLocked();
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mRemoteInstance = IRuntimePermissionPresenter.Stub.asInterface(service);
+ scheduleNextMessageIfNeededLocked();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ mRemoteInstance = null;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_GET_APP_PERMISSIONS: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ final String packageName = (String) args.arg1;
+ final OnResultCallback callback = (OnResultCallback) args.arg2;
+ final Handler handler = (Handler) args.arg3;
+ args.recycle();
+ final IRuntimePermissionPresenter remoteInstance;
+ synchronized (mLock) {
+ remoteInstance = mRemoteInstance;
+ }
+ if (remoteInstance == null) {
+ return;
+ }
+ try {
+ remoteInstance.getAppPermissions(packageName,
+ new RemoteCallback(new RemoteCallback.OnResultListener() {
+ @Override
+ public void onResult(Bundle result) {
+ final List<RuntimePermissionPresentationInfo> reportedPermissions;
+ List<RuntimePermissionPresentationInfo> permissions = null;
+ if (result != null) {
+ permissions = result.getParcelableArrayList(KEY_RESULT);
+ }
+ if (permissions == null) {
+ permissions = Collections.emptyList();
+ }
+ reportedPermissions = permissions;
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onGetAppPermissions(reportedPermissions);
+ }
+ });
+ } else {
+ callback.onGetAppPermissions(reportedPermissions);
+ }
+ }
+ }, this));
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error getting app permissions", re);
+ }
+ scheduleUnbind();
+ } break;
+
+ case MSG_UNBIND: {
+ synchronized (mLock) {
+ if (mBound) {
+ mContext.unbindService(this);
+ mBound = false;
+ }
+ mRemoteInstance = null;
+ }
+ } break;
+
+ case MSG_REVOKE_APP_PERMISSIONS: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ final String packageName = (String) args.arg1;
+ final String permissionName = (String) args.arg2;
+ args.recycle();
+ final IRuntimePermissionPresenter remoteInstance;
+ synchronized (mLock) {
+ remoteInstance = mRemoteInstance;
+ }
+ if (remoteInstance == null) {
+ return;
+ }
+ try {
+ remoteInstance.revokeRuntimePermission(packageName, permissionName);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error getting app permissions", re);
+ }
+ } break;
+ }
+
+ synchronized (mLock) {
+ scheduleNextMessageIfNeededLocked();
+ }
+ }
+
+ private void scheduleNextMessageIfNeededLocked() {
+ if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) {
+ Message nextMessage = mPendingWork.remove(0);
+ sendMessage(nextMessage);
+ }
+ }
+
+ private void scheduleUnbind() {
+ removeMessages(MSG_UNBIND);
+ sendEmptyMessageDelayed(MSG_UNBIND, UNBIND_TIMEOUT_MILLIS);
+ }
+ }
+}
diff --git a/android/content/pm/split/DefaultSplitAssetLoader.java b/android/content/pm/split/DefaultSplitAssetLoader.java
new file mode 100644
index 00000000..99eb4702
--- /dev/null
+++ b/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -0,0 +1,100 @@
+/*
+ * 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_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.os.Build;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+/**
+ * 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 int mFlags;
+
+ private AssetManager mCachedAssetManager;
+
+ public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
+ mBaseCodePath = pkg.baseCodePath;
+ mSplitCodePaths = pkg.splitCodePaths;
+ mFlags = flags;
+ }
+
+ private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
+ throws PackageParser.PackageParserException {
+ if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
+ throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + apkPath);
+ }
+
+ if (assets.addAssetPath(apkPath) == 0) {
+ throw new PackageParser.PackageParserException(
+ INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
+ }
+
+ @Override
+ public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+ if (mCachedAssetManager != null) {
+ return mCachedAssetManager;
+ }
+
+ AssetManager assets = new AssetManager();
+ try {
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
+
+ if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+ for (String apkPath : mSplitCodePaths) {
+ loadApkIntoAssetManager(assets, apkPath, mFlags);
+ }
+ }
+
+ mCachedAssetManager = assets;
+ assets = null;
+ return mCachedAssetManager;
+ } finally {
+ if (assets != null) {
+ IoUtils.closeQuietly(assets);
+ }
+ }
+ }
+
+ @Override
+ public AssetManager getSplitAssetManager(int splitIdx)
+ throws PackageParser.PackageParserException {
+ return getBaseAssetManager();
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mCachedAssetManager != null) {
+ IoUtils.closeQuietly(mCachedAssetManager);
+ }
+ }
+}
diff --git a/android/content/pm/split/SplitAssetDependencyLoader.java b/android/content/pm/split/SplitAssetDependencyLoader.java
new file mode 100644
index 00000000..16023f0d
--- /dev/null
+++ b/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -0,0 +1,129 @@
+/*
+ * 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_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.os.Build;
+import android.util.SparseArray;
+
+import libcore.io.IoUtils;
+
+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<PackageParser.PackageParserException>
+ implements SplitAssetLoader {
+ private final String[] mSplitPaths;
+ private final int mFlags;
+
+ private String[][] mCachedPaths;
+ private AssetManager[] mCachedAssetManagers;
+
+ public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
+ SparseArray<int[]> dependencies, 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;
+ mCachedPaths = new String[mSplitPaths.length][];
+ mCachedAssetManagers = new AssetManager[mSplitPaths.length];
+ }
+
+ @Override
+ protected boolean isSplitCached(int splitIdx) {
+ return mCachedAssetManagers[splitIdx] != null;
+ }
+
+ private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
+ throws PackageParser.PackageParserException {
+ final AssetManager assets = new AssetManager();
+ try {
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+
+ for (String assetPath : assetPaths) {
+ if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
+ !PackageParser.isApkPath(assetPath)) {
+ throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + assetPath);
+ }
+
+ if (assets.addAssetPath(assetPath) == 0) {
+ throw new PackageParser.PackageParserException(
+ INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + assetPath);
+ }
+ }
+ return assets;
+ } catch (Throwable e) {
+ IoUtils.closeQuietly(assets);
+ throw e;
+ }
+ }
+
+ @Override
+ protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+ int parentSplitIdx) throws PackageParser.PackageParserException {
+ final ArrayList<String> assetPaths = new ArrayList<>();
+ if (parentSplitIdx >= 0) {
+ Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
+ }
+
+ assetPaths.add(mSplitPaths[splitIdx]);
+ for (int configSplitIdx : configSplitIndices) {
+ assetPaths.add(mSplitPaths[configSplitIdx]);
+ }
+ mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+ mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
+ mFlags);
+ }
+
+ @Override
+ public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+ loadDependenciesForSplit(0);
+ return mCachedAssetManagers[0];
+ }
+
+ @Override
+ public AssetManager getSplitAssetManager(int idx) throws PackageParser.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 00000000..108fb95a
--- /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 00000000..35865469
--- /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/AssetFileDescriptor.java b/android/content/res/AssetFileDescriptor.java
new file mode 100644
index 00000000..28edde02
--- /dev/null
+++ b/android/content/res/AssetFileDescriptor.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+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;
+
+ private final ParcelFileDescriptor mFd;
+ private final long mStartOffset;
+ 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
+ * ParcelFileDescritor.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
+ * ParcelFileDescritor.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 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 00000000..f0adcd6c
--- /dev/null
+++ b/android/content/res/AssetManager.java
@@ -0,0 +1,914 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration.NativeConfig;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+
+import dalvik.annotation.optimization.FastNative;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * 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 {
+ /* modes used when opening an asset */
+
+ /**
+ * 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;
+
+ private static final String TAG = "AssetManager";
+ private static final boolean localLOGV = false || false;
+
+ private static final boolean DEBUG_REFS = false;
+
+ private static final Object sSync = new Object();
+ /*package*/ static AssetManager sSystem = null;
+
+ private final TypedValue mValue = new TypedValue();
+ private final long[] mOffsets = new long[2];
+
+ // For communication with native code.
+ private long mObject;
+
+ private StringBlock mStringBlocks[] = null;
+
+ private int mNumRefs = 1;
+ private boolean mOpen = true;
+ private HashMap<Long, RuntimeException> mRefStacks;
+
+ /**
+ * Create a new AssetManager containing only the basic system assets.
+ * Applications will not generally use this method, instead retrieving the
+ * appropriate asset manager with {@link Resources#getAssets}. Not for
+ * use by applications.
+ * {@hide}
+ */
+ public AssetManager() {
+ synchronized (this) {
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(this.hashCode());
+ }
+ init(false);
+ if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+ ensureSystemAssets();
+ }
+ }
+
+ private static void ensureSystemAssets() {
+ synchronized (sSync) {
+ if (sSystem == null) {
+ AssetManager system = new AssetManager(true);
+ system.makeStringBlocks(null);
+ sSystem = system;
+ }
+ }
+ }
+
+ private AssetManager(boolean isSystem) {
+ if (DEBUG_REFS) {
+ synchronized (this) {
+ mNumRefs = 0;
+ incRefsLocked(this.hashCode());
+ }
+ }
+ init(true);
+ if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+ }
+
+ /**
+ * Return a global shared asset manager that provides access to only
+ * system assets (no application assets).
+ * {@hide}
+ */
+ public static AssetManager getSystem() {
+ ensureSystemAssets();
+ return sSystem;
+ }
+
+ /**
+ * Close this asset manager.
+ */
+ public void close() {
+ synchronized(this) {
+ //System.out.println("Release: num=" + mNumRefs
+ // + ", released=" + mReleased);
+ if (mOpen) {
+ mOpen = false;
+ decRefsLocked(this.hashCode());
+ }
+ }
+ }
+
+ /**
+ * 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}
+ */
+ @Nullable
+ final 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
+ * @return the string value, or {@code null}
+ */
+ @Nullable
+ final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+ synchronized (this) {
+ final TypedValue outValue = mValue;
+ final int block = loadResourceBagValue(resId, bagEntryId, outValue, true);
+ if (block < 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 mStringBlocks[block].get(outValue.data);
+ }
+ return outValue.coerceToString();
+ }
+ }
+
+ /**
+ * 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
+ final String[] getResourceStringArray(@ArrayRes int resId) {
+ return getArrayStringResource(resId);
+ }
+
+ /**
+ * 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
+ */
+ final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
+ boolean resolveRefs) {
+ synchronized (this) {
+ final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
+ if (block < 0) {
+ return false;
+ }
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ outValue.string = mStringBlocks[block].get(outValue.data);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Retrieve the text array associated with a particular resource
+ * identifier.
+ *
+ * @param resId the resource id of the string array
+ */
+ final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+ synchronized (this) {
+ final int[] rawInfoArray = getArrayStringInfo(resId);
+ if (rawInfoArray == null) {
+ return null;
+ }
+ final int rawInfoArrayLen = rawInfoArray.length;
+ final int infoArrayLen = rawInfoArrayLen / 2;
+ int block;
+ int index;
+ final CharSequence[] retArray = new CharSequence[infoArrayLen];
+ for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
+ block = rawInfoArray[i];
+ index = rawInfoArray[i + 1];
+ retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null;
+ }
+ return retArray;
+ }
+ }
+
+ /**
+ * 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
+ */
+ final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
+ boolean resolveRefs) {
+ final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs);
+ if (block < 0) {
+ return false;
+ }
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ final StringBlock[] blocks = ensureStringBlocks();
+ outValue.string = blocks[block].get(outValue.data);
+ }
+ return true;
+ }
+
+ /**
+ * Ensures the string blocks are loaded.
+ *
+ * @return the string blocks
+ */
+ @NonNull
+ final StringBlock[] ensureStringBlocks() {
+ synchronized (this) {
+ if (mStringBlocks == null) {
+ makeStringBlocks(sSystem.mStringBlocks);
+ }
+ return mStringBlocks;
+ }
+ }
+
+ /*package*/ final void makeStringBlocks(StringBlock[] seed) {
+ final int seedNum = (seed != null) ? seed.length : 0;
+ final int num = getStringBlockCount();
+ mStringBlocks = new StringBlock[num];
+ if (localLOGV) Log.v(TAG, "Making string blocks for " + this
+ + ": " + num);
+ for (int i=0; i<num; i++) {
+ if (i < seedNum) {
+ mStringBlocks[i] = seed[i];
+ } else {
+ mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
+ }
+ }
+ }
+
+ /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
+ synchronized (this) {
+ // Cookies map to string blocks starting at 1.
+ return mStringBlocks[cookie - 1].get(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 final InputStream open(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 final InputStream open(String fileName, int accessMode)
+ throws IOException {
+ synchronized (this) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ long asset = openAsset(fileName, accessMode);
+ if (asset != 0) {
+ AssetInputStream res = new AssetInputStream(asset);
+ incRefsLocked(res.hashCode());
+ return res;
+ }
+ }
+ throw new FileNotFoundException("Asset file: " + fileName);
+ }
+
+ public final AssetFileDescriptor openFd(String fileName)
+ throws IOException {
+ synchronized (this) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets);
+ if (pfd != null) {
+ return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
+ }
+ }
+ throw new FileNotFoundException("Asset file: " + fileName);
+ }
+
+ /**
+ * 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 native final String[] list(String path)
+ throws IOException;
+
+ /**
+ * {@hide}
+ * Open a non-asset file as an asset using ACCESS_STREAMING mode. This
+ * provides direct access to all of the files included in an application
+ * package (not only its assets). Applications should not normally use
+ * this.
+ *
+ * @see #open(String)
+ */
+ public final InputStream openNonAsset(String fileName) throws IOException {
+ return openNonAsset(0, fileName, ACCESS_STREAMING);
+ }
+
+ /**
+ * {@hide}
+ * Open a non-asset file as an asset using a specific access mode. This
+ * provides direct access to all of the files included in an application
+ * package (not only its assets). Applications should not normally use
+ * this.
+ *
+ * @see #open(String, int)
+ */
+ public final InputStream openNonAsset(String fileName, int accessMode)
+ throws IOException {
+ return openNonAsset(0, fileName, accessMode);
+ }
+
+ /**
+ * {@hide}
+ * Open a non-asset in a specified package. Not for use by applications.
+ *
+ * @param cookie Identifier of the package to be opened.
+ * @param fileName Name of the asset to retrieve.
+ */
+ public final InputStream openNonAsset(int cookie, String fileName)
+ throws IOException {
+ return openNonAsset(cookie, fileName, ACCESS_STREAMING);
+ }
+
+ /**
+ * {@hide}
+ * Open a non-asset in a specified package. Not for use by applications.
+ *
+ * @param cookie Identifier of the package to be opened.
+ * @param fileName Name of the asset to retrieve.
+ * @param accessMode Desired access mode for retrieving the data.
+ */
+ public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
+ throws IOException {
+ synchronized (this) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ long asset = openNonAssetNative(cookie, fileName, accessMode);
+ if (asset != 0) {
+ AssetInputStream res = new AssetInputStream(asset);
+ incRefsLocked(res.hashCode());
+ return res;
+ }
+ }
+ throw new FileNotFoundException("Asset absolute file: " + fileName);
+ }
+
+ public final AssetFileDescriptor openNonAssetFd(String fileName)
+ throws IOException {
+ return openNonAssetFd(0, fileName);
+ }
+
+ public final AssetFileDescriptor openNonAssetFd(int cookie,
+ String fileName) throws IOException {
+ synchronized (this) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ ParcelFileDescriptor pfd = openNonAssetFdNative(cookie,
+ fileName, mOffsets);
+ if (pfd != null) {
+ return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
+ }
+ }
+ throw new FileNotFoundException("Asset absolute file: " + fileName);
+ }
+
+ /**
+ * Retrieve a parser for a compiled XML file.
+ *
+ * @param fileName The name of the file to retrieve.
+ */
+ public final XmlResourceParser openXmlResourceParser(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 final XmlResourceParser openXmlResourceParser(int cookie,
+ String fileName) throws IOException {
+ XmlBlock block = openXmlBlockAsset(cookie, fileName);
+ XmlResourceParser rp = block.newParser();
+ block.close();
+ return rp;
+ }
+
+ /**
+ * {@hide}
+ * Retrieve a non-asset as a compiled XML file. Not for use by
+ * applications.
+ *
+ * @param fileName The name of the file to retrieve.
+ */
+ /*package*/ final XmlBlock openXmlBlockAsset(String fileName)
+ throws IOException {
+ return openXmlBlockAsset(0, fileName);
+ }
+
+ /**
+ * {@hide}
+ * Retrieve a non-asset as a compiled XML file. Not for use by
+ * applications.
+ *
+ * @param cookie Identifier of the package to be opened.
+ * @param fileName Name of the asset to retrieve.
+ */
+ /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
+ throws IOException {
+ synchronized (this) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ long xmlBlock = openXmlAssetNative(cookie, fileName);
+ if (xmlBlock != 0) {
+ XmlBlock res = new XmlBlock(this, xmlBlock);
+ incRefsLocked(res.hashCode());
+ return res;
+ }
+ }
+ throw new FileNotFoundException("Asset XML file: " + fileName);
+ }
+
+ /*package*/ void xmlBlockGone(int id) {
+ synchronized (this) {
+ decRefsLocked(id);
+ }
+ }
+
+ /*package*/ final long createTheme() {
+ synchronized (this) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ long res = newTheme();
+ incRefsLocked(res);
+ return res;
+ }
+ }
+
+ /*package*/ final void releaseTheme(long theme) {
+ synchronized (this) {
+ deleteTheme(theme);
+ decRefsLocked(theme);
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (DEBUG_REFS && mNumRefs != 0) {
+ Log.w(TAG, "AssetManager " + this
+ + " finalized with non-zero refs: " + mNumRefs);
+ if (mRefStacks != null) {
+ for (RuntimeException e : mRefStacks.values()) {
+ Log.w(TAG, "Reference from here", e);
+ }
+ }
+ }
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public final class AssetInputStream extends InputStream {
+ /**
+ * @hide
+ */
+ public final int getAssetInt() {
+ throw new UnsupportedOperationException();
+ }
+ /**
+ * @hide
+ */
+ public final long getNativeAsset() {
+ return mAsset;
+ }
+ private AssetInputStream(long asset)
+ {
+ mAsset = asset;
+ mLength = getAssetLength(asset);
+ }
+ public final int read() throws IOException {
+ return readAssetChar(mAsset);
+ }
+ public final boolean markSupported() {
+ return true;
+ }
+ public final int available() throws IOException {
+ long len = getAssetRemainingLength(mAsset);
+ return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
+ }
+ public final void close() throws IOException {
+ synchronized (AssetManager.this) {
+ if (mAsset != 0) {
+ destroyAsset(mAsset);
+ mAsset = 0;
+ decRefsLocked(hashCode());
+ }
+ }
+ }
+ public final void mark(int readlimit) {
+ mMarkPos = seekAsset(mAsset, 0, 0);
+ }
+ public final void reset() throws IOException {
+ seekAsset(mAsset, mMarkPos, -1);
+ }
+ public final int read(byte[] b) throws IOException {
+ return readAsset(mAsset, b, 0, b.length);
+ }
+ public final int read(byte[] b, int off, int len) throws IOException {
+ return readAsset(mAsset, b, off, len);
+ }
+ public final long skip(long n) throws IOException {
+ long pos = seekAsset(mAsset, 0, 0);
+ if ((pos+n) > mLength) {
+ n = mLength-pos;
+ }
+ if (n > 0) {
+ seekAsset(mAsset, n, 0);
+ }
+ return n;
+ }
+
+ protected void finalize() throws Throwable
+ {
+ close();
+ }
+
+ private long mAsset;
+ private long mLength;
+ private long mMarkPos;
+ }
+
+ /**
+ * Add an additional set of assets to the asset manager. This can be
+ * either a directory or ZIP file. Not for use by applications. Returns
+ * the cookie of the added asset, or 0 on failure.
+ * {@hide}
+ */
+ public final int addAssetPath(String path) {
+ return addAssetPathInternal(path, false);
+ }
+
+ /**
+ * Add an application assets to the asset manager and loading it as shared library.
+ * This can be either a directory or ZIP file. Not for use by applications. Returns
+ * the cookie of the added asset, or 0 on failure.
+ * {@hide}
+ */
+ public final int addAssetPathAsSharedLibrary(String path) {
+ return addAssetPathInternal(path, true);
+ }
+
+ private final int addAssetPathInternal(String path, boolean appAsLib) {
+ synchronized (this) {
+ int res = addAssetPathNative(path, appAsLib);
+ makeStringBlocks(mStringBlocks);
+ return res;
+ }
+ }
+
+ private native final int addAssetPathNative(String path, boolean appAsLib);
+
+ /**
+ * Add a set of assets to overlay an already added set of assets.
+ *
+ * This is only intended for application resources. System wide resources
+ * are handled before any Java code is executed.
+ *
+ * {@hide}
+ */
+
+ public final int addOverlayPath(String idmapPath) {
+ synchronized (this) {
+ int res = addOverlayPathNative(idmapPath);
+ makeStringBlocks(mStringBlocks);
+ return res;
+ }
+ }
+
+ /**
+ * See addOverlayPath.
+ *
+ * {@hide}
+ */
+ public native final int addOverlayPathNative(String idmapPath);
+
+ /**
+ * Add multiple sets of assets to the asset manager at once. See
+ * {@link #addAssetPath(String)} for more information. Returns array of
+ * cookies for each added asset with 0 indicating failure, or null if
+ * the input array of paths is null.
+ * {@hide}
+ */
+ public final int[] addAssetPaths(String[] paths) {
+ if (paths == null) {
+ return null;
+ }
+
+ int[] cookies = new int[paths.length];
+ for (int i = 0; i < paths.length; i++) {
+ cookies[i] = addAssetPath(paths[i]);
+ }
+
+ return cookies;
+ }
+
+ /**
+ * 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}
+ */
+ public native final boolean isUpToDate();
+
+ /**
+ * 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 java.util.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 native final String[] getLocales();
+
+ /**
+ * 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 native final String[] getNonSystemLocales();
+
+ /** {@hide} */
+ public native final Configuration[] getSizeConfigurations();
+
+ /**
+ * Change the configuation used when retrieving resources. Not for use by
+ * applications.
+ * {@hide}
+ */
+ public native final void setConfiguration(int mcc, int mnc, String locale,
+ int orientation, int touchscreen, int density, int keyboard,
+ int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+ int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
+ int screenLayout, int uiMode, int colorMode, int majorVersion);
+
+ /**
+ * Retrieve the resource identifier for the given resource name.
+ */
+ /*package*/ native final int getResourceIdentifier(String name,
+ String defType,
+ String defPackage);
+
+ /*package*/ native final String getResourceName(int resid);
+ /*package*/ native final String getResourcePackageName(int resid);
+ /*package*/ native final String getResourceTypeName(int resid);
+ /*package*/ native final String getResourceEntryName(int resid);
+
+ private native final long openAsset(String fileName, int accessMode);
+ private final native ParcelFileDescriptor openAssetFd(String fileName,
+ long[] outOffsets) throws IOException;
+ private native final long openNonAssetNative(int cookie, String fileName,
+ int accessMode);
+ private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
+ String fileName, long[] outOffsets) throws IOException;
+ private native final void destroyAsset(long asset);
+ private native final int readAssetChar(long asset);
+ private native final int readAsset(long asset, byte[] b, int off, int len);
+ private native final long seekAsset(long asset, long offset, int whence);
+ private native final long getAssetLength(long asset);
+ private native final long getAssetRemainingLength(long asset);
+
+ /** Returns true if the resource was found, filling in mRetStringBlock and
+ * mRetData. */
+ private native final int loadResourceValue(int ident, short density, TypedValue outValue,
+ boolean resolve);
+ /** Returns true if the resource was found, filling in mRetStringBlock and
+ * mRetData. */
+ private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
+ boolean resolve);
+ /*package*/ static final int STYLE_NUM_ENTRIES = 6;
+ /*package*/ static final int STYLE_TYPE = 0;
+ /*package*/ static final int STYLE_DATA = 1;
+ /*package*/ static final int STYLE_ASSET_COOKIE = 2;
+ /*package*/ static final int STYLE_RESOURCE_ID = 3;
+
+ /* Offset within typed data array for native changingConfigurations. */
+ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
+
+ /*package*/ static final int STYLE_DENSITY = 5;
+ /*package*/ native static final void applyStyle(long theme,
+ int defStyleAttr, int defStyleRes, long xmlParser,
+ int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress);
+ /*package*/ native static final boolean resolveAttrs(long theme,
+ int defStyleAttr, int defStyleRes, int[] inValues,
+ int[] inAttrs, int[] outValues, int[] outIndices);
+ /*package*/ native final boolean retrieveAttributes(
+ long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
+ /*package*/ native final int getArraySize(int resource);
+ /*package*/ native final int retrieveArray(int resource, int[] outValues);
+ private native final int getStringBlockCount();
+ private native final long getNativeStringBlock(int block);
+
+ /**
+ * {@hide}
+ */
+ public native final String getCookieName(int cookie);
+
+ /**
+ * {@hide}
+ */
+ public native final SparseArray<String> getAssignedPackageIdentifiers();
+
+ /**
+ * {@hide}
+ */
+ public native static final int getGlobalAssetCount();
+
+ /**
+ * {@hide}
+ */
+ public native static final String getAssetAllocations();
+
+ /**
+ * {@hide}
+ */
+ public native static final int getGlobalAssetManagerCount();
+
+ private native final long newTheme();
+ private native final void deleteTheme(long theme);
+ /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
+ /*package*/ native static final void copyTheme(long dest, long source);
+ /*package*/ native static final void clearTheme(long theme);
+ /*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
+ TypedValue outValue,
+ boolean resolve);
+ /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);
+ /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme);
+
+ private native final long openXmlAssetNative(int cookie, String fileName);
+
+ private native final String[] getArrayStringResource(int arrayRes);
+ private native final int[] getArrayStringInfo(int arrayRes);
+ /*package*/ native final int[] getArrayIntResource(int arrayRes);
+ /*package*/ native final int[] getStyleAttributes(int themeRes);
+
+ private native final void init(boolean isSystem);
+ private native final void destroy();
+
+ private final void incRefsLocked(long id) {
+ if (DEBUG_REFS) {
+ if (mRefStacks == null) {
+ mRefStacks = new HashMap<Long, RuntimeException>();
+ }
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ mRefStacks.put(id, ex);
+ }
+ mNumRefs++;
+ }
+
+ private final void decRefsLocked(long id) {
+ if (DEBUG_REFS && mRefStacks != null) {
+ mRefStacks.remove(id);
+ }
+ mNumRefs--;
+ //System.out.println("Dec streams: mNumRefs=" + mNumRefs
+ // + " mReleased=" + mReleased);
+ if (mNumRefs == 0) {
+ destroy();
+ }
+ }
+}
diff --git a/android/content/res/AssetManager_Delegate.java b/android/content/res/AssetManager_Delegate.java
new file mode 100644
index 00000000..3b47ea74
--- /dev/null
+++ b/android/content/res/AssetManager_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 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 {
+
+ @LayoutlibDelegate
+ public static InputStream open(AssetManager mgr, String fileName) throws IOException {
+ return mgr.open_Original(fileName);
+ }
+
+ @LayoutlibDelegate
+ public static InputStream open(AssetManager mgr, String fileName, int accessMode)
+ throws IOException {
+ if (!(mgr instanceof BridgeAssetManager)) {
+ return mgr.open_Original(fileName, accessMode);
+ }
+ return ((BridgeAssetManager) mgr).getAssetRepository().openAsset(fileName, accessMode);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long newTheme(AssetManager manager) {
+ return Resources_Theme_Delegate.getDelegateManager()
+ .addNewDelegate(new Resources_Theme_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void deleteTheme(AssetManager manager, long theme) {
+ Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager) {
+ return new SparseArray<>();
+ }
+}
diff --git a/android/content/res/BridgeAssetManager.java b/android/content/res/BridgeAssetManager.java
new file mode 100644
index 00000000..2691e564
--- /dev/null
+++ b/android/content/res/BridgeAssetManager.java
@@ -0,0 +1,62 @@
+/*
+ * 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.AssetRepository;
+import com.android.layoutlib.bridge.Bridge;
+
+public class BridgeAssetManager extends AssetManager {
+
+ 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();
+ AssetManager.sSystem.makeStringBlocks(null);
+ }
+ 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(AssetRepository assetRepository) {
+ mAssetRepository = assetRepository;
+ }
+
+ public AssetRepository getAssetRepository() {
+ return mAssetRepository;
+ }
+
+ public BridgeAssetManager() {
+ }
+}
diff --git a/android/content/res/BridgeTypedArray.java b/android/content/res/BridgeTypedArray.java
new file mode 100644
index 00000000..5536c4f6
--- /dev/null
+++ b/android/content/res/BridgeTypedArray.java
@@ -0,0 +1,1025 @@
+/*
+ * 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.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+
+import android.annotation.Nullable;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
+import android.graphics.Typeface_Accessor;
+import android.graphics.drawable.Drawable;
+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.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 boolean mPlatformFile;
+
+ private final int[] mResourceId;
+ private final ResourceValue[] mResourceData;
+ private final String[] mNames;
+ private final boolean[] mIsFramework;
+
+ // 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,
+ boolean platformFile) {
+ super(resources);
+ mBridgeResources = resources;
+ mContext = context;
+ mPlatformFile = platformFile;
+ mResourceId = new int[len];
+ mResourceData = new ResourceValue[len];
+ mNames = new String[len];
+ mIsFramework = new boolean[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 isFramework whether the attribute is in the android namespace.
+ * @param resourceId the reference id of this resource
+ * @param value the value of the attribute
+ */
+ public void bridgeSetValue(int index, String name, boolean isFramework, int resourceId,
+ ResourceValue value) {
+ mResourceId[index] = resourceId;
+ mResourceData[index] = value;
+ mNames[index] = name;
+ mIsFramework[index] = isFramework;
+ }
+
+ /**
+ * Seals the array after all calls to
+ * {@link #bridgeSetValue(int, String, boolean, 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<Integer>(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) {
+ // FIXME: handle styled strings!
+ return getString(index);
+ }
+
+ /**
+ * 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);
+ }
+ 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);
+ }
+ 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);
+ }
+
+ 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);
+
+ 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);
+
+ 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 getType() and getName() returning a resource name.
+ // (and getValue() returning null!). We need to handle this!
+ if (resValue.getResourceType() != null) {
+ // if this is a framework id
+ if (mPlatformFile || resValue.isFramework()) {
+ // look for idName in the android R classes
+ return mContext.getFrameworkResourceValue(
+ resValue.getResourceType(), resValue.getName(), defValue);
+ }
+
+ // look for idName in the project R class.
+ return mContext.getProjectResourceValue(
+ resValue.getResourceType(), resValue.getName(), defValue);
+ }
+
+ // else, try to get the value, and resolve it somehow.
+ String value = resValue.getValue();
+ if (value == null) {
+ return defValue;
+ }
+ value = value.trim();
+
+ // if the value is just an integer, return it.
+ try {
+ int i = Integer.parseInt(value);
+ if (Integer.toString(i).equals(value)) {
+ return i;
+ }
+ } catch (NumberFormatException e) {
+ // pass
+ }
+
+ 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.
+ if (value.startsWith("@id/") || value.startsWith("@+") ||
+ value.startsWith("@android:id/")) {
+
+ int pos = value.indexOf('/');
+ String idName = value.substring(pos + 1);
+ boolean create = value.startsWith("@+");
+ boolean isFrameworkId =
+ mPlatformFile || value.startsWith("@android") || value.startsWith("@+android");
+
+ // Look for the idName in project or android R class depending on isPlatform.
+ if (create) {
+ Integer idValue;
+ if (isFrameworkId) {
+ idValue = Bridge.getResourceId(ResourceType.ID, idName);
+ } else {
+ idValue = mContext.getLayoutlibCallback().getResourceId(ResourceType.ID, idName);
+ }
+ return idValue == null ? defValue : idValue;
+ }
+ // This calls the same method as in if(create), but doesn't create a dynamic id, if
+ // one is not found.
+ if (isFrameworkId) {
+ return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
+ } else {
+ return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
+ }
+ }
+ else if (value.startsWith("@aapt:_aapt")) {
+ return mContext.getLayoutlibCallback().getResourceId(ResourceType.AAPT, value);
+ }
+
+ // 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;
+ }
+
+ // Ok, not an enum, resolve as an ID
+ Integer idValue;
+
+ if (resValue.isFramework()) {
+ idValue = Bridge.getResourceId(resValue.getResourceType(),
+ resValue.getName());
+ } else {
+ idValue = mContext.getLayoutlibCallback().getResourceId(
+ resValue.getResourceType(), resValue.getName());
+ }
+
+ if (idValue != null) {
+ return idValue;
+ }
+
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format(
+ "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]),
+ resValue);
+
+ 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
+ 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
+ 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.fillValues(mBridgeResources, array, new CharSequence[count]) :
+ 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;
+ 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 (mIsFramework[index]) {
+ map = Bridge.getEnumValues(mNames[index]);
+ } else {
+ // get the styleable matching the resolved name
+ RenderResources res = mContext.getRenderResources();
+ ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]);
+ if (attr instanceof AttrResourceValue) {
+ map = ((AttrResourceValue) attr).getAttributeValues();
+ }
+ }
+
+ if (map != null) {
+ // accumulator to store the value of the 1+ constants.
+ int result = 0;
+ boolean found = false;
+
+ // split the value in case this is a mix of several flags.
+ String[] keywords = mResourceData[index].getValue().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, true);
+ }
+}
diff --git a/android/content/res/ColorStateList.java b/android/content/res/ColorStateList.java
new file mode 100644
index 00000000..faf23816
--- /dev/null
+++ b/android/content/res/ColorStateList.java
@@ -0,0 +1,736 @@
+/*
+ * 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.content.pm.ActivityInfo.Config;
+import android.content.res.Resources.Theme;
+import android.graphics.Color;
+
+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 android.util.AttributeSet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.SparseArray;
+import android.util.StateSet;
+import android.util.Xml;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+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>
+ * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ * &lt;item android:state_focused="true"
+ * android:color="@color/sample_focused" /&gt;
+ * &lt;item android:state_pressed="true"
+ * android:state_enabled="false"
+ * android:color="@color/sample_disabled_pressed" /&gt;
+ * &lt;item android:state_enabled="false"
+ * android:color="@color/sample_disabled_not_pressed" /&gt;
+ * &lt;item android:color="@color/sample_default" /&gt;
+ * &lt;/selector&gt;
+ * </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>
+ * &lt;item android:state_focused="true"
+ * android:color="@color/sample_focused" /&gt;
+ * </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>
+ * &lt;item android:state_enabled="false"
+ * android:color="?android:attr/colorAccent"
+ * android:alpha="0.5" /&gt;
+ * </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. */
+ private ColorStateListFactory mFactory;
+
+ private int[][] mThemeAttrs;
+ private @Config int mChangingConfigurations;
+
+ private int[][] mStateSpecs;
+ private int[] mColors;
+ private int mDefaultColor;
+ private boolean mIsOpaque;
+
+ 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 &lt;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
+ 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
+ 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
+ */
+ 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
+ */
+ 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.
+ */
+ 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;
+
+ 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 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 00000000..829b6b7f
--- /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 00000000..781e2353
--- /dev/null
+++ b/android/content/res/CompatibilityInfo.java
@@ -0,0 +1,615 @@
+/*
+ * 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.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 compatibility mode that the application is
+ * running under.
+ *
+ * {@hide}
+ */
+public class CompatibilityInfo implements Parcelable {
+ /** default compatibility info object for compatible applications */
+ 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.
+ */
+ public final float applicationScale;
+
+ /**
+ * Application's inverted scale.
+ */
+ public final float applicationInvertedScale;
+
+ 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;
+ }
+
+ private CompatibilityInfo() {
+ this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
+ 1.0f,
+ 1.0f);
+ }
+
+ /**
+ * @return true if the scaling is required
+ */
+ public boolean isScalingRequired() {
+ return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
+ }
+
+ 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
+ */
+ 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 {
+ final public float applicationScale;
+ 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.
+ */
+ public void translateRectInScreenToAppWinFrame(Rect rect) {
+ rect.scale(applicationInvertedScale);
+ }
+
+ /**
+ * Translate the region in window to screen.
+ */
+ public void translateRegionInWindowToScreen(Region transparentRegion) {
+ transparentRegion.scale(applicationScale);
+ }
+
+ /**
+ * Apply translation to the canvas that is necessary to draw the content.
+ */
+ 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.
+ */
+ public void translateEventInScreenToAppWindow(MotionEvent event) {
+ event.scale(applicationInvertedScale);
+ }
+
+ /**
+ * Translate the window's layout parameter, from application's view to
+ * Screen's view.
+ */
+ public void translateWindowLayout(WindowManager.LayoutParams params) {
+ params.scale(applicationScale);
+ }
+
+ /**
+ * Translate a Rect in application's window to screen.
+ */
+ public void translateRectInAppWindowToScreen(Rect rect) {
+ rect.scale(applicationScale);
+ }
+
+ /**
+ * Translate a Rect in screen coordinates into the app window's coordinates.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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);
+ }
+
+ public static final 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 00000000..58c6fc51
--- /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 00000000..09c02601
--- /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 00000000..780e6f76
--- /dev/null
+++ b/android/content/res/Configuration.java
@@ -0,0 +1,2385 @@
+/*
+ * 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+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.view.View;
+
+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.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();
+
+ /**
+ * 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
+ */
+ 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;
+
+ /**
+ * @hide
+ * 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.
+ */
+ 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_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");
+ }
+ 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;
+
+ /** 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}.
+ */
+ 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
+ */
+ public int assetsSeq;
+
+ /**
+ * @hide Internal book-keeping.
+ */
+ public int seq;
+
+ /** @hide */
+ @IntDef(flag = true,
+ 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();
+ }
+
+ /**
+ * 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} */
+ @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;
+ }
+
+ /**
+ * 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();
+ final int localeListSize = mLocaleList.size();
+ dest.writeInt(localeListSize);
+ for (int i = 0; i < localeListSize; ++i) {
+ final Locale l = mLocaleList.get(i);
+ dest.writeString(l.toLanguageTag());
+ }
+
+ 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();
+
+ final int localeListSize = source.readInt();
+ final Locale[] localeArray = new Locale[localeListSize];
+ for (int i = 0; i < localeListSize; ++i) {
+ localeArray[i] = Locale.forLanguageTag(source.readString());
+ }
+ mLocaleList = new LocaleList(localeArray);
+ 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 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);
+ }
+
+ 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.
+ *
+ * @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).
+ *
+ * @hide
+ */
+ public static String resourceQualifierString(Configuration config) {
+ 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_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.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.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");
+ 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;
+ }
+
+ 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}
+ */
+ 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.
+ }
+
+
+ /**
+ * Writes the Configuration's member fields as attributes into the XmlSerializer.
+ * The serializer is expected to have already started a tag so that attributes can be
+ * immediately written.
+ *
+ * @param xml The serializer to which to write the attributes.
+ * @param config The Configuration whose member fields to write.
+ * {@hide}
+ */
+ public static void writeXmlAttrs(XmlSerializer xml, Configuration config) throws IOException {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_FONT_SCALE,
+ Float.floatToIntBits(config.fontScale));
+ if (config.mcc != 0) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_MCC, config.mcc);
+ }
+ if (config.mnc != 0) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_MNC, config.mnc);
+ }
+ config.fixUpLocaleList();
+ if (!config.mLocaleList.isEmpty()) {
+ XmlUtils.writeStringAttribute(xml, XML_ATTR_LOCALES, config.mLocaleList.toLanguageTags());
+ }
+ if (config.touchscreen != TOUCHSCREEN_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_TOUCHSCREEN, config.touchscreen);
+ }
+ if (config.keyboard != KEYBOARD_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_KEYBOARD, config.keyboard);
+ }
+ if (config.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_KEYBOARD_HIDDEN, config.keyboardHidden);
+ }
+ if (config.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_HARD_KEYBOARD_HIDDEN,
+ config.hardKeyboardHidden);
+ }
+ if (config.navigation != NAVIGATION_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_NAVIGATION, config.navigation);
+ }
+ if (config.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_NAVIGATION_HIDDEN, config.navigationHidden);
+ }
+ if (config.orientation != ORIENTATION_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_ORIENTATION, config.orientation);
+ }
+ if (config.screenLayout != SCREENLAYOUT_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_LAYOUT, config.screenLayout);
+ }
+ if (config.colorMode != COLOR_MODE_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_COLOR_MODE, config.colorMode);
+ }
+ if (config.uiMode != 0) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_UI_MODE, config.uiMode);
+ }
+ if (config.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_WIDTH, config.screenWidthDp);
+ }
+ if (config.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_HEIGHT, config.screenHeightDp);
+ }
+ if (config.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_SMALLEST_WIDTH, config.smallestScreenWidthDp);
+ }
+ if (config.densityDpi != DENSITY_DPI_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_DENSITY, config.densityDpi);
+ }
+
+ // For persistence, we do not care about assetsSeq and window configuration, so do not write
+ // it out.
+ }
+}
diff --git a/android/content/res/ConfigurationBoundResourceCache.java b/android/content/res/ConfigurationBoundResourceCache.java
new file mode 100644
index 00000000..70290c4b
--- /dev/null
+++ b/android/content/res/ConfigurationBoundResourceCache.java
@@ -0,0 +1,50 @@
+/*
+ * 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 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>> {
+ /**
+ * 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 00000000..09d4a59d
--- /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 00000000..7b27fac2
--- /dev/null
+++ b/android/content/res/DrawableCache.java
@@ -0,0 +1,47 @@
+/*
+ * 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.graphics.drawable.Drawable;
+
+/**
+ * Class which can be used to cache Drawable resources against a theme.
+ */
+class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
+ /**
+ * 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 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 00000000..042eb87f
--- /dev/null
+++ b/android/content/res/FontResourcesParser.java
@@ -0,0 +1,230 @@
+/*
+ * 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 com.android.internal.R;
+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 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 {
+ private final @NonNull String mFileName;
+ private int mWeight;
+ private int mItalic;
+ private int mResourceId;
+
+ public FontFileResourceEntry(@NonNull String fileName, int weight, int italic) {
+ mFileName = fileName;
+ mWeight = weight;
+ mItalic = italic;
+ }
+
+ public @NonNull String getFileName() {
+ return mFileName;
+ }
+
+ public int getWeight() {
+ return mWeight;
+ }
+
+ public int getItalic() {
+ return mItalic;
+ }
+ }
+
+ // 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,
+ Typeface.RESOLVE_BY_FONT_TABLE);
+ 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);
+ }
+
+ 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 00000000..e4659613
--- /dev/null
+++ b/android/content/res/GradientColor.java
@@ -0,0 +1,596 @@
+/*
+ * 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>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ * &lt;android:startColor="?android:attr/colorPrimary"/&gt;
+ * &lt;android:endColor="?android:attr/colorControlActivated"/&gt;
+ * &lt;.../&gt;
+ * &lt;android:type="linear"/&gt;
+ * &lt;/gradient&gt;
+ * </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({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 &lt;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 00000000..b653f9ff
--- /dev/null
+++ b/android/content/res/ObbInfo.java
@@ -0,0 +1,106 @@
+/*
+ * 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.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
+ */
+ 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) {
+ dest.writeString(filename);
+ dest.writeString(packageName);
+ dest.writeInt(version);
+ dest.writeInt(flags);
+ dest.writeByteArray(salt);
+ }
+
+ public static final 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 00000000..1b38eeaf
--- /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 00000000..adb9cf1c
--- /dev/null
+++ b/android/content/res/ResourceId.java
@@ -0,0 +1,44 @@
+/*
+ * 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 {
+
+ /**
+ * The {@code null} resource ID.
+ */
+ public static final @AnyRes int ID_NULL = 0;
+
+ /**
+ * 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 00000000..e173653c
--- /dev/null
+++ b/android/content/res/Resources.java
@@ -0,0 +1,2184 @@
+/*
+ * 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.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.Config;
+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.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.DisplayAdjustments;
+import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.GrowingArrayUtils;
+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;
+
+/**
+ * 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&mdash;by providing sets of alternative
+ * resources&mdash;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>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 {
+ static final String TAG = "Resources";
+
+ private static final Object sSync = new Object();
+
+ // Used by BridgeResources in layoutlib
+ static Resources mSystem = null;
+
+ private ResourcesImpl mResourcesImpl;
+
+ // Pool of TypedArrays targeted to this Resources object.
+ final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
+
+ /** Used to inflate drawable objects from XML. */
+ 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. */
+ private TypedValue mTmpValue = new TypedValue();
+
+ final ClassLoader mClassLoader;
+
+ /**
+ * 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<>();
+
+ /**
+ * 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
+ */
+ 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 != ResourceId.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), and is not configured for
+ * the current screen (can not use dimension units, does not change based
+ * on orientation, etc).
+ */
+ 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);
+ }
+ }
+
+ /**
+ * 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
+ */
+ public Resources(@Nullable ClassLoader classLoader) {
+ mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+ }
+
+ /**
+ * Only for creating the System resources.
+ */
+ 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
+ */
+ public void setImpl(ResourcesImpl impl) {
+ if (impl == mResourcesImpl) {
+ return;
+ }
+
+ 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 ResourcesImpl getImpl() {
+ return mResourcesImpl;
+ }
+
+ /**
+ * @hide
+ */
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * @return the inflater used to create drawable objects
+ * @hide Pending API finalization.
+ */
+ 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().getArrayIntResource(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().getArraySize(id);
+ if (len < 0) {
+ throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
+ }
+
+ TypedArray array = TypedArray.obtain(this, len);
+ array.mLength = impl.getAssets().retrieveArray(id, array.mData);
+ array.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.
+ *
+ * @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.
+ */
+ @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}.
+ * @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 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 impl.loadDrawable(this, value, id, density, theme);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ @NonNull
+ 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.
+ *
+ */
+ 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.
+ */
+ @Nullable
+ @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.
+ */
+ @Nullable
+ 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);
+ }
+ }
+
+ @Nullable
+ ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme)
+ throws NotFoundException {
+ return mResourcesImpl.loadColorStateList(this, value, id, theme);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ 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.
+ * @hide Pending API council approval.
+ */
+ public float getFloat(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
+ */
+ 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
+ */
+ 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
+ */
+ 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.
+ *
+ */
+ 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.
+ */
+ 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);
+ }
+
+ /**
+ * This class holds the current attribute values for a particular theme.
+ * In other words, a Theme is a set of values for resource attributes;
+ * these are used in conjunction with {@link TypedArray}
+ * to resolve the final value for an attribute.
+ *
+ * <p>The Theme's attributes come into play in two ways: (1) a styled
+ * attribute can explicit reference a value in the theme through the
+ * "?themeAttribute" syntax; (2) if no value has been defined for a
+ * particular styled attribute, as a last resort we will try to find that
+ * attribute's value in the Theme.
+ *
+ * <p>You will normally use the {@link #obtainStyledAttributes} APIs to
+ * retrieve XML attributes with style and theme information applied.
+ */
+ public final class Theme {
+ private 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)
+ */
+ public TypedArray obtainStyledAttributes(@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)
+ */
+ public TypedArray obtainStyledAttributes(@StyleRes int resId, @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>&lt;Button
+ * textColor="#ff000000"&gt;</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[])
+ */
+ public TypedArray obtainStyledAttributes(AttributeSet set,
+ @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
+ 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)}.
+ *
+ * @hide
+ */
+ public void rebase() {
+ mThemeImpl.rebase();
+ }
+ }
+
+ 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;
+ }
+
+ @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));
+ }
+ 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.mParseState, 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
+ */
+ 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.
+ *
+ * @return The resource's current display metrics.
+ */
+ public DisplayMetrics getDisplayMetrics() {
+ return mResourcesImpl.getDisplayMetrics();
+ }
+
+ /** @hide */
+ 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
+ */
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mResourcesImpl.getCompatibilityInfo();
+ }
+
+ /**
+ * This is just for testing.
+ * @hide
+ */
+ @VisibleForTesting
+ 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);
+ }
+
+ /**
+ * Parse a series of {@link android.R.styleable#Extra &lt;extra&gt;} 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
+ */
+ 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
+ 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 impl.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
+ 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);
+ }
+}
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
new file mode 100644
index 00000000..a8b8c4b5
--- /dev/null
+++ b/android/content/res/ResourcesImpl.java
@@ -0,0 +1,1380 @@
+/*
+ * 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.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.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.Configuration.NativeConfig;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
+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 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";
+
+ private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
+ 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;
+ 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.
+ private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
+ private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
+ = new LongSparseArray<>();
+ private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
+ sPreloadedComplexColors = new LongSparseArray<>();
+
+ /** Lock object used to protect access to caches and configuration. */
+ private final Object mAccessLock = new Object();
+
+ // These are protected by mAccessLock.
+ private final Configuration mTmpConfig = new Configuration();
+ private final DrawableCache mDrawableCache = new DrawableCache();
+ private final DrawableCache mColorDrawableCache = new DrawableCache();
+ private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
+ new ConfigurationBoundResourceCache<>();
+ private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
+ new ConfigurationBoundResourceCache<>();
+ private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
+ new ConfigurationBoundResourceCache<>();
+
+ /** 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];
+
+
+ final AssetManager mAssets;
+ private final DisplayMetrics mMetrics = new DisplayMetrics();
+ private final DisplayAdjustments mDisplayAdjustments;
+
+ private PluralRules mPluralRule;
+
+ 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.
+ */
+ 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());
+ mAssets.ensureStringBlocks();
+ }
+
+ public DisplayAdjustments getDisplayAdjustments() {
+ return mDisplayAdjustments;
+ }
+
+ public AssetManager getAssets() {
+ return mAssets;
+ }
+
+ 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;
+ }
+ }
+
+ 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
+ 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 drawable "
+ + "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 drawable 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 (false) {
+ 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);
+ }
+ }
+
+ @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, null);
+ }
+ // 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 XML or resources stream.
+ */
+ private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
+ int id, int density, @Nullable Resources.Theme theme) {
+ 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 prelaod tracing.
+ long startTime = 0;
+ int startBitmapCount = 0;
+ long startBitmapSize = 0;
+ int startDrwableCount = 0;
+ if (TRACE_FOR_DETAILED_PRELOAD) {
+ startTime = System.nanoTime();
+ startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
+ startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
+ startDrwableCount = sPreloadTracingNumLoadedDrawables;
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
+ }
+
+ final Drawable dr;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ try {
+ if (file.endsWith(".xml")) {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+ rp.close();
+ } else {
+ final InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+ dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
+ is.close();
+ }
+ } catch (Exception 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 - startDrwableCount;
+
+ 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;
+ }
+
+ /**
+ * 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 Typeface.createFromResources(mAssets, file, value.assetCookie);
+ } 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.
+ */
+ 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;
+ }
+
+ @Nullable
+ 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.
+ */
+ @Nullable
+ 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();
+ }
+ }
+
+ // 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();
+ }
+ }
+ } 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();
+ }
+ }
+
+ 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) {
+ AssetManager.applyThemeStyle(mTheme, resId, force);
+
+ mThemeResId = resId;
+ mKey.append(resId, force);
+ }
+ }
+
+ void setTo(ThemeImpl other) {
+ synchronized (mKey) {
+ synchronized (other.mKey) {
+ AssetManager.copyTheme(mTheme, 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;
+ AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
+ parser != null ? parser.mParseState : 0,
+ attrs, attrs.length, 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);
+ AssetManager.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.getThemeChangingConfigurations(mTheme);
+ return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
+ }
+ }
+
+ public void dump(int priority, String tag, String prefix) {
+ synchronized (mKey) {
+ AssetManager.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.clearTheme(mTheme);
+
+ // Reapply the same styles in the same order.
+ for (int i = 0; i < mKey.mCount; i++) {
+ final int resId = mKey.mResId[i];
+ final boolean force = mKey.mForce[i];
+ AssetManager.applyThemeStyle(mTheme, resId, force);
+ }
+ }
+ }
+ }
+}
diff --git a/android/content/res/ResourcesKey.java b/android/content/res/ResourcesKey.java
new file mode 100644
index 00000000..b03ed1ee
--- /dev/null
+++ b/android/content/res/ResourcesKey.java
@@ -0,0 +1,169 @@
+/*
+ * 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.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/** @hide */
+public final class ResourcesKey {
+ @Nullable
+ public final String mResDir;
+
+ @Nullable
+ 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;
+
+ 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) {
+ mResDir = resDir;
+ mSplitResDirs = splitResDirs;
+ mOverlayDirs = overlayDirs;
+ mLibDirs = libDirs;
+ 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);
+ mHash = hash;
+ }
+
+ 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;
+ }
+ 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("}");
+ return builder.toString();
+ }
+}
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
new file mode 100644
index 00000000..c1e9cd36
--- /dev/null
+++ b/android/content/res/Resources_Delegate.java
@@ -0,0 +1,1100 @@
+/*
+ * 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.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.ResourceValue;
+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.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.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.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.WeakHashMap;
+
+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<>();
+
+ private static boolean[] mPlatformResourceFlag = new boolean[1];
+ // 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();
+ Resources.mSystem = null;
+ }
+
+ public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
+ boolean platformFile) {
+ return new BridgeTypedArray(resources, getContext(resources), numEntries, platformFile);
+ }
+
+ private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id,
+ boolean[] platformResFlag_out) {
+ // first get the String related to this id in the framework
+ Pair<ResourceType, String> 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) {
+ platformResFlag_out[0] = true;
+ return resourceInfo;
+ }
+
+ // didn't find a match in the framework? look in the project.
+ resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ platformResFlag_out[0] = false;
+ return resourceInfo;
+ }
+ return null;
+ }
+
+ private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+ boolean[] platformResFlag_out) {
+ Pair<ResourceType, String> resourceInfo =
+ getResourceInfo(resources, id, platformResFlag_out);
+
+ if (resourceInfo != null) {
+ String attributeName = resourceInfo.getSecond();
+ RenderResources renderResources = getContext(resources).getRenderResources();
+ ResourceValue value = platformResFlag_out[0] ?
+ renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
+ renderResources.getProjectResource(resourceInfo.getFirst(), attributeName);
+
+ if (value == null) {
+ // Unable to resolve the attribute, just leave the unresolved value
+ value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
+ platformResFlag_out[0]);
+ }
+ 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, mPlatformResourceFlag);
+ 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, mPlatformResourceFlag);
+
+ 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.
+ String message;
+ if (new File(resourceValue.getValue()).isFile()) {
+ 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);
+ 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, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+
+ 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];
+ } else if (!(resValue instanceof ArrayResourceValue)) {
+ return new CharSequence[]{
+ resolveReference(resources, resValue.getValue(), resValue.isFramework())};
+ }
+ ArrayResourceValue arv = ((ArrayResourceValue) resValue);
+ return fillValues(resources, arv, new CharSequence[arv.getElementCount()]);
+ }
+
+ @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];
+ } else if (!(resValue instanceof ArrayResourceValue)) {
+ return new String[]{
+ resolveReference(resources, resValue.getValue(), resValue.isFramework())};
+ }
+ ArrayResourceValue arv = ((ArrayResourceValue) resValue);
+ return fillValues(resources, arv, new String[arv.getElementCount()]);
+ }
+
+ /**
+ * Resolve each element in resValue and copy them to {@code values}. The values copied are
+ * always Strings. The ideal signature for the method should be &lt;T super String&gt;, but java
+ * generics don't support it.
+ */
+ static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue,
+ T[] values) {
+ int i = 0;
+ for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
+ @SuppressWarnings("unchecked")
+ T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework());
+ values[i] = s;
+ }
+ return values;
+ }
+
+ @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];
+ } else if (!(rv instanceof ArrayResourceValue)) {
+ // This is an older IDE that can only give us the first element of the array.
+ String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework());
+ 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);
+ return new int[1];
+ }
+ }
+ ArrayResourceValue resValue = ((ArrayResourceValue) rv);
+ int[] values = new int[resValue.getElementCount()];
+ int i = 0;
+ for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
+ String element = resolveReference(resources, iterator.next(), resValue.isFramework());
+ 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);
+ } catch (IllegalArgumentException e2) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains wrong color format: " + element, null);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * 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, mPlatformResourceFlag);
+
+ 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);
+ 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);
+ }
+ 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;
+ }
+
+ @NonNull
+ private static String resolveReference(Resources resources, @NonNull String ref,
+ boolean forceFrameworkOnly) {
+ if (ref.startsWith(PREFIX_RESOURCE_REF) || ref.startsWith
+ (SdkConstants.PREFIX_THEME_REF)) {
+ ResourceValue rv =
+ getContext(resources).getRenderResources().findResValue(ref, forceFrameworkOnly);
+ rv = getContext(resources).getRenderResources().resolveResValue(rv);
+ if (rv != null) {
+ return rv.getValue();
+ }
+ }
+ // Not a reference.
+ return ref;
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ 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 configure parser for " + value.getValue(), e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ }
+
+ }
+
+ // 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 getAnimation(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ 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 configure parser for " + value.getValue(), e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ }
+
+ }
+
+ // 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 {
+ throw new UnsupportedOperationException();
+ }
+
+ @LayoutlibDelegate
+ static float getDimension(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+
+ 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 boolean getBoolean(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ 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 {
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+ if (resourceInfo != null) {
+ return resourceInfo.getSecond();
+ }
+ throwException(resid, null);
+ return null;
+
+ }
+
+ @LayoutlibDelegate
+ static String getResourceName(Resources resources, int resid) throws NotFoundException {
+ boolean[] platformOut = new boolean[1];
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+ String packageName;
+ if (resourceInfo != null) {
+ if (platformOut[0]) {
+ packageName = SdkConstants.ANDROID_NS_NAME;
+ } else {
+ packageName = getContext(resources).getPackageName();
+ packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
+ }
+ return packageName + ':' + resourceInfo.getFirst().getName() + '/' +
+ resourceInfo.getSecond();
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
+ boolean[] platformOut = new boolean[1];
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+ if (resourceInfo != null) {
+ if (platformOut[0]) {
+ return SdkConstants.ANDROID_NS_NAME;
+ }
+ String packageName = getContext(resources).getPackageName();
+ return packageName == null ? SdkConstants.APP_PREFIX : packageName;
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
+ Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+ if (resourceInfo != null) {
+ return resourceInfo.getFirst().getName();
+ }
+ throwException(resid, null);
+ return null;
+ }
+
+ @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, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+
+ 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, mPlatformResourceFlag);
+ 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 {
+ Resources_Delegate.getValue(resources, id, outValue, true);
+ if (outValue.string != null) {
+ return ResourceHelper.getFont(outValue.string.toString(), getContext(resources), null,
+ mPlatformResourceFlag[0]);
+ }
+
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ 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;
+ }
+ if (resVal instanceof DensityBasedResourceValue) {
+ outValue.density =
+ ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
+ }
+
+ // else it's a string
+ outValue.type = TypedValue.TYPE_STRING;
+ outValue.string = v;
+ return;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(resources, id);
+ }
+
+ @LayoutlibDelegate
+ static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String v = value.getSecond().getValue();
+
+ if (v != null) {
+ // check this is a file
+ File f = new File(v);
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ return new BridgeXmlBlockParser(parser, getContext(resources),
+ mPlatformResourceFlag[0]);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+ }
+ }
+
+ // 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).
+ getResourceValue(resources, id, mPlatformResourceFlag);
+
+ File f = new File(file);
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ } catch (FileNotFoundException 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, mPlatformResourceFlag);
+
+ if (value != null) {
+ String path = value.getSecond().getValue();
+
+ if (path != null) {
+ // check this is a file
+ File f = new File(path);
+ if (f.isFile()) {
+ try {
+ // 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(f);
+ }
+ return new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+ }
+ }
+
+ // 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();
+
+ File f = new File(path);
+ if (f.isFile()) {
+ try {
+ // 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(f);
+ }
+ return new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ NotFoundException exception = new NotFoundException();
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ throw new NotFoundException();
+ }
+
+ @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);
+ Integer id = null;
+ if (url != null) {
+ id = ANDROID_PKG.equals(url.namespace) ? Bridge.getResourceId(url.type, url.name) :
+ getLayoutlibCallback(resources).getResourceId(url.type, url.name);
+ }
+
+ return id != null ? id : 0;
+ }
+
+ /**
+ * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource
+ * type.
+ *
+ * @param id the id of the resource
+ *
+ * @throws NotFoundException
+ */
+ private static void throwException(Resources resources, int id) throws NotFoundException {
+ throwException(id, getResourceInfo(resources, id, new boolean[1]));
+ }
+
+ private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) {
+ 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.getFirst(), id, resourceInfo.getSecond());
+ } else {
+ message = String.format("Could not resolve resource value: 0x%1$X.", id);
+ }
+
+ 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);
+ }
+}
diff --git a/android/content/res/Resources_Theme_Delegate.java b/android/content/res/Resources_Theme_Delegate.java
new file mode 100644
index 00000000..f1e8fc21
--- /dev/null
+++ b/android/content/res/Resources_Theme_Delegate.java
@@ -0,0 +1,152 @@
+/*
+ * 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().obtainStyledAttributes(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().obtainStyledAttributes(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().obtainStyledAttributes(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);
+ if (theme.isFramework()) {
+ return (StyleResourceValue) context.getRenderResources()
+ .getFrameworkResource(ResourceType.STYLE, theme.getName());
+ } else {
+ return (StyleResourceValue) context.getRenderResources()
+ .getProjectResource(ResourceType.STYLE, theme.getName());
+ }
+ }
+}
diff --git a/android/content/res/StringBlock.java b/android/content/res/StringBlock.java
new file mode 100644
index 00000000..5cfc41f2
--- /dev/null
+++ b/android/content/res/StringBlock.java
@@ -0,0 +1,496 @@
+/*
+ * 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.graphics.Color;
+import android.text.*;
+import android.text.style.*;
+import android.util.Log;
+import android.util.SparseArray;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+
+import java.util.Arrays;
+
+/**
+ * Conveniences for retrieving data out of a compiled string resource.
+ *
+ * {@hide}
+ */
+final class StringBlock {
+ 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;
+ 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));
+ }
+
+ 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;
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ super.finalize();
+ } finally {
+ 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.
+ */
+ 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 00000000..f1b1e74a
--- /dev/null
+++ b/android/content/res/ThemedResourceCache.java
@@ -0,0 +1,234 @@
+/*
+ * 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.content.pm.ActivityInfo.Config;
+import android.content.res.Resources.Theme;
+import android.content.res.Resources.ThemeKey;
+import android.util.LongSparseArray;
+import android.util.ArrayMap;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Data structure used for caching data against themes.
+ *
+ * @param <T> type of data to cache
+ */
+abstract class ThemedResourceCache<T> {
+ 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
+ */
+ 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));
+ }
+}
diff --git a/android/content/res/TypedArray.java b/android/content/res/TypedArray.java
new file mode 100644
index 00000000..f33c7516
--- /dev/null
+++ b/android/content/res/TypedArray.java
@@ -0,0 +1,1286 @@
+/*
+ * 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.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;
+ }
+
+ private final Resources mResources;
+ private DisplayMetrics mMetrics;
+ private AssetManager mAssets;
+
+ private boolean mRecycled;
+
+ /*package*/ XmlBlock.Parser mXml;
+ /*package*/ Resources.Theme mTheme;
+ /*package*/ int[] mData;
+ /*package*/ long mDataAddress;
+ /*package*/ int[] mIndices;
+ /*package*/ long mIndicesAddress;
+ /*package*/ int mLength;
+ /*package*/ TypedValue mValue = new TypedValue();
+
+ private void resize(int len) {
+ mLength = len;
+ final int dataLen = len * AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_STRING) {
+ final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ if (cookie < 0) {
+ return mXml.getPooledString(
+ data[index+AssetManager.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
+ */
+ public String getNonConfigurationString(@StyleableRes int index,
+ @Config int allowedChangingConfigs) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
+ data[index + AssetManager.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 will return whether
+ * it is equal to zero. 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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA] != 0;
+ }
+
+ 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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ }
+
+ 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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type == TypedValue.TYPE_FLOAT) {
+ return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
+ } else if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ } 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*AssetManager.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*AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ } 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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimension(
+ data[index + AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(
+ data[index + AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(
+ data[index+AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(
+ data[index+AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(
+ data[index + AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return defValue;
+ } else if (type == TypedValue.TYPE_FRACTION) {
+ return TypedValue.complexToFraction(
+ data[index+AssetManager.STYLE_DATA], base, pbase);
+ } 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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
+ final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
+ if (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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+ return data[index + AssetManager.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*AssetManager.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*AssetManager.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*AssetManager.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*AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ return mData[index + AssetManager.STYLE_TYPE];
+ }
+
+ /**
+ * 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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ return type != TypedValue.TYPE_NULL
+ || data[index+AssetManager.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*AssetManager.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
+ public int[] extractThemeAttrs() {
+ return extractThemeAttrs(null);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ 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 * AssetManager.STYLE_NUM_ENTRIES;
+ if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ // Not an attribute, ignore.
+ continue;
+ }
+
+ // Null the entry so that we can safely call getZzz().
+ data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
+
+ final int attr = data[index + AssetManager.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 * AssetManager.STYLE_NUM_ENTRIES;
+ final int type = data[index + AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ continue;
+ }
+ changingConfig |= ActivityInfo.activityInfoConfigNativeToJava(
+ data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+ }
+ return changingConfig;
+ }
+
+ private boolean getValueAt(int index, TypedValue outValue) {
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ return false;
+ }
+ outValue.type = type;
+ outValue.data = data[index+AssetManager.STYLE_DATA];
+ outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+ outValue.density = data[index+AssetManager.STYLE_DENSITY];
+ outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
+ return true;
+ }
+
+ private CharSequence loadStringValueAt(int index) {
+ final int[] data = mData;
+ final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ if (cookie < 0) {
+ if (mXml != null) {
+ return mXml.getPooledString(
+ data[index+AssetManager.STYLE_DATA]);
+ }
+ return null;
+ }
+ return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.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 00000000..faa8852b
--- /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 00000000..e6b95741
--- /dev/null
+++ b/android/content/res/XmlBlock.java
@@ -0,0 +1,535 @@
+/*
+ * 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.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 {
+ private static final boolean DEBUG=false;
+
+ 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);
+ }
+
+ 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());
+ }
+ }
+ }
+
+ public XmlResourceParser newParser() {
+ synchronized (this) {
+ if (mNative != 0) {
+ return new Parser(nativeCreateParseState(mNative), this);
+ }
+ return null;
+ }
+ }
+
+ /*package*/ final class Parser implements XmlResourceParser {
+ Parser(long parseState, XmlBlock block) {
+ mParseState = parseState;
+ mBlock = block;
+ block.mOpenCount++;
+ }
+
+ 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);
+ }
+
+ /*package*/ long mParseState;
+ 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(AssetManager assets, long xmlBlock) {
+ mAssets = assets;
+ mNative = xmlBlock;
+ mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
+ }
+
+ private 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);
+ 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);
+}
diff --git a/android/content/res/XmlResourceParser.java b/android/content/res/XmlResourceParser.java
new file mode 100644
index 00000000..5af49d4d
--- /dev/null
+++ b/android/content/res/XmlResourceParser.java
@@ -0,0 +1,36 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+
+import android.util.AttributeSet;
+
+/**
+ * The XML parsing interface returned for an XML resource. This is a standard
+ * XmlPullParser interface, as well as an extended AttributeSet interface and
+ * an additional close() method on this interface for the client to indicate
+ * when it is done reading the resource.
+ */
+public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
+ /**
+ * Close this interface to the resource. Calls on the interface are no
+ * longer value after this call.
+ */
+ public void close();
+}
+