diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/content | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-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')
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> + * <intent-filter> + * <action android:name="android.content.SyncAdapter" /> + * </intent-filter> + * <meta-data android:name="android.content.SyncAdapter" + * android:resource="@xml/syncadapter" /> + * </pre> + * The <code>android:resource</code> attribute must point to a resource that looks like: + * <pre> + * <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" + * android:contentAuthority="authority" + * android:accountType="accountType" + * android:userVisible="true|false" + * android:supportsUploading="true|false" + * android:allowParallelSyncs="true|false" + * android:isAlwaysSyncable="true|false" + * android:syncAdapterSettingsAction="ACTION_OF_SETTINGS_ACTIVITY" + * /> + * </pre> + * <ul> + * <li>The <code>android:contentAuthority</code> and <code>android:accountType</code> attributes + * indicate which content authority and for which account types this sync adapter serves. + * <li><code>android:userVisible</code> defaults to true and controls whether or not this sync + * adapter shows up in the Sync Settings screen. + * <li><code>android:supportsUploading</code> defaults + * to true and if true an upload-only sync will be requested for all syncadapters associated + * with an authority whenever that authority's content provider does a + * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)} + * with syncToNetwork set to true. + * <li><code>android:allowParallelSyncs</code> defaults to false and if true indicates that + * the sync adapter can handle syncs for multiple accounts at the same time. Otherwise + * the SyncManager will wait until the sync adapter is not in use before requesting that + * it sync an account's data. + * <li><code>android:isAlwaysSyncable</code> defaults to false and if true tells the SyncManager + * to 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 <receiver>} + * tag in your <code>AndroidManifest.xml</code>. + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using BroadcastReceiver, read the + * <a href="{@docRoot}guide/components/broadcasts.html">Broadcasts</a> developer guide.</p></div> + * + */ +public abstract class BroadcastReceiver { + 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 <receiver> tag, + * then the object is no longer alive after returning from this + * function.</b> This means you should not perform any operations that + * return a result to you asynchronously. If you need to perform any follow up + * background work, schedule a {@link android.app.job.JobService} with + * {@link android.app.job.JobScheduler}. + * + * If you wish to interact with a service that is already running and previously + * bound using {@link android.content.Context#bindService(Intent, ServiceConnection, int) bindService()}, + * you can use {@link #peekService}. + * + * <p>The Intent filters used in {@link android.content.Context#registerReceiver} + * and in application manifests are <em>not</em> guaranteed to be exclusive. They + * are hints to the operating system about how to find suitable recipients. It is + * possible for senders to force delivery to specific recipients, bypassing filter + * resolution. For this reason, {@link #onReceive(Context, Intent) onReceive()} + * implementations should respond only to known actions, ignoring any unexpected + * Intents that they may receive. + * + * @param context The Context in which the receiver is running. + * @param intent The Intent being received. + */ + public abstract void onReceive(Context context, Intent intent); + + /** + * This can be called by an application in {@link #onReceive} to allow + * it to keep the broadcast active after returning from that function. + * This does <em>not</em> change the expectation of being relatively + * responsive to the broadcast, but does allow + * the implementation to move work related to it over to another thread + * to avoid glitching the main UI thread due to disk IO. + * + * <p>As a general rule, broadcast receivers are allowed to run for up to 10 seconds + * before they system will consider them non-responsive and ANR the app. Since these usually + * execute on the app's main thread, they are already bound by the ~5 second time limit + * of various operations that can happen there (not to mention just avoiding UI jank), so + * the receive limit is generally not of concern. However, once you use {@code goAsync}, though + * able to be off the main thread, the broadcast execution limit still applies, and that + * includes the time spent between calling this method and ultimately + * {@link PendingResult#finish() PendingResult.finish()}.</p> + * + * <p>If you are taking advantage of this method to have more time to execute, it is useful + * to know that the available time can be longer in certain situations. In particular, if + * the broadcast you are receiving is not a foreground broadcast (that is, the sender has not + * used {@link Intent#FLAG_RECEIVER_FOREGROUND}), then more time is allowed for the receivers + * to run, allowing them to execute for 30 seconds or even a bit more. This is something that + * receivers should rarely take advantage of (long work should be punted to another system + * facility such as {@link android.app.job.JobScheduler}, {@link android.app.Service}, or + * see especially {@link android.support.v4.app.JobIntentService}), but can be useful in + * certain rare cases where it is necessary to do some work as soon as the broadcast is + * delivered. Keep in mind that the work you do here will block further broadcasts until + * it completes, so taking advantage of this at all excessively can be counter-productive + * and cause later events to be received more slowly.</p> + * + * @return Returns a {@link PendingResult} representing the result of + * the active broadcast. The BroadcastRecord itself is no longer active; + * all data and other interaction must go through {@link PendingResult} + * APIs. The {@link PendingResult#finish PendingResult.finish()} method + * must be called once processing of the broadcast is done. + */ + public final PendingResult goAsync() { + PendingResult res = mPendingResult; + mPendingResult = null; + return res; + } + + /** + * Provide a binder to an already-bound service. This method is synchronous + * and will not start the target service if it is not present, so it is safe + * to call from {@link #onReceive}. + * + * For peekService() to return a non null {@link android.os.IBinder} interface + * the service must have published it before. In other words some component + * must have called {@link android.content.Context#bindService(Intent, ServiceConnection, int)} on it. + * + * @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)} + * @param service Identifies the already-bound service you wish to use. See + * {@link android.content.Context#bindService(Intent, ServiceConnection, int)} + * for more information. + */ + public IBinder peekService(Context myContext, Intent service) { + IActivityManager am = ActivityManager.getService(); + IBinder binder = null; + try { + service.prepareToLeaveProcess(myContext); + binder = am.peekService(service, service.resolveTypeIfNeeded( + myContext.getContentResolver()), myContext.getOpPackageName()); + } catch (RemoteException e) { + } + return binder; + } + + /** + * Change the current result code of this broadcast; only works with + * broadcasts sent through + * {@link Context#sendOrderedBroadcast(Intent, String) + * Context.sendOrderedBroadcast}. Often uses the + * Activity {@link android.app.Activity#RESULT_CANCELED} and + * {@link android.app.Activity#RESULT_OK} constants, though the + * actual meaning of this value is ultimately up to the broadcaster. + * + * <p class="note">This method does not work with non-ordered broadcasts such + * as those sent with {@link Context#sendBroadcast(Intent) + * Context.sendBroadcast}</p> + * + * @param code The new result code. + * + * @see #setResult(int, String, Bundle) + */ + public final void setResultCode(int code) { + checkSynchronousHint(); + mPendingResult.mResultCode = code; + } + + /** + * Retrieve the current result code, as set by the previous receiver. + * + * @return int The current result code. + */ + public final int getResultCode() { + return mPendingResult != null ? mPendingResult.mResultCode : 0; + } + + /** + * Change the current result data of this broadcast; only works with + * broadcasts sent through + * {@link Context#sendOrderedBroadcast(Intent, String) + * Context.sendOrderedBroadcast}. This is an arbitrary + * string whose interpretation is up to the broadcaster. + * + * <p><strong>This method does not work with non-ordered broadcasts such + * as those sent with {@link Context#sendBroadcast(Intent) + * Context.sendBroadcast}</strong></p> + * + * @param data The new result data; may be null. + * + * @see #setResult(int, String, Bundle) + */ + public final void setResultData(String data) { + checkSynchronousHint(); + mPendingResult.mResultData = data; + } + + /** + * Retrieve the current result data, as set by the previous receiver. + * Often this is null. + * + * @return String The current result data; may be null. + */ + public final String getResultData() { + return mPendingResult != null ? mPendingResult.mResultData : null; + } + + /** + * Change the current result extras of this broadcast; only works with + * broadcasts sent through + * {@link Context#sendOrderedBroadcast(Intent, String) + * Context.sendOrderedBroadcast}. This is a Bundle + * holding arbitrary data, whose interpretation is up to the + * broadcaster. Can be set to null. Calling this method completely + * replaces the current map (if any). + * + * <p><strong>This method does not work with non-ordered broadcasts such + * as those sent with {@link Context#sendBroadcast(Intent) + * Context.sendBroadcast}</strong></p> + * + * @param extras The new extra data map; may be null. + * + * @see #setResult(int, String, Bundle) + */ + public final void setResultExtras(Bundle extras) { + checkSynchronousHint(); + mPendingResult.mResultExtras = extras; + } + + /** + * Retrieve the current result extra data, as set by the previous receiver. + * Any changes you make to the returned Map will be propagated to the next + * receiver. + * + * @param makeMap If true then a new empty Map will be made for you if the + * current Map is null; if false you should be prepared to + * receive a null Map. + * + * @return Map The current extras map. + */ + public final Bundle getResultExtras(boolean makeMap) { + if (mPendingResult == null) { + return null; + } + Bundle e = mPendingResult.mResultExtras; + if (!makeMap) return e; + if (e == null) mPendingResult.mResultExtras = e = new Bundle(); + return e; + } + + /** + * Change all of the result data returned from this broadcasts; only works + * with broadcasts sent through + * {@link Context#sendOrderedBroadcast(Intent, String) + * Context.sendOrderedBroadcast}. All current result data is replaced + * by the value given to this method. + * + * <p><strong>This method does not work with non-ordered broadcasts such + * as those sent with {@link Context#sendBroadcast(Intent) + * Context.sendBroadcast}</strong></p> + * + * @param code The new result code. Often uses the + * Activity {@link android.app.Activity#RESULT_CANCELED} and + * {@link android.app.Activity#RESULT_OK} constants, though the + * actual meaning of this value is ultimately up to the broadcaster. + * @param data The new result data. This is an arbitrary + * string whose interpretation is up to the broadcaster; may be null. + * @param extras The new extra data map. This is a Bundle + * holding arbitrary data, whose interpretation is up to the + * broadcaster. Can be set to null. This completely + * replaces the current map (if any). + */ + public final void setResult(int code, String data, Bundle extras) { + checkSynchronousHint(); + mPendingResult.mResultCode = code; + mPendingResult.mResultData = data; + mPendingResult.mResultExtras = extras; + } + + /** + * Returns the flag indicating whether or not this receiver should + * abort the current broadcast. + * + * @return True if the broadcast should be aborted. + */ + public final boolean getAbortBroadcast() { + return mPendingResult != null ? mPendingResult.mAbortBroadcast : false; + } + + /** + * Sets the flag indicating that this receiver should abort the + * current broadcast; only works with broadcasts sent through + * {@link Context#sendOrderedBroadcast(Intent, String) + * Context.sendOrderedBroadcast}. This will prevent + * any other broadcast receivers from receiving the broadcast. It will still + * call {@link #onReceive} of the BroadcastReceiver that the caller of + * {@link Context#sendOrderedBroadcast(Intent, String) + * Context.sendOrderedBroadcast} passed in. + * + * <p><strong>This method does not work with non-ordered broadcasts such + * as those sent with {@link Context#sendBroadcast(Intent) + * Context.sendBroadcast}</strong></p> + */ + public final void abortBroadcast() { + checkSynchronousHint(); + mPendingResult.mAbortBroadcast = true; + } + + /** + * Clears the flag indicating that this receiver should abort the current + * broadcast. + */ + public final void clearAbortBroadcast() { + if (mPendingResult != null) { + mPendingResult.mAbortBroadcast = false; + } + } + + /** + * Returns true if the receiver is currently processing an ordered + * broadcast. + */ + public final boolean isOrderedBroadcast() { + return mPendingResult != null ? mPendingResult.mOrderedHint : false; + } + + /** + * Returns true if the receiver is currently processing the initial + * value of a sticky broadcast -- that is, the value that was last + * broadcast and is currently held in the sticky cache, so this is + * not directly the result of a broadcast right now. + */ + public final boolean isInitialStickyBroadcast() { + return mPendingResult != null ? mPendingResult.mInitialStickyHint : false; + } + + /** + * For internal use, sets the hint about whether this BroadcastReceiver is + * running in ordered mode. + */ + public final void setOrderedHint(boolean isOrdered) { + // Accidentally left in the SDK. + } + + /** + * For internal use to set the result data that is active. @hide + */ + 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 */*. + * @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 */* 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 */*, if the caller does not have specific type + * requirements; in this case the content provider will pick its best + * type matching the pattern. + * @param opts Additional options from the client. The definitions of + * these are specific to the content provider being called. + * + * @return Returns a new AssetFileDescriptor from which the client can + * read data of the desired type. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the data. + * @throws IllegalArgumentException Throws IllegalArgumentException if the + * content provider does not support the requested MIME type. + * + * @see #getStreamTypes(Uri, String) + * @see #openAssetFile(Uri, String) + * @see ClipDescription#compareMimeTypes(String, String) + */ + public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri, + @NonNull String mimeTypeFilter, @Nullable Bundle opts) throws FileNotFoundException { + if ("*/*".equals(mimeTypeFilter)) { + // If they can take anything, the untyped open call is good enough. + return openAssetFile(uri, "r"); + } + String baseType = getType(uri); + if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) { + // Use old untyped open call if this provider has a type for this + // URI and it matches the request. + return openAssetFile(uri, "r"); + } + throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter); + } + + + /** + * Called by a client to open a read-only stream containing data of a + * particular MIME type. This is like {@link #openAssetFile(Uri, String)}, + * except the file can only be read-only and the content provider may + * perform data conversions to generate data of the desired type. + * + * <p>The default implementation compares the given mimeType against the + * result of {@link #getType(Uri)} and, if they match, simply calls + * {@link #openAssetFile(Uri, String)}. + * + * <p>See {@link ClipData} for examples of the use and implementation + * of this method. + * <p> + * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. + * + * <p class="note">For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}. + * You may also want to support other common columns if you have additional meta-data + * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED} + * in {@link android.provider.MediaStore.MediaColumns}.</p> + * + * @param uri The data in the content provider being queried. + * @param mimeTypeFilter The type of data the client desires. May be + * a pattern, such as */*, if the caller does not have specific type + * requirements; in this case the content provider will pick its best + * type matching the pattern. + * @param opts Additional options from the client. The definitions of + * these are specific to the content provider being called. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new AssetFileDescriptor from which the client can + * read data of the desired type. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the data. + * @throws IllegalArgumentException Throws IllegalArgumentException if the + * content provider does not support the requested MIME type. + * + * @see #getStreamTypes(Uri, String) + * @see #openAssetFile(Uri, String) + * @see ClipDescription#compareMimeTypes(String, String) + */ + 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 <provider_component_name>". + * + * @param fd The raw file descriptor that the dump is being sent to. + * @param writer The PrintWriter to which you should dump your state. This will be + * closed for you after you return. + * @param args additional arguments to the dump request. + */ + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("nothing to dump"); + } + + /** @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 */*, 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 "*/*". This allows such callers to benefit + * from any built-in data conversion that a provider implements. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile + * ContentProvider.openAssetFile}. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * file exists under the URI or the mode is invalid. + */ + public final @Nullable AssetFileDescriptor openAssetFileDescriptor(@NonNull Uri uri, + @NonNull String mode) throws FileNotFoundException { + return openAssetFileDescriptor(uri, mode, null); + } + + /** + * Open a raw file descriptor to access data under a URI. This + * interacts with the underlying {@link ContentProvider#openAssetFile} + * method of the provider associated with the given URI, to retrieve any file stored there. + * + * <h5>Accepts the following URI schemes:</h5> + * <ul> + * <li>content ({@link #SCHEME_CONTENT})</li> + * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li> + * <li>file ({@link #SCHEME_FILE})</li> + * </ul> + * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5> + * <p> + * A Uri object can be used to reference a resource in an APK file. The + * Uri should be one of the following formats: + * <ul> + * <li><code>android.resource://package_name/id_number</code><br/> + * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. + * For example <code>com.example.myapp</code><br/> + * <code>id_number</code> is the int form of the ID.<br/> + * The easiest way to construct this form is + * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre> + * </li> + * <li><code>android.resource://package_name/type/name</code><br/> + * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. + * For example <code>com.example.myapp</code><br/> + * <code>type</code> is the string form of the resource type. For example, <code>raw</code> + * or <code>drawable</code>. + * <code>name</code> is the string form of the resource name. That is, whatever the file + * name was in your res directory, without the type extension. + * The easiest way to construct this form is + * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre> + * </li> + * </ul> + * + * <p>Note that if this function is called for read-only input (mode is "r") + * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor} + * for you with a MIME type of "*/*". This allows such callers to benefit + * from any built-in data conversion that a provider implements. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile + * ContentProvider.openAssetFile}. + * @param cancellationSignal A signal to cancel the operation in progress, or null if + * none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * file exists under the URI or the mode is invalid. + */ + public final @Nullable AssetFileDescriptor openAssetFileDescriptor(@NonNull Uri uri, + @NonNull String mode, @Nullable CancellationSignal cancellationSignal) + throws FileNotFoundException { + 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 */*, which will allow the content provider to + * select a type, though there is no way for you to determine what type + * it is returning. + * @param opts Additional provider-dependent options. + * @return Returns a new ParcelFileDescriptor from which you can read the + * data stream from the provider. Note that this may be a pipe, meaning + * you can't seek in it. The only seek you should do is if the + * AssetFileDescriptor contains an offset, to move to that offset before + * reading. You own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * data of the desired type exists under the URI. + */ + public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri, + @NonNull String mimeType, @Nullable Bundle opts) throws FileNotFoundException { + return openTypedAssetFileDescriptor(uri, mimeType, opts, null); + } + + /** + * Open a raw file descriptor to access (potentially type transformed) + * data from a "content:" URI. This interacts with the underlying + * {@link ContentProvider#openTypedAssetFile} method of the provider + * associated with the given URI, to retrieve retrieve any appropriate + * data stream for the data stored there. + * + * <p>Unlike {@link #openAssetFileDescriptor}, this function only works + * with "content:" URIs, because content providers are the only facility + * with an associated MIME type to ensure that the returned data stream + * is of the desired type. + * + * <p>All text/* streams are encoded in UTF-8. + * + * @param uri The desired URI to open. + * @param mimeType The desired MIME type of the returned data. This can + * be a pattern such as */*, which will allow the content provider to + * select a type, though there is no way for you to determine what type + * it is returning. + * @param opts Additional provider-dependent options. + * @param cancellationSignal A signal to cancel the operation in progress, + * or null if none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor from which you can read the + * data stream from the provider. Note that this may be a pipe, meaning + * you can't seek in it. The only seek you should do is if the + * AssetFileDescriptor contains an offset, to move to that offset before + * reading. You own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * data of the desired type exists under the URI. + */ + public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri, + @NonNull String mimeType, @Nullable Bundle opts, + @Nullable CancellationSignal cancellationSignal) throws FileNotFoundException { + 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 "content" (content://) scheme. +* +*<p> +* Content URIs have the syntax +*</p> +*<p> +* <code>content://<em>authority</em>/<em>path</em>/<em>id</em></code> +*</p> +*<dl> +* <dt> +* <code>content:</code> +* </dt> +* <dd> +* The scheme portion of the URI. This is always set to {@link +* android.content.ContentResolver#SCHEME_CONTENT ContentResolver.SCHEME_CONTENT} (value +* <code>content://</code>). +* </dd> +* <dt> +* <em>authority</em> +* </dt> +* <dd> +* A string that identifies the entire content provider. All the content URIs for the provider +* start with this string. To guarantee a unique authority, providers should consider +* using an authority that is the same as the provider class' package identifier. +* </dd> +* <dt> +* <em>path</em> +* </dt> +* <dd> +* Zero or more segments, separated by a forward slash (<code>/</code>), that identify +* some subset of the provider's data. Most providers use the path part to identify +* individual tables. Individual segments in the path are often called +* "directories" although they do not refer to file directories. The right-most +* segment in a path is often called a "twig" +* </dd> +* <dt> +* <em>id</em> +* </dt> +* <dd> +* A unique numeric identifier for a single row in the subset of data identified by the +* preceding path part. Most providers recognize content URIs that contain an id part +* and give them special handling. A table that contains a column named <code>_ID</code> +* often expects the id part to be a particular value for that column. +* </dd> +*</dl> +* +*/ +public class ContentUris { + + /** + * Converts the last path segment to a long. + * + * <p>This supports a common convention for content URIs where an ID is + * stored in the last segment. + * + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * @throws NumberFormatException if the last segment isn't a number + * + * @return the long conversion of the last segment or -1 if the path is + * empty + */ + public static long parseId(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 + *` + * @see #startActivity(Intent, Bundle) + * @see PackageManager#resolveActivity + */ + public abstract void startActivity(@RequiresPermission Intent intent); + + /** + * Version of {@link #startActivity(Intent)} that allows you to specify the + * user the activity will be started for. This is not available to applications + * that are not pre-installed on the system image. + * @param intent The description of the activity to start. + * @param user The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException + * @hide + */ + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_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 + * + * @see #startActivity(Intent) + * @see PackageManager#resolveActivity + */ + public abstract void startActivity(@RequiresPermission Intent intent, + @Nullable Bundle options); + + /** + * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the + * user the activity will be started for. This is not available to applications + * that are not pre-installed on the system image. + * @param intent The description of the activity to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * @param userId The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException + * @hide + */ + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_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 + * + * @see #startActivities(Intent[], Bundle) + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(@RequiresPermission Intent[] intents); + + /** + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + * <p>This method throws {@link ActivityNotFoundException} + * if there was no Activity found for <em>any</em> given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle)} + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws ActivityNotFoundException + * + * @see #startActivities(Intent[]) + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(@RequiresPermission Intent[] intents, Bundle options); + + /** + * @hide + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + * <p>This method throws {@link ActivityNotFoundException} + * if there was no Activity found for <em>any</em> given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * @param options Additional options for how the Activity should be started. + * @param userHandle The user for whom to launch the activities + * See {@link android.content.Context#startActivity(Intent, Bundle)} + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws ActivityNotFoundException + * + * @see #startActivities(Intent[]) + * @see PackageManager#resolveActivity + */ + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + 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 > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkPermission(String, int, int) + */ + public abstract void enforcePermission( + @NonNull String permission, int pid, int uid, @Nullable String message); + + /** + * If the calling process of an IPC you are handling has not been + * granted a particular permission, throw a {@link + * SecurityException}. This is basically the same as calling + * {@link #enforcePermission(String, int, int, String)} with the + * pid and uid returned by {@link android.os.Binder#getCallingPid} + * and {@link android.os.Binder#getCallingUid}. One important + * difference is that if you are not currently processing an IPC, + * this function will always throw the SecurityException. This is + * done to protect against accidentally leaking permissions; you + * can use {@link #enforceCallingOrSelfPermission} to avoid this + * protection. + * + * @param permission The name of the permission being checked. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingPermission(String) + */ + public abstract void enforceCallingPermission( + @NonNull String permission, @Nullable String message); + + /** + * If neither you nor the calling process of an IPC you are + * handling has been granted a particular permission, throw a + * {@link SecurityException}. This is the same as {@link + * #enforceCallingPermission}, except it grants your own + * permissions if you are not currently processing an IPC. Use + * with care! + * + * @param permission The name of the permission being checked. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingOrSelfPermission(String) + */ + public abstract void enforceCallingOrSelfPermission( + @NonNull String permission, @Nullable String message); + + /** + * Grant permission to access a specific Uri to another package, regardless + * of whether that package has general permission to access the Uri's + * content provider. This can be used to grant specific, temporary + * permissions, typically in response to user interaction (such as the + * user opening an attachment that you would like someone else to + * display). + * + * <p>Normally you should use {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} or + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} with the Intent being used to + * start an activity instead of this function directly. If you use this + * function directly, you should be sure to call + * {@link #revokeUriPermission} when the target should no longer be allowed + * to access it. + * + * <p>To succeed, the content provider owning the Uri must have set the + * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions + * grantUriPermissions} attribute in its manifest or included the + * {@link android.R.styleable#AndroidManifestGrantUriPermission + * <grant-uri-permissions>} tag. + * + * @param toPackage The package you would like to allow to access the Uri. + * @param uri The Uri you would like to grant access to. + * @param modeFlags The desired access modes. + * + * @see #revokeUriPermission + */ + public abstract void grantUriPermission(String toPackage, Uri uri, + @Intent.GrantUriMode int modeFlags); + + /** + * Remove all permissions to access a particular content provider Uri + * that were previously added with {@link #grantUriPermission} or <em>any other</em> mechanism. + * The given Uri will match all previously granted Uris that are the same or a + * sub-path of the given Uri. That is, revoking "content://foo/target" will + * revoke both "content://foo/target" and "content://foo/target/sub", but not + * "content://foo". It will not remove any prefix grants that exist at a + * higher level. + * + * <p>Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, if you did not have + * regular permission access to a Uri, but had received access to it through + * a specific Uri permission grant, you could not revoke that grant with this + * function and a {@link SecurityException} would be thrown. As of + * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this function will not throw a security + * exception, but will remove whatever permission grants to the Uri had been given to the app + * (or none).</p> + * + * <p>Unlike {@link #revokeUriPermission(String, Uri, int)}, this method impacts all permission + * grants matching the given Uri, for any package they had been granted to, through any + * mechanism this had happened (such as indirectly through the clipboard, activity launch, + * service start, etc). That means this can be potentially dangerous to use, as it can + * revoke grants that another app could be strongly expecting to stick around.</p> + * + * @param uri The Uri you would like to revoke access to. + * @param modeFlags The access modes to revoke. + * + * @see #grantUriPermission + */ + public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags); + + /** + * Remove permissions to access a particular content provider Uri + * that were previously added with {@link #grantUriPermission} for a specific target + * package. The given Uri will match all previously granted Uris that are the same or a + * sub-path of the given Uri. That is, revoking "content://foo/target" will + * revoke both "content://foo/target" and "content://foo/target/sub", but not + * "content://foo". It will not remove any prefix grants that exist at a + * higher level. + * + * <p>Unlike {@link #revokeUriPermission(Uri, int)}, this method will <em>only</em> + * revoke permissions that had been explicitly granted through {@link #grantUriPermission} + * and only for the package specified. Any matching grants that have happened through + * other mechanisms (clipboard, activity launching, service starting, etc) will not be + * removed.</p> + * + * @param toPackage The package you had previously granted access to. + * @param uri The Uri you would like to revoke access to. + * @param modeFlags The access modes to revoke. + * + * @see #grantUriPermission + */ + public abstract void revokeUriPermission(String toPackage, Uri uri, + @Intent.AccessUriMode int modeFlags); + + /** + * Determine whether a particular process and user ID has been granted + * permission to access a specific URI. This only checks for permissions + * that have been explicitly granted -- if the given process/uid has + * more general access to the URI's content provider then this check will + * always fail. + * + * @param uri The uri that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The access modes to check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the given + * pid/uid is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkCallingUriPermission + */ + @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") + @PackageManager.PermissionResult + public abstract int checkUriPermission(Uri uri, int pid, int uid, + @Intent.AccessUriMode int modeFlags); + + /** @hide */ + @PackageManager.PermissionResult + public abstract int checkUriPermission(Uri uri, int pid, int uid, + @Intent.AccessUriMode int modeFlags, IBinder callerToken); + + /** + * Determine whether the calling process and user ID has been + * granted permission to access a specific URI. This is basically + * the same as calling {@link #checkUriPermission(Uri, int, int, + * int)} with the pid and uid returned by {@link + * android.os.Binder#getCallingPid} and {@link + * android.os.Binder#getCallingUid}. One important difference is + * that if you are not currently processing an IPC, this function + * will always fail. + * + * @param uri The uri that is being checked. + * @param modeFlags The access modes to check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller + * is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkUriPermission(Uri, int, int, int) + */ + @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") + @PackageManager.PermissionResult + public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags); + + /** + * Determine whether the calling process of an IPC <em>or you</em> has been granted + * permission to access a specific URI. This is the same as + * {@link #checkCallingUriPermission}, except it grants your own permissions + * if you are not currently processing an IPC. Use with care! + * + * @param uri The uri that is being checked. + * @param modeFlags The access modes to check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller + * is allowed to access that uri, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see #checkCallingUriPermission + */ + @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") + @PackageManager.PermissionResult + public abstract int checkCallingOrSelfUriPermission(Uri uri, + @Intent.AccessUriMode int modeFlags); + + /** + * Check both a Uri and normal permission. This allows you to perform + * both {@link #checkPermission} and {@link #checkUriPermission} in one + * call. + * + * @param uri The Uri whose permission is to be checked, or null to not + * do this check. + * @param readPermission The permission that provides overall read access, + * or null to not do this check. + * @param writePermission The permission that provides overall write + * access, or null to not do this check. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The access modes to check. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the caller + * is allowed to access that uri or holds one of the given permissions, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + */ + @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)") + @PackageManager.PermissionResult + public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, + @Nullable String writePermission, int pid, int uid, + @Intent.AccessUriMode int modeFlags); + + /** + * If a particular process and user ID has not been granted + * permission to access a specific URI, throw {@link + * SecurityException}. This only checks for permissions that have + * been explicitly granted -- if the given process/uid has more + * general access to the URI's content provider then this check + * will always fail. + * + * @param uri The uri that is being checked. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The access modes to enforce. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkUriPermission(Uri, int, int, int) + */ + public abstract void enforceUriPermission( + Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message); + + /** + * If the calling process and user ID has not been granted + * permission to access a specific URI, throw {@link + * SecurityException}. This is basically the same as calling + * {@link #enforceUriPermission(Uri, int, int, int, String)} with + * the pid and uid returned by {@link + * android.os.Binder#getCallingPid} and {@link + * android.os.Binder#getCallingUid}. One important difference is + * that if you are not currently processing an IPC, this function + * will always throw a SecurityException. + * + * @param uri The uri that is being checked. + * @param modeFlags The access modes to enforce. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingUriPermission(Uri, int) + */ + public abstract void enforceCallingUriPermission( + Uri uri, @Intent.AccessUriMode int modeFlags, String message); + + /** + * If the calling process of an IPC <em>or you</em> has not been + * granted permission to access a specific URI, throw {@link + * SecurityException}. This is the same as {@link + * #enforceCallingUriPermission}, except it grants your own + * permissions if you are not currently processing an IPC. Use + * with care! + * + * @param uri The uri that is being checked. + * @param modeFlags The access modes to enforce. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkCallingOrSelfUriPermission(Uri, int) + */ + public abstract void enforceCallingOrSelfUriPermission( + Uri uri, @Intent.AccessUriMode int modeFlags, String message); + + /** + * Enforce both a Uri and normal permission. This allows you to perform + * both {@link #enforcePermission} and {@link #enforceUriPermission} in one + * call. + * + * @param uri The Uri whose permission is to be checked, or null to not + * do this check. + * @param readPermission The permission that provides overall read access, + * or null to not do this check. + * @param writePermission The permission that provides overall write + * access, or null to not do this check. + * @param pid The process ID being checked against. Must be > 0. + * @param uid The user ID being checked against. A uid of 0 is the root + * user, which will pass every permission check. + * @param modeFlags The access modes to enforce. + * @param message A message to include in the exception if it is thrown. + * + * @see #checkUriPermission(Uri, String, String, int, int, int) + */ + public abstract void enforceUriPermission( + @Nullable Uri uri, @Nullable String readPermission, + @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags, + @Nullable String message); + + /** @hide */ + @IntDef(flag = true, prefix = { "CONTEXT_" }, value = { + CONTEXT_INCLUDE_CODE, + CONTEXT_IGNORE_SECURITY, + CONTEXT_RESTRICTED, + CONTEXT_DEVICE_PROTECTED_STORAGE, + CONTEXT_CREDENTIAL_PROTECTED_STORAGE, + CONTEXT_REGISTER_PACKAGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CreatePackageOptions {} + + /** + * Flag for use with {@link #createPackageContext}: include the application + * code with the context. This means loading code into the caller's + * process, so that {@link #getClassLoader()} can be used to instantiate + * the application's classes. Setting this flags imposes security + * restrictions on what application context you can access; if the + * requested application can not be safely loaded into your process, + * java.lang.SecurityException will be thrown. If this flag is not set, + * there will be no restrictions on the packages that can be loaded, + * but {@link #getClassLoader} will always return the default system + * class loader. + */ + public static final int CONTEXT_INCLUDE_CODE = 0x00000001; + + /** + * Flag for use with {@link #createPackageContext}: ignore any security + * restrictions on the Context being requested, allowing it to always + * be loaded. For use with {@link #CONTEXT_INCLUDE_CODE} to allow code + * to be loaded into a process even when it isn't safe to do so. Use + * with extreme care! + */ + public static final int CONTEXT_IGNORE_SECURITY = 0x00000002; + + /** + * Flag for use with {@link #createPackageContext}: a restricted context may + * disable specific features. For instance, a View associated with a restricted + * context would ignore particular XML attributes. + */ + public static final int CONTEXT_RESTRICTED = 0x00000004; + + /** + * Flag for use with {@link #createPackageContext}: point all file APIs at + * device-protected storage. + * + * @hide + */ + public static final int CONTEXT_DEVICE_PROTECTED_STORAGE = 0x00000008; + + /** + * Flag for use with {@link #createPackageContext}: point all file APIs at + * credential-protected storage. + * + * @hide + */ + public static final int CONTEXT_CREDENTIAL_PROTECTED_STORAGE = 0x00000010; + + /** + * @hide Used to indicate we should tell the activity manager about the process + * loading this code. + */ + public static final int CONTEXT_REGISTER_PACKAGE = 0x40000000; + + /** + * Return a new Context object for the given application name. This + * Context is the same as what the named application gets when it is + * launched, containing the same resources and class loader. Each call to + * this method returns a new instance of a Context object; Context objects + * are not shared, however they share common state (Resources, ClassLoader, + * etc) so the Context instance itself is fairly lightweight. + * + * <p>Throws {@link android.content.pm.PackageManager.NameNotFoundException} if there is no + * application with the given package name. + * + * <p>Throws {@link java.lang.SecurityException} if the Context requested + * can not be loaded into the caller's process for security reasons (see + * {@link #CONTEXT_INCLUDE_CODE} for more information}. + * + * @param packageName Name of the application's package. + * @param flags Option flags. + * + * @return A {@link Context} for the application. + * + * @throws SecurityException + * @throws PackageManager.NameNotFoundException if there is no application with + * the given package name. + */ + public abstract Context createPackageContext(String packageName, + @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException; + + /** + * Similar to {@link #createPackageContext(String, int)}, but with a + * different {@link UserHandle}. For example, {@link #getContentResolver()} + * will open any {@link Uri} as the given user. + * + * @hide + */ + 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> + * <row uri="content://contacts/people"> + * <Col column = "name" value = "foo feebe "/> + * <Col column = "addr" value = "Tx"/> + * </row></pre> + * <br/> + * Delete, it must be in order of uri, select, and arg: + * <pre> + * <del uri="content://contacts/people" select="name=? and addr=?" + * arg1 = "foo feebe" arg2 ="Tx"/></pre> + * <br/> + * Use first row's uri to insert into another table, + * content://contacts/people/1/phones: + * <pre> + * <row uri="content://contacts/people"> + * <col column = "name" value = "foo feebe"/> + * <col column = "addr" value = "Tx"/> + * <row postfix="phones"> + * <col column="number" value="512-514-6535"/> + * </row> + * <row postfix="phones"> + * <col column="cell" value="512-514-6535"/> + * </row> + * </row></pre> + * <br/> + * Insert multiple rows in to same table and same attributes: + * <pre> + * <row uri="content://contacts/people" > + * <row> + * <col column= "name" value = "foo feebe"/> + * <col column= "addr" value = "Tx"/> + * </row> + * <row> + * </row> + * </row></pre> + * + * @hide + */ +public class DefaultDataHandler implements ContentInsertHandler { + private final static String ROW = "row"; + private final static String COL = "col"; + private final static String URI_STR = "uri"; + private final static String POSTFIX = "postfix"; + private final static String DEL = "del"; + private final static String SELECT = "select"; + private final static String ARG = "arg"; + + private Stack<Uri> mUris = new Stack<Uri>(); + private ContentValues mValues; + private ContentResolver mContentResolver; + + public void insert(ContentResolver contentResolver, InputStream in) + throws IOException, SAXException { + mContentResolver = contentResolver; + Xml.parse(in, Xml.Encoding.UTF_8, this); + } + + public void insert(ContentResolver contentResolver, String in) + throws SAXException { + mContentResolver = contentResolver; + Xml.parse(in, this); + } + + private void parseRow(Attributes atts) throws SAXException { + String uriStr = atts.getValue(URI_STR); + Uri uri; + if (uriStr != null) { + // case 1 + uri = Uri.parse(uriStr); + if (uri == null) { + throw new SAXException("attribute " + + atts.getValue(URI_STR) + " parsing failure"); + } + + } else if (mUris.size() > 0){ + // case 2 + String postfix = atts.getValue(POSTFIX); + if (postfix != null) { + uri = Uri.withAppendedPath(mUris.lastElement(), + postfix); + } else { + uri = mUris.lastElement(); + } + } else { + throw new SAXException("attribute parsing failure"); + } + + mUris.push(uri); + + } + + private Uri insertRow() { + Uri u = mContentResolver.insert(mUris.lastElement(), mValues); + mValues = null; + return u; + } + + public void startElement(String uri, String localName, String name, + Attributes atts) throws SAXException { + if (ROW.equals(localName)) { + if (mValues != null) { + // case 2, <Col> before <Row> insert last uri + if (mUris.empty()) { + throw new SAXException("uri is empty"); + } + Uri nextUri = insertRow(); + if (nextUri == null) { + throw new SAXException("insert to uri " + + mUris.lastElement().toString() + " failure"); + } else { + // make sure the stack lastElement save uri for more than one row + mUris.pop(); + mUris.push(nextUri); + parseRow(atts); + } + } else { + int attrLen = atts.getLength(); + if (attrLen == 0) { + // case 3, share same uri as last level + mUris.push(mUris.lastElement()); + } else { + parseRow(atts); + } + } + } else if (COL.equals(localName)) { + int attrLen = atts.getLength(); + if (attrLen != 2) { + throw new SAXException("illegal attributes number " + attrLen); + } + String key = atts.getValue(0); + String value = atts.getValue(1); + if (key != null && key.length() > 0 && value != null && value.length() > 0) { + if (mValues == null) { + mValues = new ContentValues(); + } + mValues.put(key, value); + } else { + throw new SAXException("illegal attributes value"); + } + } else if (DEL.equals(localName)){ + Uri u = Uri.parse(atts.getValue(URI_STR)); + if (u == null) { + throw new SAXException("attribute " + + atts.getValue(URI_STR) + " parsing failure"); + } + int attrLen = atts.getLength() - 2; + if (attrLen > 0) { + String[] selectionArgs = new String[attrLen]; + for (int i = 0; i < attrLen; i++) { + selectionArgs[i] = atts.getValue(i+2); + } + mContentResolver.delete(u, atts.getValue(1), selectionArgs); + } else if (attrLen == 0){ + mContentResolver.delete(u, atts.getValue(1), null); + } else { + mContentResolver.delete(u, null, null); + } + + } else { + throw new SAXException("unknown element: " + localName); + } + } + + public void endElement(String uri, String localName, String name) + throws SAXException { + if (ROW.equals(localName)) { + if (mUris.empty()) { + throw new SAXException("uri mismatch"); + } + if (mValues != null) { + insertRow(); + } + mUris.pop(); + } + } + + + public void characters(char[] ch, int start, int length) + throws SAXException { + // TODO Auto-generated method stub + + } + + public void endDocument() throws SAXException { + // TODO Auto-generated method stub + + } + + public void endPrefixMapping(String prefix) throws SAXException { + // TODO Auto-generated method stub + + } + + public void ignorableWhitespace(char[] ch, int start, int length) + throws SAXException { + // TODO Auto-generated method stub + + } + + public void processingInstruction(String target, String data) + throws SAXException { + // TODO Auto-generated method stub + + } + + public void setDocumentLocator(Locator locator) { + // TODO Auto-generated method stub + + } + + public void skippedEntity(String name) throws SAXException { + // TODO Auto-generated method stub + + } + + public void startDocument() throws SAXException { + // TODO Auto-generated method stub + + } + + public void startPrefixMapping(String prefix, String uri) + throws SAXException { + // TODO Auto-generated method stub + + } + +} diff --git a/android/content/DialogInterface.java b/android/content/DialogInterface.java new file mode 100644 index 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 <intent-filter> descriptions in the + * installed application packages. (Plus, in the case of broadcasts, any {@link BroadcastReceiver} + * objects explicitly registered with {@link Context#registerReceiver}.) More + * details on this can be found in the documentation on the {@link + * IntentFilter} class.</p> + * + * <p>There are three pieces of information in the Intent that are used for + * resolution: the action, type, and category. Using this information, a query + * is done on the {@link PackageManager} for a component that can handle the + * intent. The appropriate component is determined based on the intent + * information supplied in the <code>AndroidManifest.xml</code> file as + * follows:</p> + * + * <ul> + * <li> <p>The <b>action</b>, if given, must be listed by the component as + * one it handles.</p> + * <li> <p>The <b>type</b> is retrieved from the Intent's data, if not + * already supplied in the Intent. Like the action, if a type is + * included in the intent (either explicitly or implicitly in its + * data), then this must be listed by the component as one it handles.</p> + * <li> For data that is not a <code>content:</code> URI and where no explicit + * type is included in the Intent, instead the <b>scheme</b> of the + * intent data (such as <code>http:</code> or <code>mailto:</code>) is + * considered. Again like the action, if we are matching a scheme it + * must be listed by the component as one it can handle. + * <li> <p>The <b>categories</b>, if supplied, must <em>all</em> be listed + * by the activity as categories it handles. That is, if you include + * the categories {@link #CATEGORY_LAUNCHER} and + * {@link #CATEGORY_ALTERNATIVE}, then you will only resolve to components + * with an intent that lists <em>both</em> of those categories. + * Activities will very often need to support the + * {@link #CATEGORY_DEFAULT} so that they can be found by + * {@link Context#startActivity Context.startActivity()}.</p> + * </ul> + * + * <p>For example, consider the Note Pad sample application that + * allows 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> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + * package="<i>com.android.notepad</i>"> + * <application android:icon="@drawable/app_notes" + * android:label="@string/app_name"> + * + * <provider class=".NotePadProvider" + * android:authorities="<i>com.google.provider.NotePad</i>" /> + * + * <activity class=".NotesList" android:label="@string/title_notes_list"> + * <intent-filter> + * <action android:name="android.intent.action.MAIN" /> + * <category android:name="android.intent.category.LAUNCHER" /> + * </intent-filter> + * <intent-filter> + * <action android:name="android.intent.action.VIEW" /> + * <action android:name="android.intent.action.EDIT" /> + * <action android:name="android.intent.action.PICK" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /> + * </intent-filter> + * <intent-filter> + * <action android:name="android.intent.action.GET_CONTENT" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /> + * </intent-filter> + * </activity> + * + * <activity class=".NoteEditor" android:label="@string/title_note"> + * <intent-filter android:label="@string/resolve_edit"> + * <action android:name="android.intent.action.VIEW" /> + * <action android:name="android.intent.action.EDIT" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /> + * </intent-filter> + * + * <intent-filter> + * <action android:name="android.intent.action.INSERT" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /> + * </intent-filter> + * + * </activity> + * + * <activity class=".TitleEditor" android:label="@string/title_edit_title" + * android:theme="@android:style/Theme.Dialog"> + * <intent-filter android:label="@string/resolve_title"> + * <action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <category android:name="android.intent.category.ALTERNATIVE" /> + * <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> + * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /> + * </intent-filter> + * </activity> + * + * </application> + * </manifest></pre> + * + * <p>The first activity, + * <code>com.android.notepad.NotesList</code>, serves as our main + * entry into the app. It can do three things as described by its three intent + * templates: + * <ol> + * <li><pre> + * <intent-filter> + * <action android:name="{@link #ACTION_MAIN android.intent.action.MAIN}" /> + * <category android:name="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" /> + * </intent-filter></pre> + * <p>This provides a top-level entry into the NotePad application: the standard + * MAIN action is a main entry point (not requiring any other information in + * the Intent), and the LAUNCHER category says that this entry point should be + * listed in the application launcher.</p> + * <li><pre> + * <intent-filter> + * <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" /> + * <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" /> + * <action android:name="{@link #ACTION_PICK android.intent.action.PICK}" /> + * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /> + * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /> + * </intent-filter></pre> + * <p>This declares the things that the activity can do on a directory of + * notes. The type being supported is given with the <type> tag, where + * <code>vnd.android.cursor.dir/vnd.google.note</code> is a URI from which + * a Cursor of zero or more items (<code>vnd.android.cursor.dir</code>) can + * be retrieved which holds our note pad data (<code>vnd.google.note</code>). + * The activity allows the user to view or edit the directory of data (via + * the VIEW and EDIT actions), or to pick a particular note and return it + * to the caller (via the PICK action). Note also the DEFAULT category + * supplied here: this is <em>required</em> for the + * {@link Context#startActivity Context.startActivity} method to resolve your + * activity when its component name is not explicitly specified.</p> + * <li><pre> + * <intent-filter> + * <action android:name="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" /> + * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /> + * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /> + * </intent-filter></pre> + * <p>This filter describes the ability to return to the caller a note selected by + * the user without needing to know where it came from. The data type + * <code>vnd.android.cursor.item/vnd.google.note</code> is a URI from which + * a Cursor of exactly one (<code>vnd.android.cursor.item</code>) item can + * be retrieved which contains our note pad data (<code>vnd.google.note</code>). + * The GET_CONTENT action is similar to the PICK action, where the activity + * will return to its caller a piece of data selected by the user. Here, + * however, the caller specifies the type of data they desire instead of + * the type of data the user will be picking from.</p> + * </ol> + * + * <p>Given these capabilities, the following intents will resolve to the + * NotesList activity:</p> + * + * <ul> + * <li> <p><b>{ action=android.app.action.MAIN }</b> matches all of the + * activities that can be used as top-level entry points into an + * application.</p> + * <li> <p><b>{ action=android.app.action.MAIN, + * category=android.app.category.LAUNCHER }</b> is the actual intent + * used by the Launcher to populate its top-level list.</p> + * <li> <p><b>{ action=android.intent.action.VIEW + * data=content://com.google.provider.NotePad/notes }</b> + * displays a list of all the notes under + * "content://com.google.provider.NotePad/notes", which + * the user can browse through and see the details on.</p> + * <li> <p><b>{ action=android.app.action.PICK + * data=content://com.google.provider.NotePad/notes }</b> + * provides a list of the notes under + * "content://com.google.provider.NotePad/notes", from which + * the user can pick a note whose data URL is returned back to the caller.</p> + * <li> <p><b>{ action=android.app.action.GET_CONTENT + * type=vnd.android.cursor.item/vnd.google.note }</b> + * is similar to the pick action, but allows the caller to specify the + * kind of data they want back so that the system can find the appropriate + * activity to pick something of that data type.</p> + * </ul> + * + * <p>The second activity, + * <code>com.android.notepad.NoteEditor</code>, shows the user a single + * note entry and allows them to edit it. It can do two things as described + * by its two intent templates: + * <ol> + * <li><pre> + * <intent-filter android:label="@string/resolve_edit"> + * <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" /> + * <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" /> + * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /> + * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /> + * </intent-filter></pre> + * <p>The first, primary, purpose of this activity is to let the user interact + * with a single note, as decribed by the MIME type + * <code>vnd.android.cursor.item/vnd.google.note</code>. The activity can + * either VIEW a note or allow the user to EDIT it. Again we support the + * DEFAULT category to allow the activity to be launched without explicitly + * specifying its component.</p> + * <li><pre> + * <intent-filter> + * <action android:name="{@link #ACTION_INSERT android.intent.action.INSERT}" /> + * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /> + * <data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /> + * </intent-filter></pre> + * <p>The secondary use of this activity is to insert a new note entry into + * an existing directory of notes. This is used when the user creates a new + * note: the INSERT action is executed on the directory of notes, causing + * this activity to run and have the user create the new note data which + * it then adds to the content provider.</p> + * </ol> + * + * <p>Given these capabilities, the following intents will resolve to the + * NoteEditor activity:</p> + * + * <ul> + * <li> <p><b>{ action=android.intent.action.VIEW + * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b> + * shows the user the content of note <var>{ID}</var>.</p> + * <li> <p><b>{ action=android.app.action.EDIT + * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b> + * allows the user to edit the content of note <var>{ID}</var>.</p> + * <li> <p><b>{ action=android.app.action.INSERT + * data=content://com.google.provider.NotePad/notes }</b> + * creates a new, empty note in the notes list at + * "content://com.google.provider.NotePad/notes" + * and allows the user to edit it. If they keep their changes, the URI + * of the newly created note is returned to the caller.</p> + * </ul> + * + * <p>The last activity, + * <code>com.android.notepad.TitleEditor</code>, allows the user to + * edit the title of a note. This could be implemented as a class that the + * application directly invokes (by explicitly setting its component in + * the Intent), but here we show a way you can publish alternative + * operations on existing data:</p> + * + * <pre> + * <intent-filter android:label="@string/resolve_title"> + * <action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" /> + * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /> + * <category android:name="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" /> + * <category android:name="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" /> + * <data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /> + * </intent-filter></pre> + * + * <p>In the single intent template here, we + * have created our own private action called + * <code>com.android.notepad.action.EDIT_TITLE</code> which means to + * edit the title of a note. It must be invoked on a specific note + * (data type <code>vnd.android.cursor.item/vnd.google.note</code>) like the previous + * view and edit actions, but here displays and edits the title contained + * in the note data. + * + * <p>In addition to supporting the default category as usual, our title editor + * also supports two other standard categories: ALTERNATIVE and + * SELECTED_ALTERNATIVE. Implementing + * these categories allows others to find the special action it provides + * without directly knowing about it, through the + * {@link android.content.pm.PackageManager#queryIntentActivityOptions} method, or + * more often to build dynamic menu items with + * {@link android.view.Menu#addIntentOptions}. Note that in the intent + * template here was also supply an explicit name for the template + * (via <code>android:label="@string/resolve_title"</code>) to better control + * what the user sees when presented with this activity as an alternative + * action to the data they are viewing. + * + * <p>Given these capabilities, the following intent will resolve to the + * TitleEditor activity:</p> + * + * <ul> + * <li> <p><b>{ action=com.android.notepad.action.EDIT_TITLE + * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b> + * displays and allows the user to edit the title associated + * with note <var>{ID}</var>.</p> + * </ul> + * + * <h3>Standard Activity Actions</h3> + * + * <p>These are the current standard actions that Intent defines for launching + * activities (usually through {@link Context#startActivity}. The most + * important, and by far most frequently used, are {@link #ACTION_MAIN} and + * {@link #ACTION_EDIT}. + * + * <ul> + * <li> {@link #ACTION_MAIN} + * <li> {@link #ACTION_VIEW} + * <li> {@link #ACTION_ATTACH_DATA} + * <li> {@link #ACTION_EDIT} + * <li> {@link #ACTION_PICK} + * <li> {@link #ACTION_CHOOSER} + * <li> {@link #ACTION_GET_CONTENT} + * <li> {@link #ACTION_DIAL} + * <li> {@link #ACTION_CALL} + * <li> {@link #ACTION_SEND} + * <li> {@link #ACTION_SENDTO} + * <li> {@link #ACTION_ANSWER} + * <li> {@link #ACTION_INSERT} + * <li> {@link #ACTION_DELETE} + * <li> {@link #ACTION_RUN} + * <li> {@link #ACTION_SYNC} + * <li> {@link #ACTION_PICK_ACTIVITY} + * <li> {@link #ACTION_SEARCH} + * <li> {@link #ACTION_WEB_SEARCH} + * <li> {@link #ACTION_FACTORY_TEST} + * </ul> + * + * <h3>Standard Broadcast Actions</h3> + * + * <p>These are the current standard actions that Intent defines for receiving + * broadcasts (usually through {@link Context#registerReceiver} or a + * <receiver> tag in a manifest). + * + * <ul> + * <li> {@link #ACTION_TIME_TICK} + * <li> {@link #ACTION_TIME_CHANGED} + * <li> {@link #ACTION_TIMEZONE_CHANGED} + * <li> {@link #ACTION_BOOT_COMPLETED} + * <li> {@link #ACTION_PACKAGE_ADDED} + * <li> {@link #ACTION_PACKAGE_CHANGED} + * <li> {@link #ACTION_PACKAGE_REMOVED} + * <li> {@link #ACTION_PACKAGE_RESTARTED} + * <li> {@link #ACTION_PACKAGE_DATA_CLEARED} + * <li> {@link #ACTION_PACKAGES_SUSPENDED} + * <li> {@link #ACTION_PACKAGES_UNSUSPENDED} + * <li> {@link #ACTION_UID_REMOVED} + * <li> {@link #ACTION_BATTERY_CHANGED} + * <li> {@link #ACTION_POWER_CONNECTED} + * <li> {@link #ACTION_POWER_DISCONNECTED} + * <li> {@link #ACTION_SHUTDOWN} + * </ul> + * + * <h3>Standard Categories</h3> + * + * <p>These are the current standard categories that can be used to further + * clarify an Intent via {@link #addCategory}. + * + * <ul> + * <li> {@link #CATEGORY_DEFAULT} + * <li> {@link #CATEGORY_BROWSABLE} + * <li> {@link #CATEGORY_TAB} + * <li> {@link #CATEGORY_ALTERNATIVE} + * <li> {@link #CATEGORY_SELECTED_ALTERNATIVE} + * <li> {@link #CATEGORY_LAUNCHER} + * <li> {@link #CATEGORY_INFO} + * <li> {@link #CATEGORY_HOME} + * <li> {@link #CATEGORY_PREFERENCE} + * <li> {@link #CATEGORY_TEST} + * <li> {@link #CATEGORY_CAR_DOCK} + * <li> {@link #CATEGORY_DESK_DOCK} + * <li> {@link #CATEGORY_LE_DESK_DOCK} + * <li> {@link #CATEGORY_HE_DESK_DOCK} + * <li> {@link #CATEGORY_CAR_MODE} + * <li> {@link #CATEGORY_APP_MARKET} + * <li> {@link #CATEGORY_VR_HOME} + * </ul> + * + * <h3>Standard Extra Data</h3> + * + * <p>These are the current standard fields that can be used as extra data via + * {@link #putExtra}. + * + * <ul> + * <li> {@link #EXTRA_ALARM_COUNT} + * <li> {@link #EXTRA_BCC} + * <li> {@link #EXTRA_CC} + * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME} + * <li> {@link #EXTRA_DATA_REMOVED} + * <li> {@link #EXTRA_DOCK_STATE} + * <li> {@link #EXTRA_DOCK_STATE_HE_DESK} + * <li> {@link #EXTRA_DOCK_STATE_LE_DESK} + * <li> {@link #EXTRA_DOCK_STATE_CAR} + * <li> {@link #EXTRA_DOCK_STATE_DESK} + * <li> {@link #EXTRA_DOCK_STATE_UNDOCKED} + * <li> {@link #EXTRA_DONT_KILL_APP} + * <li> {@link #EXTRA_EMAIL} + * <li> {@link #EXTRA_INITIAL_INTENTS} + * <li> {@link #EXTRA_INTENT} + * <li> {@link #EXTRA_KEY_EVENT} + * <li> {@link #EXTRA_ORIGINATING_URI} + * <li> {@link #EXTRA_PHONE_NUMBER} + * <li> {@link #EXTRA_REFERRER} + * <li> {@link #EXTRA_REMOTE_INTENT_TOKEN} + * <li> {@link #EXTRA_REPLACING} + * <li> {@link #EXTRA_SHORTCUT_ICON} + * <li> {@link #EXTRA_SHORTCUT_ICON_RESOURCE} + * <li> {@link #EXTRA_SHORTCUT_INTENT} + * <li> {@link #EXTRA_STREAM} + * <li> {@link #EXTRA_SHORTCUT_NAME} + * <li> {@link #EXTRA_SUBJECT} + * <li> {@link #EXTRA_TEMPLATE} + * <li> {@link #EXTRA_TEXT} + * <li> {@link #EXTRA_TITLE} + * <li> {@link #EXTRA_UID} + * </ul> + * + * <h3>Flags</h3> + * + * <p>These are the possible flags that can be used in the Intent via + * {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list + * of all possible flags. + */ +public class Intent implements Parcelable, Cloneable { + private static final String 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<RestrictionEntry></code>. It can also + * contain an extra {@link #EXTRA_RESTRICTIONS_INTENT}, which is of type <code>Intent</code>. + * The activity specified by that intent will be launched for a result which must contain + * one of the extras {@link #EXTRA_RESTRICTIONS_LIST} or {@link #EXTRA_RESTRICTIONS_BUNDLE}. + * The keys and values of the returned restrictions will be persisted. + * @see RestrictionEntry + */ + public static final String ACTION_GET_RESTRICTION_ENTRIES = + "android.intent.action.GET_RESTRICTION_ENTRIES"; + + /** + * Sent the first time a user is starting, to allow system apps to + * perform one time initialization. (This will not be seen by third + * party applications because a newly initialized user does not have any + * third party applications installed for it.) This is sent early in + * starting the user, around the time the home app is started, before + * {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + */ + public static final String ACTION_USER_INITIALIZE = + "android.intent.action.USER_INITIALIZE"; + + /** + * Sent when a user switch is happening, causing the process's user to be + * brought to the foreground. This is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. It is sent to the user that is going to the + * foreground. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + */ + public static final String ACTION_USER_FOREGROUND = + "android.intent.action.USER_FOREGROUND"; + + /** + * Sent when a user switch is happening, causing the process's user to be + * sent to the background. This is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. It is sent to the user that is going to the + * background. This is sent as a foreground + * broadcast, since it is part of a visible user interaction; be as quick + * as possible when handling it. + */ + public static final String ACTION_USER_BACKGROUND = + "android.intent.action.USER_BACKGROUND"; + + /** + * Broadcast sent to the system when a user is added. Carries an extra + * EXTRA_USER_HANDLE that has the userHandle of the new user. It is sent to + * all running users. You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. + * @hide + */ + 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 <intent-filter> of this action + * describing to the user what it does. + * + * <p>The action of IntentFilter with this category is important in that it + * describes the specific action the target will perform. This generally + * should not be a generic action (such as {@link #ACTION_VIEW}, but rather + * a specific name such as "com.android.camera.action.CROP. Only one + * alternative of any particular action will be shown to the user, so using + * a specific action like this makes sure that your alternative will be + * displayed while also allowing other applications to provide their own + * overrides of that particular action. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE"; + /** + * Set if the activity should be considered as an alternative selection + * action to the data the user has currently selected. This is like + * {@link #CATEGORY_ALTERNATIVE}, but is used in activities showing a list + * of items from which the user can select, giving them alternatives to the + * default action that will be performed on it. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE"; + /** + * Intended to be used as a tab inside of a containing TabActivity. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_TAB = "android.intent.category.TAB"; + /** + * Should be displayed in the top-level launcher. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; + /** + * Indicates an activity optimized for Leanback mode, and that should + * be displayed in the Leanback launcher. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER"; + /** + * Indicates 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<RestrictionEntry></code>. + */ + public static final String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list"; + + /** + * Extra sent in the intent to the BroadcastReceiver that handles + * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is a Bundle containing + * the restrictions as key/value pairs. + */ + public static final String EXTRA_RESTRICTIONS_BUNDLE = + "android.intent.extra.restrictions_bundle"; + + /** + * Extra used in the response from a BroadcastReceiver that handles + * {@link #ACTION_GET_RESTRICTION_ENTRIES}. + */ + public static final String EXTRA_RESTRICTIONS_INTENT = + "android.intent.extra.restrictions_intent"; + + /** + * Extra used to communicate a set of acceptable MIME types. The type of the + * extra is {@code String[]}. Values may be a combination of concrete MIME + * types (such as "image/png") and/or partial MIME types (such as + * "audio/*"). + * + * @see #ACTION_GET_CONTENT + * @see #ACTION_OPEN_DOCUMENT + */ + public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES"; + + /** + * Optional extra for {@link #ACTION_SHUTDOWN} that allows the sender to qualify that + * this shutdown is only for the user space of the system, not a complete shutdown. + * When this is true, hardware devices can use this information to determine that + * they shouldn't do a complete shutdown of their device since this is not a + * complete shutdown down to the kernel, but only user space restarting. + * The default if not supplied is false. + */ + public static final String EXTRA_SHUTDOWN_USERSPACE_ONLY + = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY"; + + /** + * Optional 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 "*/*". + * + * @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> + * <?xml version="1.0" encoding="utf-8"?> + * <restrictions xmlns:android="http://schemas.android.com/apk/res/android" > + * <restriction + * android:key="string" + * android:title="string resource" + * android:restrictionType=["bool" | "string" | "integer" + * | "choice" | "multi-select" | "hidden" + * | "bundle" | "bundle_array"] + * android:description="string resource" + * android:entries="string-array resource" + * android:entryValues="string-array resource" + * android:defaultValue="reference" > + * <restriction ... /> + * ... + * </restriction> + * <restriction ... /> + * ... + * </restrictions> + * </pre> + * <p> + * The attributes for each restriction depend on the restriction type. + * <p> + * <ul> + * <li><code>key</code>, <code>title</code> and <code>restrictionType</code> are mandatory.</li> + * <li><code>entries</code> and <code>entryValues</code> are required if <code>restrictionType + * </code> is <code>choice</code> or <code>multi-select</code>.</li> + * <li><code>defaultValue</code> is optional and its type depends on the + * <code>restrictionType</code></li> + * <li><code>hidden</code> type must have a <code>defaultValue</code> and will + * not be shown to the administrator. It can be used to pass along data that cannot be modified, + * such as a version code.</li> + * <li><code>description</code> is meant to describe the restriction in more detail to the + * administrator controlling the values, if the title is not sufficient.</li> + * </ul> + * <p> + * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested + * restriction elements. + * <p> + * In your manifest's <code>application</code> section, add the meta-data tag to point to + * the restrictions XML file as shown below: + * <pre> + * <application ... > + * <meta-data android:name="android.content.APP_RESTRICTIONS" + * android:resource="@xml/app_restrictions" /> + * ... + * </application> + * </pre> + * + * @see RestrictionEntry + * @see RestrictionsReceiver + * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) + * @see DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle) + */ +@SystemService(Context.RESTRICTIONS_SERVICE) +public class RestrictionsManager { + + private static final String TAG = "RestrictionsManager"; + + /** + * Broadcast intent delivered when a response is received for a permission request. The + * application should not interrupt the user by coming to the foreground if it isn't + * currently in the foreground. It can either post a notification informing + * the user of the response or wait until the next time the user launches the app. + * <p> + * For instance, if the user requested permission to make an in-app purchase, + * the app can post a notification that the request had been approved or denied. + * <p> + * The broadcast Intent carries the following extra: + * {@link #EXTRA_RESPONSE_BUNDLE}. + */ + public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = + "android.content.action.PERMISSION_RESPONSE_RECEIVED"; + + /** + * Broadcast intent sent to the Restrictions Provider to handle a permission request from + * an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME}, + * {@link #EXTRA_REQUEST_TYPE}, {@link #EXTRA_REQUEST_ID} and {@link #EXTRA_REQUEST_BUNDLE}. + * The Restrictions Provider will handle the request and respond back to the + * RestrictionsManager, when a response is available, by calling + * {@link #notifyPermissionResponse}. + * <p> + * The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN} + * permission to ensure that only the system can send the broadcast. + */ + public static final String ACTION_REQUEST_PERMISSION = + "android.content.action.REQUEST_PERMISSION"; + + /** + * Activity intent that is optionally implemented by the Restrictions Provider package + * to challenge for an administrator PIN or password locally on the device. Apps will + * call this intent using {@link Activity#startActivityForResult}. On a successful + * response, {@link Activity#onActivityResult} will return a resultCode of + * {@link Activity#RESULT_OK}. + * <p> + * The intent must contain {@link #EXTRA_REQUEST_BUNDLE} as an extra and the bundle must + * contain at least {@link #REQUEST_KEY_MESSAGE} for the activity to display. + * <p> + * @see #createLocalApprovalIntent() + */ + public static final String ACTION_REQUEST_LOCAL_APPROVAL = + "android.content.action.REQUEST_LOCAL_APPROVAL"; + + /** + * The package name of the application making the request. + * <p> + * Type: String + */ + public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; + + /** + * The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. + * <p> + * Type: String + */ + public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE"; + + /** + * The request ID passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. + * <p> + * Type: String + */ + public static final String EXTRA_REQUEST_ID = "android.content.extra.REQUEST_ID"; + + /** + * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. + * <p> + * Type: {@link PersistableBundle} + */ + public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE"; + + /** + * Contains a response from the administrator for specific request. + * The bundle contains the following information, at least: + * <ul> + * <li>{@link #REQUEST_KEY_ID}: The request ID.</li> + * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li> + * </ul> + * <p> + * Type: {@link PersistableBundle} + */ + public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE"; + + /** + * Request type for a simple question, with a possible title and icon. + * <p> + * Required keys are: {@link #REQUEST_KEY_MESSAGE} + * <p> + * Optional keys are + * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE}, + * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}. + */ + public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval"; + + /** + * Key for request ID contained in the request bundle. + * <p> + * App-generated request ID to identify the specific request when receiving + * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_ID = "android.request.id"; + + /** + * Key for request data contained in the request bundle. + * <p> + * Optional, typically used to identify the specific data that is being referred to, + * such as the unique identifier for a movie or book. This is not used for display + * purposes and is more like a cookie. This value is returned in the + * {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DATA = "android.request.data"; + + /** + * Key for request title contained in the request bundle. + * <p> + * Optional, typically used as the title of any notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_TITLE = "android.request.title"; + + /** + * Key for request message contained in the request bundle. + * <p> + * Required, shown as the actual message in a notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_MESSAGE = "android.request.mesg"; + + /** + * Key for request icon contained in the request bundle. + * <p> + * Optional, shown alongside the request message presented to the administrator + * who approves the request. The content must be a compressed image such as a + * PNG or JPEG, as a byte array. + * <p> + * Type: byte[] + */ + public static final String REQUEST_KEY_ICON = "android.request.icon"; + + /** + * Key for request approval button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the positive button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label"; + + /** + * Key for request rejection button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the negative button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label"; + + /** + * Key for issuing a new request, contained in the request bundle. If this is set to true, + * the Restrictions Provider must make a new request. If it is false or not specified, then + * the Restrictions Provider can return a cached response that has the same requestId, if + * available. If there's no cached response, it will issue a new one to the administrator. + * <p> + * Type: boolean + */ + public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request"; + + /** + * Key for the response result in the response bundle sent to the application, for a permission + * request. It indicates the status of the request. In some cases an additional message might + * be available in {@link #RESPONSE_KEY_MESSAGE}, to be displayed to the user. + * <p> + * Type: int + * <p> + * Possible values: {@link #RESULT_APPROVED}, {@link #RESULT_DENIED}, + * {@link #RESULT_NO_RESPONSE}, {@link #RESULT_UNKNOWN_REQUEST} or + * {@link #RESULT_ERROR}. + */ + public static final String RESPONSE_KEY_RESULT = "android.response.result"; + + /** + * Response result value indicating that the request was approved. + */ + public static final int RESULT_APPROVED = 1; + + /** + * Response result value indicating that the request was denied. + */ + public static final int RESULT_DENIED = 2; + + /** + * Response result value indicating that the request has not received a response yet. + */ + public static final int RESULT_NO_RESPONSE = 3; + + /** + * Response result value indicating that the request is unknown, when it's not a new + * request. + */ + public static final int RESULT_UNKNOWN_REQUEST = 4; + + /** + * Response result value indicating an error condition. Additional error code might be available + * in the response bundle, for the key {@link #RESPONSE_KEY_ERROR_CODE}. There might also be + * an associated error message in the response bundle, for the key + * {@link #RESPONSE_KEY_MESSAGE}. + */ + public static final int RESULT_ERROR = 5; + + /** + * Error code indicating that there was a problem with the request. + * <p> + * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle. + */ + public static final int RESULT_ERROR_BAD_REQUEST = 1; + + /** + * Error code indicating that there was a problem with the network. + * <p> + * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle. + */ + public static final int RESULT_ERROR_NETWORK = 2; + + /** + * Error code indicating that there was an internal error. + * <p> + * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle. + */ + public static final int RESULT_ERROR_INTERNAL = 3; + + /** + * Key for the optional error code in the response bundle sent to the application. + * <p> + * Type: int + * <p> + * Possible values: {@link #RESULT_ERROR_BAD_REQUEST}, {@link #RESULT_ERROR_NETWORK} or + * {@link #RESULT_ERROR_INTERNAL}. + */ + public static final String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode"; + + /** + * Key for the optional message in the response bundle sent to the application. + * <p> + * Type: String + */ + public static final String RESPONSE_KEY_MESSAGE = "android.response.msg"; + + /** + * Key for the optional timestamp of when the administrator responded to the permission + * request. It is an represented in milliseconds since January 1, 1970 00:00:00.0 UTC. + * <p> + * Type: long + */ + public static final String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp"; + + /** + * Name of the meta-data entry in the manifest that points to the XML file containing the + * application's available restrictions. + * @see #getManifestRestrictions(String) + */ + public static final String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS"; + + private static final String TAG_RESTRICTION = "restriction"; + + private final Context mContext; + 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"> + * <!-- Content provider for search suggestions --> + * <provider android:name="YourSuggestionProviderClass" + * android:authorities="your.suggestion.authority" /></pre> + * </li> + * <li>Please note that you <i>do not</i> instantiate this content provider directly from within + * your code. This is done automatically by the system Content Resolver, when the search dialog + * looks for suggestions.</li> + * <li>In order for the Content Resolver to do this, you must update your searchable activity's + * XML configuration file with information about your content provider. The following additions + * are usually sufficient: + * <pre class="prettyprint"> + * android:searchSuggestAuthority="your.suggestion.authority" + * android:searchSuggestSelection=" ? "</pre> + * </li> + * <li>In your searchable activities, capture any user-generated queries and record them + * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery + * SearchRecentSuggestions.saveRecentQuery()}.</li> + * </ul> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about using search suggestions in your application, read the + * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p> + * </div> + * + * @see android.provider.SearchRecentSuggestions + */ +public class SearchRecentSuggestionsProvider extends ContentProvider { + // debugging support + private static final String TAG = "SuggestionsProvider"; + + // client-provided configuration values + private String mAuthority; + private int mMode; + private boolean mTwoLineDisplay; + + // general database configuration and tables + private SQLiteOpenHelper mOpenHelper; + private static final String sDatabaseName = "suggestions.db"; + private static final String sSuggestions = "suggestions"; + private static final String ORDER_BY = "date DESC"; + private static final String NULL_COLUMN = "query"; + + // Table of database versions. Don't forget to update! + // NOTE: These version values are shifted left 8 bits (x 256) in order to create space for + // a small set of mode bitflags in the version int. + // + // 1 original implementation with queries, and 1 or 2 display columns + // 1->2 added UNIQUE constraint to display1 column + private static final int DATABASE_VERSION = 2 * 256; + + /** + * This mode bit configures the database to record recent queries. <i>required</i> + * + * @see #setupSuggestions(String, int) + */ + public static final int DATABASE_MODE_QUERIES = 1; + /** + * This mode bit configures the database to include a 2nd annotation line with each entry. + * <i>optional</i> + * + * @see #setupSuggestions(String, int) + */ + public static final int DATABASE_MODE_2LINES = 2; + + // Uri and query support + private static final int URI_MATCH_SUGGEST = 1; + + private Uri mSuggestionsUri; + private UriMatcher mUriMatcher; + + private String mSuggestSuggestionClause; + 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 <activity> and + * <receiver> tags. + */ +public class ActivityInfo extends ComponentInfo + implements Parcelable { + + // NOTE: When adding new data members be sure to update the copy-constructor, Parcel + // constructor, and writeToParcel. + + /** + * A style resource identifier (in the package's resources) of this + * activity's theme. From the "theme" attribute or, if not set, 0. + */ + public int theme; + + /** + * Constant corresponding to <code>standard</code> in + * the {@link android.R.attr#launchMode} attribute. + */ + public static final int LAUNCH_MULTIPLE = 0; + /** + * Constant corresponding to <code>singleTop</code> in + * the {@link android.R.attr#launchMode} attribute. + */ + public static final int LAUNCH_SINGLE_TOP = 1; + /** + * Constant corresponding to <code>singleTask</code> in + * the {@link android.R.attr#launchMode} attribute. + */ + public static final int LAUNCH_SINGLE_TASK = 2; + /** + * Constant corresponding to <code>singleInstance</code> in + * the {@link android.R.attr#launchMode} attribute. + */ + public static final int LAUNCH_SINGLE_INSTANCE = 3; + /** + * The launch mode style requested by the activity. From the + * {@link android.R.attr#launchMode} attribute, one of + * {@link #LAUNCH_MULTIPLE}, + * {@link #LAUNCH_SINGLE_TOP}, {@link #LAUNCH_SINGLE_TASK}, or + * {@link #LAUNCH_SINGLE_INSTANCE}. + */ + public int launchMode; + + /** + * Constant corresponding to <code>none</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_NONE = 0; + /** + * Constant corresponding to <code>intoExisting</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_INTO_EXISTING = 1; + /** + * Constant corresponding to <code>always</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_ALWAYS = 2; + /** + * Constant corresponding to <code>never</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_NEVER = 3; + /** + * The document launch mode style requested by the activity. From the + * {@link android.R.attr#documentLaunchMode} attribute, one of + * {@link #DOCUMENT_LAUNCH_NONE}, {@link #DOCUMENT_LAUNCH_INTO_EXISTING}, + * {@link #DOCUMENT_LAUNCH_ALWAYS}. + * + * <p>Modes DOCUMENT_LAUNCH_ALWAYS + * and DOCUMENT_LAUNCH_INTO_EXISTING are equivalent to {@link + * android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT + * Intent.FLAG_ACTIVITY_NEW_DOCUMENT} with and without {@link + * android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + * Intent.FLAG_ACTIVITY_MULTIPLE_TASK} respectively. + */ + public int documentLaunchMode; + + /** + * Constant corresponding to <code>persistRootOnly</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_ROOT_ONLY = 0; + /** + * Constant corresponding to <code>doNotPersist</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_NEVER = 1; + /** + * Constant corresponding to <code>persistAcrossReboots</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_ACROSS_REBOOTS = 2; + /** + * Value indicating how this activity is to be persisted across + * reboots for restoring in the Recents list. + * {@link android.R.attr#persistableMode} + */ + public int persistableMode; + + /** + * The maximum number of tasks rooted at this activity that can be in the recent task list. + * Refer to {@link android.R.attr#maxRecents}. + */ + public int maxRecents; + + /** + * Optional name of a permission required to be able to access this + * Activity. From the "permission" attribute. + */ + public String permission; + + /** + * The affinity this activity has for another task in the system. The + * string here is the name of the task, often the package name of the + * overall package. If null, the activity has no affinity. Set from the + * {@link android.R.attr#taskAffinity} attribute. + */ + public String taskAffinity; + + /** + * If this is an activity alias, this is the real activity class to run + * for it. Otherwise, this is null. + */ + public String targetActivity; + + /** + * Token used to string together multiple events within a single launch action. + * @hide + */ + public String launchToken; + + /** + * Activity can not be resized and always occupies the fullscreen area with all windows fully + * visible. + * @hide + */ + public static final int RESIZE_MODE_UNRESIZEABLE = 0; + /** + * Activity didn't explicitly request to be resizeable, but we are making it resizeable because + * of the SDK version it targets. Only affects apps with target SDK >= N where the app is + * implied to be resizeable if it doesn't explicitly set the attribute to any value. + * @hide + */ + public static final int RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION = 1; + /** + * Activity explicitly requested to be resizeable. + * @hide + */ + 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 + * <application> tag. + */ +public class ApplicationInfo extends PackageItemInfo implements Parcelable { + + /** + * Default task affinity of all activities in this application. See + * {@link ActivityInfo#taskAffinity} for more information. This comes + * from the "taskAffinity" attribute. + */ + public String taskAffinity; + + /** + * Optional name of a permission required to be able to access this + * application's components. From the "permission" attribute. + */ + public String permission; + + /** + * The name of the process this application should run in. From the + * "process" attribute or, if not set, the same as + * <var>packageName</var>. + */ + public String processName; + + /** + * Class implementing the Application object. From the "class" + * attribute. + */ + public String className; + + /** + * A style resource identifier (in the package's resources) of the + * description of an application. From the "description" attribute + * or, if not set, 0. + */ + public int descriptionRes; + + /** + * A style resource identifier (in the package's resources) of the + * default visual theme of the application. From the "theme" attribute + * or, if not set, 0. + */ + public int theme; + + /** + * Class implementing the Application's manage space + * functionality. From the "manageSpaceActivity" + * attribute. This is an optional attribute and will be null if + * applications don't specify it in their manifest + */ + public String manageSpaceActivityName; + + /** + * Class implementing the Application's backup functionality. From + * the "backupAgent" attribute. This is an optional attribute and + * will be null if the application does not specify it in its manifest. + * + * <p>If android:allowBackup is set to false, this attribute is ignored. + */ + public String backupAgentName; + + /** + * An optional attribute that indicates the app supports automatic backup of app data. + * <p>0 is the default and means the app's entire data folder + managed external storage will + * be backed up; + * Any negative value indicates the app does not support full-data backup, though it may still + * want to participate via the traditional key/value backup API; + * A positive number specifies an xml resource in which the application has defined its backup + * include/exclude criteria. + * <p>If android:allowBackup is set to false, this attribute is ignored. + * + * @see android.content.Context#getNoBackupFilesDir() + * @see #FLAG_ALLOW_BACKUP + * + * @hide + */ + 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 <application> tag. + */ + public static final int FLAG_DEBUGGABLE = 1<<1; + + /** + * Value for {@link #flags}: set to true if this application has code + * associated with it. Comes + * from {@link android.R.styleable#AndroidManifestApplication_hasCode + * android:hasCode} of the <application> tag. + */ + public static final int FLAG_HAS_CODE = 1<<2; + + /** + * Value for {@link #flags}: set to true if this application is persistent. + * Comes from {@link android.R.styleable#AndroidManifestApplication_persistent + * android:persistent} of the <application> tag. + */ + public static final int FLAG_PERSISTENT = 1<<3; + + /** + * Value for {@link #flags}: set to true if this application holds the + * {@link android.Manifest.permission#FACTORY_TEST} permission and the + * device is running in factory test mode. + */ + public static final int FLAG_FACTORY_TEST = 1<<4; + + /** + * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. + * Comes from {@link android.R.styleable#AndroidManifestApplication_allowTaskReparenting + * android:allowTaskReparenting} of the <application> tag. + */ + public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5; + + /** + * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. + * Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData + * android:allowClearUserData} of the <application> tag. + */ + public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6; + + /** + * Value for {@link #flags}: this is set if this application has been + * installed as an update to a built-in system application. + */ + public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7; + + /** + * Value for {@link #flags}: this is set if the application has specified + * {@link android.R.styleable#AndroidManifestApplication_testOnly + * android:testOnly} to be true. + */ + public static final int FLAG_TEST_ONLY = 1<<8; + + /** + * Value for {@link #flags}: true when the application's window can be + * reduced in size for smaller screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_smallScreens + * android:smallScreens}. + */ + public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9; + + /** + * Value for {@link #flags}: true when the application's window can be + * displayed on normal screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens + * android:normalScreens}. + */ + public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10; + + /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for larger screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens + * android:largeScreens}. + */ + public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; + + /** + * Value for {@link #flags}: true when the application knows how to adjust + * its UI for different screen sizes. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_resizeable + * android:resizeable}. + */ + public static final int FLAG_RESIZEABLE_FOR_SCREENS = 1<<12; + + /** + * Value for {@link #flags}: true when the application knows how to + * 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 <application> tag. + */ + public static final int FLAG_VM_SAFE_MODE = 1<<14; + + /** + * Value for {@link #flags}: set to <code>false</code> if the application does not wish + * to permit any OS-driven backups of its data; <code>true</code> otherwise. + * + * <p>Comes from the + * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} + * attribute of the <application> tag. + */ + public static final int FLAG_ALLOW_BACKUP = 1<<15; + + /** + * Value for {@link #flags}: set to <code>false</code> if the application must be kept + * in memory following a full-system restore operation; <code>true</code> otherwise. + * Ordinarily, during a full system restore operation each application is shut down + * following execution of its agent's onRestore() method. Setting this attribute to + * <code>false</code> prevents this. Most applications will not need to set this attribute. + * + * <p>If + * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} + * is set to <code>false</code> or no + * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent} + * is specified, this flag will be ignored. + * + * <p>Comes from the + * {@link android.R.styleable#AndroidManifestApplication_killAfterRestore android:killAfterRestore} + * attribute of the <application> tag. + */ + public static final int FLAG_KILL_AFTER_RESTORE = 1<<16; + + /** + * Value for {@link #flags}: Set to <code>true</code> if the application's backup + * agent claims to be able to handle restore data even "from the future," + * i.e. from versions of the application with a versionCode greater than + * the one currently installed on the device. <i>Use with caution!</i> By default + * this attribute is <code>false</code> and the Backup Manager will ensure that data + * from "future" versions of the application are never supplied during a restore operation. + * + * <p>If + * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup} + * is set to <code>false</code> or no + * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent} + * is specified, this flag will be ignored. + * + * <p>Comes from the + * {@link android.R.styleable#AndroidManifestApplication_restoreAnyVersion android:restoreAnyVersion} + * attribute of the <application> tag. + */ + public static final int FLAG_RESTORE_ANY_VERSION = 1<<17; + + /** + * Value for {@link #flags}: Set to true if the application is + * currently installed on external/removable/unprotected storage. Such + * applications may not be available if their storage is not currently + * mounted. When the storage it is on is not available, it will look like + * the application has been uninstalled (its .apk is no longer available) + * but its persistent data is not removed. + */ + public static final int FLAG_EXTERNAL_STORAGE = 1<<18; + + /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for extra large screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens + * android:xlargeScreens}. + */ + public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19; + + /** + * Value for {@link #flags}: true when the application has requested a + * large heap for its processes. Corresponds to + * {@link android.R.styleable#AndroidManifestApplication_largeHeap + * android:largeHeap}. + */ + public static final int FLAG_LARGE_HEAP = 1<<20; + + /** + * Value for {@link #flags}: true if this application's package is in + * the stopped state. + */ + public static final int FLAG_STOPPED = 1<<21; + + /** + * Value for {@link #flags}: true when the application is willing to support + * RTL (right to left). All activities will inherit this value. + * + * Set from the {@link android.R.attr#supportsRtl} attribute in the + * activity's manifest. + * + * Default value is false (no support for RTL). + */ + public static final int FLAG_SUPPORTS_RTL = 1<<22; + + /** + * Value for {@link #flags}: true if the application is currently + * installed for the calling user. + */ + public static final int FLAG_INSTALLED = 1<<23; + + /** + * Value for {@link #flags}: true if the application only has its + * data installed; the application package itself does not currently + * exist on the device. + */ + public static final int FLAG_IS_DATA_ONLY = 1<<24; + + /** + * Value for {@link #flags}: true if the application was declared to be a + * game, or false if it is a non-game application. + * + * @deprecated use {@link #CATEGORY_GAME} instead. + */ + @Deprecated + public static final int FLAG_IS_GAME = 1<<25; + + /** + * Value for {@link #flags}: {@code true} if the application asks that only + * full-data streaming backups of its data be performed even though it defines + * a {@link android.app.backup.BackupAgent BackupAgent}, which normally + * indicates that the app will manage its backed-up data via incremental + * key/value updates. + */ + public static final int FLAG_FULL_BACKUP_ONLY = 1<<26; + + /** + * Value for {@link #flags}: {@code true} if the application may use cleartext network traffic + * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP + * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use + * cleartext network traffic, in which case platform components (e.g., HTTP stacks, + * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext + * traffic. Third-party libraries are encouraged to honor this flag as well. + * + * <p>NOTE: {@code WebView} 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 <application> tag. + */ + public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; + + /** + * When set installer extracts native libs from .apk files. + */ + public static final int FLAG_EXTRACT_NATIVE_LIBS = 1<<28; + + /** + * Value for {@link #flags}: {@code true} when the application's rendering + * should be hardware accelerated. + */ + public static final int FLAG_HARDWARE_ACCELERATED = 1<<29; + + /** + * Value for {@link #flags}: true if this application's package is in + * the suspended state. + */ + public static final int FLAG_SUSPENDED = 1<<30; + + /** + * Value for {@link #flags}: true if code from this application will need to be + * loaded into other applications' processes. On devices that support multiple + * instruction sets, this implies the code might be loaded into a process that's + * using any of the devices supported instruction sets. + * + * <p> The system might treat such applications specially, for eg., by + * extracting the application's native libraries for all supported instruction + * sets or by compiling the application's dex code for all supported instruction + * sets. + */ + public static final int FLAG_MULTIARCH = 1 << 31; + + /** + * Flags associated with the application. Any combination of + * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE}, + * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and + * {@link #FLAG_ALLOW_TASK_REPARENTING} + * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, + * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, + * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, + * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS}, + * {@link #FLAG_RESIZEABLE_FOR_SCREENS}, + * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}, + * {@link #FLAG_ALLOW_BACKUP}, {@link #FLAG_KILL_AFTER_RESTORE}, + * {@link #FLAG_RESTORE_ANY_VERSION}, {@link #FLAG_EXTERNAL_STORAGE}, + * {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED}, + * {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED}, + * {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME}, + * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC}, + * {@link #FLAG_MULTIARCH}. + */ + public int flags = 0; + + /** + * Value for {@link #privateFlags}: true if the application is hidden via restrictions and for + * most purposes is considered as not installed. + * {@hide} + */ + public static final int PRIVATE_FLAG_HIDDEN = 1<<0; + + /** + * Value for {@link #privateFlags}: set to <code>true</code> if the application + * has reported that it is heavy-weight, and thus can not participate in + * the normal application lifecycle. + * + * <p>Comes from the + * android.R.styleable#AndroidManifestApplication_cantSaveState + * attribute of the <application> tag. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_CANT_SAVE_STATE = 1<<1; + + /** + * Value for {@link #privateFlags}: Set to 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 <supports-screens> tag. + */ + public int requiresSmallestWidthDp = 0; + + /** + * The maximum smallest screen width the application is designed for. If 0, + * nothing has been specified. Comes from + * {@link android.R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp + * android:compatibleWidthLimitDp} attribute of the <supports-screens> tag. + */ + public int compatibleWidthLimitDp = 0; + + /** + * The maximum smallest screen width the application will work on. If 0, + * nothing has been specified. Comes from + * {@link android.R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp + * android:largestWidthLimitDp} attribute of the <supports-screens> tag. + */ + public int largestWidthLimitDp = 0; + + /** + * Value indicating the maximum aspect ratio the application supports. + * <p> + * 0 means unset. + * @See {@link android.R.attr#maxAspectRatio}. + * @hide + */ + public float maxAspectRatio; + + /** @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 <manifest> tag of the app's + * AndroidManifest.xml. + * + * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs}, + * and {@link #splitPublicSourceDirs} arrays. + * Each key represents a split and its value is an array of splits. The first element of this + * array is the parent split, and the rest are configuration splits. These configuration splits + * have no dependencies themselves. + * Cycles do not exist because they are illegal and screened for during installation. + * + * May be null if no splits are installed, or if no dependencies exist between them. + * + * NOTE: Any change to the way split dependencies are stored must update the logic that + * creates the class loader context for dexopt (DexoptUtils#getClassLoaderContexts). + * + * @hide + */ + public SparseArray<int[]> splitDependencies; + + /** + * Full paths to the locations of extra resource packages (runtime overlays) + * this application uses. This field is only used if there are extra resource + * packages, otherwise it is null. + * + * {@hide} + */ + 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 + * <activity>, <receiver>, <service>, or + * <provider> tag. + */ + public boolean exported = false; + + /** + * Indicates if this component is aware of direct boot lifecycle, and can be + * safely run before the user has entered their credentials (such as a lock + * pattern or PIN). + */ + public boolean directBootAware = false; + + /** @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 <uses-configuration> and <uses-feature> tags. + */ +public class ConfigurationInfo implements Parcelable { + /** + * The kind of touch screen attached to the device. + * One of: {@link android.content.res.Configuration#TOUCHSCREEN_NOTOUCH}, + * {@link android.content.res.Configuration#TOUCHSCREEN_STYLUS}, + * {@link android.content.res.Configuration#TOUCHSCREEN_FINGER}. + */ + public int reqTouchScreen; + + /** + * Application's input method preference. + * One of: {@link android.content.res.Configuration#KEYBOARD_UNDEFINED}, + * {@link android.content.res.Configuration#KEYBOARD_NOKEYS}, + * {@link android.content.res.Configuration#KEYBOARD_QWERTY}, + * {@link android.content.res.Configuration#KEYBOARD_12KEY} + */ + public int reqKeyboardType; + + /** + * A flag indicating whether any keyboard is available. + * one of: {@link android.content.res.Configuration#NAVIGATION_UNDEFINED}, + * {@link android.content.res.Configuration#NAVIGATION_DPAD}, + * {@link android.content.res.Configuration#NAVIGATION_TRACKBALL}, + * {@link android.content.res.Configuration#NAVIGATION_WHEEL} + */ + public int reqNavigation; + + /** + * Value for {@link #reqInputFeatures}: if set, indicates that the application + * requires a hard keyboard + */ + public static final int INPUT_FEATURE_HARD_KEYBOARD = 0x00000001; + + /** + * Value for {@link #reqInputFeatures}: if set, indicates that the application + * requires a five way navigation device + */ + public static final int INPUT_FEATURE_FIVE_WAY_NAV = 0x00000002; + + /** + * Flags associated with the input features. Any combination of + * {@link #INPUT_FEATURE_HARD_KEYBOARD}, + * {@link #INPUT_FEATURE_FIVE_WAY_NAV} + */ + public int reqInputFeatures = 0; + + /** + * Default value for {@link #reqGlEsVersion}; + */ + public static final int GL_ES_VERSION_UNDEFINED = 0; + /** + * The GLES version used by an application. The upper order 16 bits represent the + * major version and the lower order 16 bits the minor version. + */ + public int reqGlEsVersion; + + public ConfigurationInfo() { + } + + public ConfigurationInfo(ConfigurationInfo orig) { + reqTouchScreen = orig.reqTouchScreen; + reqKeyboardType = orig.reqKeyboardType; + reqNavigation = orig.reqNavigation; + reqInputFeatures = orig.reqInputFeatures; + reqGlEsVersion = orig.reqGlEsVersion; + } + + public String toString() { + return "ConfigurationInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + " touchscreen = " + reqTouchScreen + + " inputMethod = " + reqKeyboardType + + " navigation = " + reqNavigation + + " reqInputFeatures = " + reqInputFeatures + + " reqGlEsVersion = " + reqGlEsVersion + "}"; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeInt(reqTouchScreen); + dest.writeInt(reqKeyboardType); + dest.writeInt(reqNavigation); + dest.writeInt(reqInputFeatures); + dest.writeInt(reqGlEsVersion); + } + + public static final 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 <instrumentation> tag. + */ +public class InstrumentationInfo extends PackageItemInfo implements Parcelable { + /** + * The name of the application package being instrumented. From the + * "package" attribute. + */ + public String targetPackage; + + /** + * Names of the process(es) this instrumentation will run in. If not specified, only + * runs in the main process of the targetPackage. Can either be a comma-separated list + * of process names or '*' for any process that launches to run targetPackage code. + */ + public String targetProcesses; + + /** + * Full path to the base APK for this application. + */ + public String sourceDir; + + /** + * Full path to the publicly available parts of {@link #sourceDir}, + * including resources and manifest. This may be different from + * {@link #sourceDir} if an application is forward locked. + */ + public String publicSourceDir; + + /** + * The names of all installed split APKs, ordered lexicographically. + */ + public String[] splitNames; + + /** + * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}. + */ + public String[] splitSourceDirs; + + /** + * Full path to the publicly available parts of {@link #splitSourceDirs}, + * including resources and manifest. This may be different from + * {@link #splitSourceDirs} if an application is forward locked. + * + * @see #splitSourceDirs + */ + public String[] splitPublicSourceDirs; + + /** + * Maps the dependencies between split APKs. All splits implicitly depend on the base APK. + * + * Available since platform version O. + * + * Only populated if the application opts in to isolated split loading via the + * {@link android.R.attr.isolatedSplits} attribute in the <manifest> tag of the app's + * AndroidManifest.xml. + * + * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs}, + * and {@link #splitPublicSourceDirs} arrays. + * Each key represents a split and its value is an array of splits. The first element of this + * array is the parent split, and the rest are configuration splits. These configuration splits + * have no dependencies themselves. + * Cycles do not exist because they are illegal and screened for during installation. + * + * May be null if no splits are installed, or if no dependencies exist between them. + * @hide + */ + public SparseArray<int[]> splitDependencies; + + /** + * Full path to a directory assigned to the package for its persistent data. + */ + public String dataDir; + + /** {@hide} */ + public String deviceProtectedDataDir; + /** {@hide} */ + public String credentialProtectedDataDir; + + /** {@hide} 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} & {@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—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 <manifest> 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 <manifest> + * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode} + * attribute. + */ + public int versionCode; + + /** + * The version name of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_versionName versionName} + * attribute. + */ + public String versionName; + + /** + * The revision number of the base APK for this package, as specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. + */ + public int baseRevisionCode; + + /** + * The revision number of any split APKs for this package, as specified by + * the <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. Indexes are a 1:1 mapping against {@link #splitNames}. + */ + public int[] splitRevisionCodes; + + /** + * The shared user ID name of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId} + * attribute. + */ + public String sharedUserId; + + /** + * The shared user ID label of this package, as specified by the <manifest> + * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel} + * attribute. + */ + public int sharedUserLabel; + + /** + * Information collected from the <application> tag, or null if + * there was none. + */ + public ApplicationInfo applicationInfo; + + /** + * The time at which the app was first installed. Units are as + * per {@link System#currentTimeMillis()}. + */ + public long firstInstallTime; + + /** + * The time at which the app was last updated. Units are as + * per {@link System#currentTimeMillis()}. + */ + public long lastUpdateTime; + + /** + * All kernel group-IDs that have been assigned to this package. + * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set. + */ + public int[] gids; + + /** + * Array of all {@link android.R.styleable#AndroidManifestActivity + * <activity>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_ACTIVITIES} was set. + */ + public ActivityInfo[] activities; + + /** + * Array of all {@link android.R.styleable#AndroidManifestReceiver + * <receiver>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_RECEIVERS} was set. + */ + public ActivityInfo[] receivers; + + /** + * Array of all {@link android.R.styleable#AndroidManifestService + * <service>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_SERVICES} was set. + */ + public ServiceInfo[] services; + + /** + * Array of all {@link android.R.styleable#AndroidManifestProvider + * <provider>} tags included under <application>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PROVIDERS} was set. + */ + public ProviderInfo[] providers; + + /** + * Array of all {@link android.R.styleable#AndroidManifestInstrumentation + * <instrumentation>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_INSTRUMENTATION} was set. + */ + public InstrumentationInfo[] instrumentation; + + /** + * Array of all {@link android.R.styleable#AndroidManifestPermission + * <permission>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PERMISSIONS} was set. + */ + public PermissionInfo[] permissions; + + /** + * Array of all {@link android.R.styleable#AndroidManifestUsesPermission + * <uses-permission>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PERMISSIONS} was set. This list includes + * all permissions requested, even those that were not granted or known + * by the system at install time. + */ + public String[] requestedPermissions; + + /** + * Array of flags of all {@link android.R.styleable#AndroidManifestUsesPermission + * <uses-permission>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_PERMISSIONS} was set. Each value matches + * the corresponding entry in {@link #requestedPermissions}, and will have + * the flag {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate. + */ + public int[] requestedPermissionsFlags; + + /** + * Flag for {@link #requestedPermissionsFlags}: the requested permission + * is required for the application to run; the user can not optionally + * disable it. Currently all permissions are required. + * + * @removed We do not support required permissions. + */ + public static final int REQUESTED_PERMISSION_REQUIRED = 1<<0; + + /** + * Flag for {@link #requestedPermissionsFlags}: the requested permission + * is currently granted to the application. + */ + public static final int REQUESTED_PERMISSION_GRANTED = 1<<1; + + /** + * Array of all signatures read from the package file. This is only filled + * in if the flag {@link PackageManager#GET_SIGNATURES} was set. A package + * must be 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 + * <uses-configuration>} tags included under <manifest>, + * or null if there were none. This is only filled in if the flag + * {@link PackageManager#GET_CONFIGURATIONS} was set. + */ + public ConfigurationInfo[] configPreferences; + + /** + * Features that this application has requested. + * + * @see FeatureInfo#FLAG_REQUIRED + */ + public FeatureInfo[] reqFeatures; + + /** + * Groups of features that this application has requested. + * Each group contains a set of features that are required. + * A device must match the features listed in {@link #reqFeatures} and one + * or more FeatureGroups in order to have satisfied the feature requirement. + * + * @see FeatureInfo#FLAG_REQUIRED + */ + public FeatureGroupInfo[] featureGroups; + + /** + * Constant corresponding to <code>auto</code> in + * the {@link android.R.attr#installLocation} attribute. + * @hide + */ + 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 <manifest> tag's "name" + * attribute. + */ + public String packageName; + + /** Names of any split APKs, ordered by parsed splitName */ + public String[] splitNames; + + /** + * The android:versionCode of the package. + */ + 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 + * <permission-tree>} tag in its manifest. A package can only add + * permissions to trees that were defined by either its own package or + * another with the same user id; a permission is in a tree if it + * matches the name of the permission tree + ".": for example, + * "com.foo.bar" is a member of the permission tree "com.foo". + * + * <p>It is good to make your permission tree name descriptive, because you + * are taking possession of that entire set of permission names. Thus, it + * must be under a domain you control, with a suffix that will not match + * any normal permissions that may be declared in any applications that + * are part of that domain. + * + * <p>New permissions must be added before + * any .apks are installed that use those permissions. Permissions you + * add through this method are remembered across reboots of the device. + * If the given permission already exists, the info you supply here + * will be used to update it. + * + * @param info Description of the permission to be added. + * + * @return Returns true if a new permission was created, false if an + * existing one was updated. + * + * @throws SecurityException if you are not allowed to add the + * given permission name. + * + * @see #removePermission(String) + */ + public abstract boolean addPermission(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 <permission-group> tags. + */ +public class PermissionGroupInfo extends PackageItemInfo implements Parcelable { + /** + * A string resource identifier (in the package's resources) of this + * permission's description. From the "description" attribute or, + * if not set, 0. + */ + public 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 <permission> tags. + */ +public class PermissionInfo extends PackageItemInfo implements Parcelable { + /** + * A normal application value for {@link #protectionLevel}, corresponding + * to the <code>normal</code> value of + * {@link android.R.attr#protectionLevel}. + */ + public static final int PROTECTION_NORMAL = 0; + + /** + * Dangerous value for {@link #protectionLevel}, corresponding + * to the <code>dangerous</code> value of + * {@link android.R.attr#protectionLevel}. + */ + public static final int PROTECTION_DANGEROUS = 1; + + /** + * System-level value for {@link #protectionLevel}, corresponding + * to the <code>signature</code> value of + * {@link android.R.attr#protectionLevel}. + */ + public static final int PROTECTION_SIGNATURE = 2; + + /** + * @deprecated Use {@link #PROTECTION_SIGNATURE}|{@link #PROTECTION_FLAG_PRIVILEGED} + * instead. + */ + @Deprecated + public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3; + + /** + * 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 + * <intent> 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 <service> tags. + */ +public class ServiceInfo extends ComponentInfo + implements Parcelable { + /** + * Optional name of a permission required to be able to access this + * Service. From the "permission" attribute. + */ + public String permission; + + /** + * Bit in {@link #flags}: If set, the service will automatically be + * stopped by the system if the user removes a task that is rooted + * in one of the application's activities. Set from the + * {@link android.R.attr#stopWithTask} attribute. + */ + public static final int FLAG_STOP_WITH_TASK = 0x0001; + + /** + * Bit in {@link #flags}: If set, the service will run in its own + * isolated process. Set from the + * {@link android.R.attr#isolatedProcess} attribute. + */ + public static final int FLAG_ISOLATED_PROCESS = 0x0002; + + /** + * Bit in {@link #flags}: If set, the service can be bound and run in the + * calling application's package, rather than the package in which it is + * declared. Set from {@link android.R.attr#externalService} attribute. + */ + public static final int FLAG_EXTERNAL_SERVICE = 0x0004; + + /** + * Bit in {@link #flags} 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—ones that define the {@link Intent#ACTION_MAIN} + * and {@link Intent#CATEGORY_LAUNCHER} intent filters—can be target + * activities. + * + * <li>By default, the first main activity defined in the app's manifest is + * the target activity. + * + * <li>A target activity must belong to the publisher app. + * </ul> + * + * @see ShortcutInfo#getActivity() + */ + @NonNull + public Builder setActivity(@NonNull ComponentName activity) { + mActivity = 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—activities that handle the {@code MAIN} action and the + * {@code LAUNCHER} category—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> + * <selector xmlns:android="http://schemas.android.com/apk/res/android"> + * <item android:state_focused="true" + * android:color="@color/sample_focused" /> + * <item android:state_pressed="true" + * android:state_enabled="false" + * android:color="@color/sample_disabled_pressed" /> + * <item android:state_enabled="false" + * android:color="@color/sample_disabled_not_pressed" /> + * <item android:color="@color/sample_default" /> + * </selector> + * </pre> + * + * This defines a set of state spec / color pairs where each state spec specifies a set of + * states that a view must either be in or not be in and the color specifies the color associated + * with that spec. + * + * <a name="StateSpec"></a> + * <h3>State specs</h3> + * <p> + * Each item defines a set of state spec and color pairs, where the state spec is a series of + * attributes set to either {@code true} or {@code false} to represent inclusion or exclusion. If + * an attribute is not specified for an item, it may be any value. + * <p> + * For example, the following item will be matched whenever the focused state is set; any other + * states may be set or unset: + * <pre> + * <item android:state_focused="true" + * android:color="@color/sample_focused" /> + * </pre> + * <p> + * Typically, a color state list will reference framework-defined state attributes such as + * {@link android.R.attr#state_focused android:state_focused} or + * {@link android.R.attr#state_enabled android:state_enabled}; however, app-defined attributes may + * also be used. + * <p> + * <strong>Note:</strong> The list of state specs will be matched against in the order that they + * appear in the XML file. For this reason, more-specific items should be placed earlier in the + * file. An item with no state spec is considered to match any set of states and is generally + * useful as a final item to be used as a default. + * <p> + * If an item with no state spec is placed before other items, those items + * will be ignored. + * + * <a name="ItemAttributes"></a> + * <h3>Item attributes</h3> + * <p> + * Each item must define an {@link android.R.attr#color android:color} attribute, which may be + * an HTML-style hex color, a reference to a color resource, or -- in API 23 and above -- a theme + * attribute that resolves to a color. + * <p> + * Starting with API 23, items may optionally define an {@link android.R.attr#alpha android:alpha} + * attribute to modify the base color's opacity. This attribute takes a either floating-point value + * between 0 and 1 or a theme attribute that resolves as such. The item's overall color is + * calculated by multiplying by the base color's alpha channel by the {@code alpha} value. For + * example, the following item represents the theme's accent color at 50% opacity: + * <pre> + * <item android:state_enabled="false" + * android:color="?android:attr/colorAccent" + * android:alpha="0.5" /> + * </pre> + * + * <a name="DeveloperGuide"></a> + * <h3>Developer guide</h3> + * <p> + * For more information, see the guide to + * <a href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State + * List Resource</a>. + * + * @attr ref android.R.styleable#ColorStateListItem_alpha + * @attr ref android.R.styleable#ColorStateListItem_color + */ +public class ColorStateList extends ComplexColor implements Parcelable { + private static final String TAG = "ColorStateList"; + + private static final int DEFAULT_COLOR = Color.RED; + private static final int[][] EMPTY = new int[][] { new int[0] }; + + /** Thread-safe cache of single-color ColorStateLists. */ + private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>(); + + /** Lazily-created factory for this color state list. */ + 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 <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> + * <gradient xmlns:android="http://schemas.android.com/apk/res/android"> + * <android:startColor="?android:attr/colorPrimary"/> + * <android:endColor="?android:attr/colorControlActivated"/> + * <.../> + * <android:type="linear"/> + * </gradient> + * </pre> + * + * This can describe either a {@link android.graphics.LinearGradient}, + * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}. + * + * Note that different attributes are relevant for different types of gradient. + * For example, android:gradientRadius is only applied to RadialGradient. + * android:centerX and android:centerY are only applied to SweepGradient or RadialGradient. + * android:startX, android:startY, android:endX and android:endY are only applied to LinearGradient. + * + * Also note if any color "item" element is defined, then startColor, centerColor and endColor will + * be ignored. + * @hide + */ +public class GradientColor extends ComplexColor { + private static final String TAG = "GradientColor"; + + private static final boolean DBG_GRADIENT = false; + + @IntDef({TILE_MODE_CLAMP, TILE_MODE_REPEAT, TILE_MODE_MIRROR}) + @Retention(RetentionPolicy.SOURCE) + private @interface GradientTileMode {} + private static final int TILE_MODE_CLAMP = 0; + private static final int TILE_MODE_REPEAT = 1; + private static final int TILE_MODE_MIRROR = 2; + + /** Lazily-created factory for this GradientColor. */ + private GradientColorFactory mFactory; + + private @Config int mChangingConfigurations; + private int mDefaultColor; + + // After parsing all the attributes from XML, this shader is the ultimate result containing + // all the XML information. + private Shader mShader = null; + + // Below are the attributes at the root element <gradient>. + // NOTE: they need to be copied in the copy constructor! + private int mGradientType = GradientDrawable.LINEAR_GRADIENT; + + private float mCenterX = 0f; + private float mCenterY = 0f; + + private float mStartX = 0f; + private float mStartY = 0f; + private float mEndX = 0f; + private float mEndY = 0f; + + private int mStartColor = 0; + private int mCenterColor = 0; + private int mEndColor = 0; + private boolean mHasCenterColor = false; + + private int mTileMode = 0; // Clamp mode. + + private float mGradientRadius = 0f; + + // Below are the attributes for the <item> element. + private int[] mItemColors; + private float[] mItemOffsets; + + // Theme attributes for the root and item elements. + private int[] mThemeAttrs; + private int[][] mItemsThemeAttrs; + + private GradientColor() { + } + + private GradientColor(GradientColor copy) { + if (copy != null) { + mChangingConfigurations = copy.mChangingConfigurations; + mDefaultColor = copy.mDefaultColor; + mShader = copy.mShader; + mGradientType = copy.mGradientType; + mCenterX = copy.mCenterX; + mCenterY = copy.mCenterY; + mStartX = copy.mStartX; + mStartY = copy.mStartY; + mEndX = copy.mEndX; + mEndY = copy.mEndY; + mStartColor = copy.mStartColor; + mCenterColor = copy.mCenterColor; + mEndColor = copy.mEndColor; + mHasCenterColor = copy.mHasCenterColor; + mGradientRadius = copy.mGradientRadius; + mTileMode = copy.mTileMode; + + if (copy.mItemColors != null) { + mItemColors = copy.mItemColors.clone(); + } + if (copy.mItemOffsets != null) { + mItemOffsets = copy.mItemOffsets.clone(); + } + + if (copy.mThemeAttrs != null) { + mThemeAttrs = copy.mThemeAttrs.clone(); + } + if (copy.mItemsThemeAttrs != null) { + mItemsThemeAttrs = copy.mItemsThemeAttrs.clone(); + } + } + } + + // Set the default to clamp mode. + private static Shader.TileMode parseTileMode(@GradientTileMode int tileMode) { + switch (tileMode) { + case TILE_MODE_CLAMP: + return Shader.TileMode.CLAMP; + case TILE_MODE_REPEAT: + return Shader.TileMode.REPEAT; + case TILE_MODE_MIRROR: + return Shader.TileMode.MIRROR; + default: + return Shader.TileMode.CLAMP; + } + } + + /** + * Update the root level's attributes, either for inflate or applyTheme. + */ + private void updateRootElementState(TypedArray a) { + // Extract the theme attributes, if any. + mThemeAttrs = a.extractThemeAttrs(); + + mStartX = a.getFloat( + R.styleable.GradientColor_startX, mStartX); + mStartY = a.getFloat( + R.styleable.GradientColor_startY, mStartY); + mEndX = a.getFloat( + R.styleable.GradientColor_endX, mEndX); + mEndY = a.getFloat( + R.styleable.GradientColor_endY, mEndY); + + mCenterX = a.getFloat( + R.styleable.GradientColor_centerX, mCenterX); + mCenterY = a.getFloat( + R.styleable.GradientColor_centerY, mCenterY); + + mGradientType = a.getInt( + R.styleable.GradientColor_type, mGradientType); + + mStartColor = a.getColor( + R.styleable.GradientColor_startColor, mStartColor); + mHasCenterColor |= a.hasValue( + R.styleable.GradientColor_centerColor); + mCenterColor = a.getColor( + R.styleable.GradientColor_centerColor, mCenterColor); + mEndColor = a.getColor( + R.styleable.GradientColor_endColor, mEndColor); + + mTileMode = a.getInt( + R.styleable.GradientColor_tileMode, mTileMode); + + if (DBG_GRADIENT) { + Log.v(TAG, "hasCenterColor is " + mHasCenterColor); + if (mHasCenterColor) { + Log.v(TAG, "centerColor:" + mCenterColor); + } + Log.v(TAG, "startColor: " + mStartColor); + Log.v(TAG, "endColor: " + mEndColor); + Log.v(TAG, "tileMode: " + mTileMode); + } + + mGradientRadius = a.getFloat(R.styleable.GradientColor_gradientRadius, + mGradientRadius); + } + + /** + * Check if the XML content is valid. + * + * @throws XmlPullParserException if errors were found. + */ + private void validateXmlContent() throws XmlPullParserException { + if (mGradientRadius <= 0 + && mGradientType == GradientDrawable.RADIAL_GRADIENT) { + throw new XmlPullParserException( + "<gradient> tag requires 'gradientRadius' " + + "attribute with radial type"); + } + } + + /** + * The shader information will be applied to the native VectorDrawable's path. + * @hide + */ + public Shader getShader() { + return mShader; + } + + /** + * A public method to create GradientColor from a XML resource. + */ + public static GradientColor createFromXml(Resources r, XmlResourceParser parser, Theme theme) + throws XmlPullParserException, IOException { + final AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Seek parser to start tag. + } + + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + return createFromXmlInner(r, parser, attrs, theme); + } + + /** + * Create from inside an XML document. Called on a parser positioned at a + * tag in an XML document, tries to create a GradientColor from that tag. + * + * @return A new GradientColor for the current tag. + * @throws XmlPullParserException if the current tag is not <gradient> + */ + @NonNull + static GradientColor createFromXmlInner(@NonNull Resources r, + @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final String name = parser.getName(); + if (!name.equals("gradient")) { + throw new XmlPullParserException( + parser.getPositionDescription() + ": invalid gradient color tag " + name); + } + + final GradientColor gradientColor = new GradientColor(); + gradientColor.inflate(r, parser, attrs, theme); + return gradientColor; + } + + /** + * Fill in this object based on the contents of an XML "gradient" element. + */ + private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = Resources.obtainAttributes(r, theme, attrs, R.styleable.GradientColor); + updateRootElementState(a); + mChangingConfigurations |= a.getChangingConfigurations(); + a.recycle(); + + // Check correctness and throw exception if errors found. + validateXmlContent(); + + inflateChildElements(r, parser, attrs, theme); + + onColorsChange(); + } + + /** + * Inflates child elements "item"s for each color stop. + * + * Note that at root level, we need to save ThemeAttrs for theme applied later. + * Here similarly, at each child item, we need to save the theme's attributes, and apply theme + * later as applyItemsAttrsTheme(). + */ + private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @NonNull Theme theme) + throws XmlPullParserException, IOException { + final int innerDepth = parser.getDepth() + 1; + int type; + int depth; + + // Pre-allocate the array with some size, for better performance. + float[] offsetList = new float[20]; + int[] colorList = new int[offsetList.length]; + int[][] themeAttrsList = new int[offsetList.length][]; + + int listSize = 0; + boolean hasUnresolvedAttrs = false; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth + || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + if (depth > innerDepth || !parser.getName().equals("item")) { + continue; + } + + final TypedArray a = Resources.obtainAttributes(r, theme, attrs, + R.styleable.GradientColorItem); + boolean hasColor = a.hasValue(R.styleable.GradientColorItem_color); + boolean hasOffset = a.hasValue(R.styleable.GradientColorItem_offset); + if (!hasColor || !hasOffset) { + throw new XmlPullParserException( + parser.getPositionDescription() + + ": <item> tag requires a 'color' attribute and a 'offset' " + + "attribute!"); + } + + final int[] themeAttrs = a.extractThemeAttrs(); + int color = a.getColor(R.styleable.GradientColorItem_color, 0); + float offset = a.getFloat(R.styleable.GradientColorItem_offset, 0); + + if (DBG_GRADIENT) { + Log.v(TAG, "new item color " + color + " " + Integer.toHexString(color)); + Log.v(TAG, "offset" + offset); + } + mChangingConfigurations |= a.getChangingConfigurations(); + a.recycle(); + + if (themeAttrs != null) { + hasUnresolvedAttrs = true; + } + + colorList = GrowingArrayUtils.append(colorList, listSize, color); + offsetList = GrowingArrayUtils.append(offsetList, listSize, offset); + themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs); + listSize++; + } + if (listSize > 0) { + if (hasUnresolvedAttrs) { + mItemsThemeAttrs = new int[listSize][]; + System.arraycopy(themeAttrsList, 0, mItemsThemeAttrs, 0, listSize); + } else { + mItemsThemeAttrs = null; + } + + mItemColors = new int[listSize]; + mItemOffsets = new float[listSize]; + System.arraycopy(colorList, 0, mItemColors, 0, listSize); + System.arraycopy(offsetList, 0, mItemOffsets, 0, listSize); + } + } + + /** + * Apply theme to all the items. + */ + private void applyItemsAttrsTheme(Theme t) { + if (mItemsThemeAttrs == null) { + return; + } + + boolean hasUnresolvedAttrs = false; + + final int[][] themeAttrsList = mItemsThemeAttrs; + final int N = themeAttrsList.length; + for (int i = 0; i < N; i++) { + if (themeAttrsList[i] != null) { + final TypedArray a = t.resolveAttributes(themeAttrsList[i], + R.styleable.GradientColorItem); + + // Extract the theme attributes, if any, before attempting to + // read from the typed array. This prevents a crash if we have + // unresolved attrs. + themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]); + if (themeAttrsList[i] != null) { + hasUnresolvedAttrs = true; + } + + mItemColors[i] = a.getColor(R.styleable.GradientColorItem_color, mItemColors[i]); + mItemOffsets[i] = a.getFloat(R.styleable.GradientColorItem_offset, mItemOffsets[i]); + if (DBG_GRADIENT) { + Log.v(TAG, "applyItemsAttrsTheme Colors[i] " + i + " " + + Integer.toHexString(mItemColors[i])); + Log.v(TAG, "Offsets[i] " + i + " " + mItemOffsets[i]); + } + + // Account for any configuration changes. + mChangingConfigurations |= a.getChangingConfigurations(); + + a.recycle(); + } + } + + if (!hasUnresolvedAttrs) { + mItemsThemeAttrs = null; + } + } + + private void onColorsChange() { + int[] tempColors = null; + float[] tempOffsets = null; + + if (mItemColors != null) { + int length = mItemColors.length; + tempColors = new int[length]; + tempOffsets = new float[length]; + + for (int i = 0; i < length; i++) { + tempColors[i] = mItemColors[i]; + tempOffsets[i] = mItemOffsets[i]; + } + } else { + if (mHasCenterColor) { + tempColors = new int[3]; + tempColors[0] = mStartColor; + tempColors[1] = mCenterColor; + tempColors[2] = mEndColor; + + tempOffsets = new float[3]; + tempOffsets[0] = 0.0f; + // Since 0.5f is default value, try to take the one that isn't 0.5f + tempOffsets[1] = 0.5f; + tempOffsets[2] = 1f; + } else { + tempColors = new int[2]; + tempColors[0] = mStartColor; + tempColors[1] = mEndColor; + } + } + if (tempColors.length < 2) { + Log.w(TAG, "<gradient> tag requires 2 color values specified!" + tempColors.length + + " " + tempColors); + } + + if (mGradientType == GradientDrawable.LINEAR_GRADIENT) { + mShader = new LinearGradient(mStartX, mStartY, mEndX, mEndY, tempColors, tempOffsets, + parseTileMode(mTileMode)); + } else { + if (mGradientType == GradientDrawable.RADIAL_GRADIENT) { + mShader = new RadialGradient(mCenterX, mCenterY, mGradientRadius, tempColors, + tempOffsets, parseTileMode(mTileMode)); + } else { + mShader = new SweepGradient(mCenterX, mCenterY, tempColors, tempOffsets); + } + } + mDefaultColor = tempColors[0]; + } + + /** + * For Gradient color, the default color is not very useful, since the gradient will override + * the color information anyway. + */ + @Override + @ColorInt + public int getDefaultColor() { + return mDefaultColor; + } + + /** + * Similar to ColorStateList, setup constant state and its factory. + * @hide only for resource preloading + */ + @Override + public ConstantState<ComplexColor> getConstantState() { + if (mFactory == null) { + mFactory = new GradientColorFactory(this); + } + return mFactory; + } + + private static class GradientColorFactory extends ConstantState<ComplexColor> { + private final GradientColor mSrc; + + public GradientColorFactory(GradientColor src) { + mSrc = src; + } + + @Override + public @Config int getChangingConfigurations() { + return mSrc.mChangingConfigurations; + } + + @Override + public GradientColor newInstance() { + return mSrc; + } + + @Override + public GradientColor newInstance(Resources res, Theme theme) { + return mSrc.obtainForTheme(theme); + } + } + + /** + * Returns an appropriately themed gradient color. + * + * @param t the theme to apply + * @return a copy of the gradient color the theme applied, or the + * gradient itself if there were no unresolved theme + * attributes + * @hide only for resource preloading + */ + @Override + public GradientColor obtainForTheme(Theme t) { + if (t == null || !canApplyTheme()) { + return this; + } + + final GradientColor clone = new GradientColor(this); + clone.applyTheme(t); + return clone; + } + + /** + * Returns a mask of the configuration parameters for which this gradient + * may change, requiring that it be re-created. + * + * @return a mask of the changing configuration parameters, as defined by + * {@link android.content.pm.ActivityInfo} + * + * @see android.content.pm.ActivityInfo + */ + public int getChangingConfigurations() { + return super.getChangingConfigurations() | mChangingConfigurations; + } + + private void applyTheme(Theme t) { + if (mThemeAttrs != null) { + applyRootAttrsTheme(t); + } + if (mItemsThemeAttrs != null) { + applyItemsAttrsTheme(t); + } + onColorsChange(); + } + + private void applyRootAttrsTheme(Theme t) { + final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.GradientColor); + // mThemeAttrs will be set to null if if there are no theme attributes in the + // typed array. + mThemeAttrs = a.extractThemeAttrs(mThemeAttrs); + // merging the attributes update inside the updateRootElementState(). + updateRootElementState(a); + + // Account for any configuration changes. + mChangingConfigurations |= a.getChangingConfigurations(); + a.recycle(); + } + + + /** + * Returns whether a theme can be applied to this gradient color, which + * usually indicates that the gradient color has unresolved theme + * attributes. + * + * @return whether a theme can be applied to this gradient color. + * @hide only for resource preloading + */ + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || mItemsThemeAttrs != null; + } + +} diff --git a/android/content/res/ObbInfo.java b/android/content/res/ObbInfo.java new file mode 100644 index 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—by providing sets of alternative + * resources—enables you to optimize your application for a variety of device configurations + * (such as for different languages and screen sizes). This is an important aspect of developing + * Android applications that are compatible on different types of devices.</p> + * + * <p>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><Button + * textColor="#ff000000"></code>, then the button's text will + * <em>always</em> be black, regardless of what is specified in any of + * the styles. + * + * @param set The base set of attribute values. May be null. + * @param attrs The desired attributes to be retrieved. These attribute IDs must be sorted + * in ascending order. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies + * defaults values for the TypedArray. Can be + * 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the TypedArray, + * used only if defStyleAttr is 0 or can not be found + * in the theme. Can be 0 to not look for defaults. + * + * @return Returns a TypedArray holding an array of the attribute values. + * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} + * when done with it. + * + * @see Resources#obtainAttributes + * @see #obtainStyledAttributes(int[]) + * @see #obtainStyledAttributes(int, int[]) + */ + 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 <extra>} tags from + * an XML file. You call this when you are at the parent tag of the + * extra tags, and it will return once all of the child tags have been parsed. + * This will call {@link #parseBundleExtra} for each extra tag encountered. + * + * @param parser The parser from which to retrieve the extras. + * @param outBundle A Bundle in which to place all parsed extras. + * @throws XmlPullParserException + * @throws IOException + */ + public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String nodeName = parser.getName(); + if (nodeName.equals("extra")) { + parseBundleExtra("extra", parser, outBundle); + XmlUtils.skipCurrentTag(parser); + + } else { + XmlUtils.skipCurrentTag(parser); + } + } + } + + /** + * Parse a name/value pair out of an XML tag holding that data. The + * AttributeSet must be holding the data defined by + * {@link android.R.styleable#Extra}. The following value types are supported: + * <ul> + * <li> {@link TypedValue#TYPE_STRING}: + * {@link Bundle#putCharSequence Bundle.putCharSequence()} + * <li> {@link TypedValue#TYPE_INT_BOOLEAN}: + * {@link Bundle#putCharSequence Bundle.putBoolean()} + * <li> {@link TypedValue#TYPE_FIRST_INT}-{@link TypedValue#TYPE_LAST_INT}: + * {@link Bundle#putCharSequence Bundle.putBoolean()} + * <li> {@link TypedValue#TYPE_FLOAT}: + * {@link Bundle#putCharSequence Bundle.putFloat()} + * </ul> + * + * @param tagName The name of the tag these attributes come from; this is + * only used for reporting error messages. + * @param attrs The attributes from which to retrieve the name/value pair. + * @param outBundle The Bundle in which to place the parsed value. + * @throws XmlPullParserException If the attributes are not valid. + */ + public void parseBundleExtra(String tagName, AttributeSet attrs, + Bundle outBundle) throws XmlPullParserException { + TypedArray sa = obtainAttributes(attrs, + com.android.internal.R.styleable.Extra); + + String name = sa.getString( + com.android.internal.R.styleable.Extra_name); + if (name == null) { + sa.recycle(); + throw new XmlPullParserException("<" + tagName + + "> requires an android:name attribute at " + + attrs.getPositionDescription()); + } + + TypedValue v = sa.peekValue( + com.android.internal.R.styleable.Extra_value); + if (v != null) { + if (v.type == TypedValue.TYPE_STRING) { + CharSequence cs = v.coerceToString(); + outBundle.putCharSequence(name, cs); + } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) { + outBundle.putBoolean(name, v.data != 0); + } else if (v.type >= TypedValue.TYPE_FIRST_INT + && v.type <= TypedValue.TYPE_LAST_INT) { + outBundle.putInt(name, v.data); + } else if (v.type == TypedValue.TYPE_FLOAT) { + outBundle.putFloat(name, v.getFloat()); + } else { + sa.recycle(); + throw new XmlPullParserException("<" + tagName + + "> only supports string, integer, float, color, and boolean at " + + attrs.getPositionDescription()); + } + } else { + sa.recycle(); + throw new XmlPullParserException("<" + tagName + + "> requires an android:value or android:resource attribute at " + + attrs.getPositionDescription()); + } + + sa.recycle(); + } + + /** + * Retrieve underlying AssetManager storage for these resources. + */ + public final AssetManager getAssets() { + return mResourcesImpl.getAssets(); + } + + /** + * Call this to remove all cached loaded layout resources from the + * Resources object. Only intended for use with performance testing + * tools. + */ + public final void flushLayoutCache() { + mResourcesImpl.flushLayoutCache(); + } + + /** + * Start preloading of resource data using this Resources object. Only + * for use by the zygote process for loading common system resources. + * {@hide} + */ + public final void startPreloading() { + mResourcesImpl.startPreloading(); + } + + /** + * Called by zygote when it is done preloading resources, to change back + * to normal Resources operation. + */ + public final void finishPreloading() { + mResourcesImpl.finishPreloading(); + } + + /** + * @hide + */ + 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 <T super String>, 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(); +} + |