auto import from //depot/cupcake/@135843
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
new file mode 100644
index 0000000..ce6501c
--- /dev/null
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -0,0 +1,601 @@
+package android.content;
+
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.Cursor;
+import android.net.Uri;
+import android.accounts.AccountMonitor;
+import android.accounts.AccountMonitorListener;
+import android.provider.SyncConstValue;
+import android.util.Config;
+import android.util.Log;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ * A specialization of the ContentProvider that centralizes functionality
+ * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
+ * inside of database transactions.
+ *
+ * @hide
+ */
+public abstract class AbstractSyncableContentProvider extends SyncableContentProvider {
+ private static final String TAG = "SyncableContentProvider";
+ protected SQLiteOpenHelper mOpenHelper;
+ protected SQLiteDatabase mDb;
+ private final String mDatabaseName;
+ private final int mDatabaseVersion;
+ private final Uri mContentUri;
+ private AccountMonitor mAccountMonitor;
+
+ /** the account set in the last call to onSyncStart() */
+ private String mSyncingAccount;
+
+ private SyncStateContentProviderHelper mSyncState = null;
+
+ private static final String[] sAccountProjection = new String[] {SyncConstValue._SYNC_ACCOUNT};
+
+ private boolean mIsTemporary;
+
+ private AbstractTableMerger mCurrentMerger = null;
+ private boolean mIsMergeCancelled = false;
+
+ private static final String SYNC_ACCOUNT_WHERE_CLAUSE = SyncConstValue._SYNC_ACCOUNT + "=?";
+
+ protected boolean isTemporary() {
+ return mIsTemporary;
+ }
+
+ /**
+ * Indicates whether or not this ContentProvider contains a full
+ * set of data or just diffs. This knowledge comes in handy when
+ * determining how to incorporate the contents of a temporary
+ * provider into a real provider.
+ */
+ private boolean mContainsDiffs;
+
+ /**
+ * Initializes the AbstractSyncableContentProvider
+ * @param dbName the filename of the database
+ * @param dbVersion the current version of the database schema
+ * @param contentUri The base Uri of the syncable content in this provider
+ */
+ public AbstractSyncableContentProvider(String dbName, int dbVersion, Uri contentUri) {
+ super();
+
+ mDatabaseName = dbName;
+ mDatabaseVersion = dbVersion;
+ mContentUri = contentUri;
+ mIsTemporary = false;
+ setContainsDiffs(false);
+ if (Config.LOGV) {
+ Log.v(TAG, "created SyncableContentProvider " + this);
+ }
+ }
+
+ /**
+ * Close resources that must be closed. You must call this to properly release
+ * the resources used by the AbstractSyncableContentProvider.
+ */
+ public void close() {
+ if (mOpenHelper != null) {
+ mOpenHelper.close(); // OK to call .close() repeatedly.
+ }
+ }
+
+ /**
+ * Override to create your schema and do anything else you need to do with a new database.
+ * This is run inside a transaction (so you don't need to use one).
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ */
+ protected void bootstrapDatabase(SQLiteDatabase db) {}
+
+ /**
+ * Override to upgrade your database from an old version to the version you specified.
+ * Don't set the DB version; this will automatically be done after the method returns.
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ *
+ * @param oldVersion version of the existing database
+ * @param newVersion current version to upgrade to
+ * @return true if the upgrade was lossless, false if it was lossy
+ */
+ protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
+
+ /**
+ * Override to do anything (like cleanups or checks) you need to do after opening a database.
+ * Does nothing by default. This is run inside a transaction (so you don't need to use one).
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ */
+ protected void onDatabaseOpened(SQLiteDatabase db) {}
+
+ private class DatabaseHelper extends SQLiteOpenHelper {
+ DatabaseHelper(Context context, String name) {
+ // Note: context and name may be null for temp providers
+ super(context, name, null, mDatabaseVersion);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ bootstrapDatabase(db);
+ mSyncState.createDatabase(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (!upgradeDatabase(db, oldVersion, newVersion)) {
+ mSyncState.discardSyncData(db, null /* all accounts */);
+ getContext().getContentResolver().startSync(mContentUri, new Bundle());
+ }
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ onDatabaseOpened(db);
+ mSyncState.onDatabaseOpened(db);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
+ mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(), mDatabaseName);
+ mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
+
+ AccountMonitorListener listener = new AccountMonitorListener() {
+ public void onAccountsUpdated(String[] accounts) {
+ // Some providers override onAccountsChanged(); give them a database to work with.
+ mDb = mOpenHelper.getWritableDatabase();
+ onAccountsChanged(accounts);
+ TempProviderSyncAdapter syncAdapter = (TempProviderSyncAdapter)getSyncAdapter();
+ if (syncAdapter != null) {
+ syncAdapter.onAccountsChanged(accounts);
+ }
+ }
+ };
+ mAccountMonitor = new AccountMonitor(getContext(), listener);
+
+ return true;
+ }
+
+ /**
+ * Get a non-persistent instance of this content provider.
+ * You must call {@link #close} on the returned
+ * SyncableContentProvider when you are done with it.
+ *
+ * @return a non-persistent content provider with the same layout as this
+ * provider.
+ */
+ public AbstractSyncableContentProvider getTemporaryInstance() {
+ AbstractSyncableContentProvider temp;
+ try {
+ temp = getClass().newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException("unable to instantiate class, "
+ + "this should never happen", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "IllegalAccess while instantiating class, "
+ + "this should never happen", e);
+ }
+
+ // Note: onCreate() isn't run for the temp provider, and it has no Context.
+ temp.mIsTemporary = true;
+ temp.setContainsDiffs(true);
+ temp.mOpenHelper = temp.new DatabaseHelper(null, null);
+ temp.mSyncState = new SyncStateContentProviderHelper(temp.mOpenHelper);
+ if (!isTemporary()) {
+ mSyncState.copySyncState(
+ mOpenHelper.getReadableDatabase(),
+ temp.mOpenHelper.getWritableDatabase(),
+ getSyncingAccount());
+ }
+ return temp;
+ }
+
+ public SQLiteDatabase getDatabase() {
+ if (mDb == null) mDb = mOpenHelper.getWritableDatabase();
+ return mDb;
+ }
+
+ public boolean getContainsDiffs() {
+ return mContainsDiffs;
+ }
+
+ public void setContainsDiffs(boolean containsDiffs) {
+ if (containsDiffs && !isTemporary()) {
+ throw new IllegalStateException(
+ "only a temporary provider can contain diffs");
+ }
+ mContainsDiffs = containsDiffs;
+ }
+
+ /**
+ * Each subclass of this class should define a subclass of {@link
+ * android.content.AbstractTableMerger} for each table they wish to merge. It
+ * should then override this method and return one instance of
+ * each merger, in sequence. Their {@link
+ * android.content.AbstractTableMerger#merge merge} methods will be called, one at a
+ * time, in the order supplied.
+ *
+ * <p>The default implementation returns an empty list, so that no
+ * merging will occur.
+ * @return A sequence of subclasses of {@link
+ * android.content.AbstractTableMerger}, one for each table that should be merged.
+ */
+ protected Iterable<? extends AbstractTableMerger> getMergers() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public final int update(final Uri url, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ if (isTemporary() && mSyncState.matches(url)) {
+ int numRows = mSyncState.asContentProvider().update(
+ url, values, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+ return numRows;
+ }
+
+ int result = updateInternal(url, values, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(url, null /* observer */,
+ changeRequiresLocalSync(url));
+ }
+
+ return result;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ @Override
+ public final int delete(final Uri url, final String selection,
+ final String[] selectionArgs) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ if (isTemporary() && mSyncState.matches(url)) {
+ int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+ return numRows;
+ }
+ int result = deleteInternal(url, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(url, null /* observer */,
+ changeRequiresLocalSync(url));
+ }
+ return result;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ @Override
+ public final Uri insert(final Uri url, final ContentValues values) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ if (isTemporary() && mSyncState.matches(url)) {
+ Uri result = mSyncState.asContentProvider().insert(url, values);
+ mDb.setTransactionSuccessful();
+ return result;
+ }
+ Uri result = insertInternal(url, values);
+ mDb.setTransactionSuccessful();
+ if (!isTemporary() && result != null) {
+ getContext().getContentResolver().notifyChange(url, null /* observer */,
+ changeRequiresLocalSync(url));
+ }
+ return result;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ @Override
+ public final int bulkInsert(final Uri uri, final ContentValues[] values) {
+ int size = values.length;
+ int completed = 0;
+ final boolean isSyncStateUri = mSyncState.matches(uri);
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ for (int i = 0; i < size; i++) {
+ Uri result;
+ if (isTemporary() && isSyncStateUri) {
+ result = mSyncState.asContentProvider().insert(uri, values[i]);
+ } else {
+ result = insertInternal(uri, values[i]);
+ mDb.yieldIfContended();
+ }
+ if (result != null) {
+ completed++;
+ }
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+ if (!isTemporary() && completed == size) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
+ }
+ return completed;
+ }
+
+ /**
+ * Check if changes to this URI can be syncable changes.
+ * @param uri the URI of the resource that was changed
+ * @return true if changes to this URI can be syncable changes, false otherwise
+ */
+ public boolean changeRequiresLocalSync(Uri uri) {
+ return true;
+ }
+
+ @Override
+ public final Cursor query(final Uri url, final String[] projection,
+ final String selection, final String[] selectionArgs,
+ final String sortOrder) {
+ mDb = mOpenHelper.getReadableDatabase();
+ if (isTemporary() && mSyncState.matches(url)) {
+ return mSyncState.asContentProvider().query(
+ url, projection, selection, selectionArgs, sortOrder);
+ }
+ return queryInternal(url, projection, selection, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Called right before a sync is started.
+ *
+ * @param context the sync context for the operation
+ * @param account
+ */
+ public void onSyncStart(SyncContext context, String account) {
+ if (TextUtils.isEmpty(account)) {
+ throw new IllegalArgumentException("you passed in an empty account");
+ }
+ mSyncingAccount = account;
+ }
+
+ /**
+ * Called right after a sync is completed
+ *
+ * @param context the sync context for the operation
+ * @param success true if the sync succeeded, false if an error occurred
+ */
+ public void onSyncStop(SyncContext context, boolean success) {
+ }
+
+ /**
+ * The account of the most recent call to onSyncStart()
+ * @return the account
+ */
+ public String getSyncingAccount() {
+ return mSyncingAccount;
+ }
+
+ /**
+ * Merge diffs from a sync source with this content provider.
+ *
+ * @param context the SyncContext within which this merge is taking place
+ * @param diffs A temporary content provider containing diffs from a sync
+ * source.
+ * @param result a MergeResult that contains information about the merge, including
+ * a temporary content provider with the same layout as this provider containing
+ * @param syncResult
+ */
+ public void merge(SyncContext context, SyncableContentProvider diffs,
+ TempProviderSyncResult result, SyncResult syncResult) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ synchronized(this) {
+ mIsMergeCancelled = false;
+ }
+ Iterable<? extends AbstractTableMerger> mergers = getMergers();
+ try {
+ for (AbstractTableMerger merger : mergers) {
+ synchronized(this) {
+ if (mIsMergeCancelled) break;
+ mCurrentMerger = merger;
+ }
+ merger.merge(context, getSyncingAccount(), diffs, result, syncResult, this);
+ }
+ if (mIsMergeCancelled) return;
+ if (diffs != null) {
+ mSyncState.copySyncState(
+ ((AbstractSyncableContentProvider)diffs).mOpenHelper.getReadableDatabase(),
+ mOpenHelper.getWritableDatabase(),
+ getSyncingAccount());
+ }
+ } finally {
+ synchronized (this) {
+ mCurrentMerger = null;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+
+ /**
+ * Invoked when the active sync has been canceled. Sets the sync state of this provider and
+ * its merger to canceled.
+ */
+ public void onSyncCanceled() {
+ synchronized (this) {
+ mIsMergeCancelled = true;
+ if (mCurrentMerger != null) {
+ mCurrentMerger.onMergeCancelled();
+ }
+ }
+ }
+
+
+ public boolean isMergeCancelled() {
+ return mIsMergeCancelled;
+ }
+
+ /**
+ * Subclasses should override this instead of update(). See update()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract int updateInternal(Uri url, ContentValues values,
+ String selection, String[] selectionArgs);
+
+ /**
+ * Subclasses should override this instead of delete(). See delete()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
+
+ /**
+ * Subclasses should override this instead of insert(). See insert()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract Uri insertInternal(Uri url, ContentValues values);
+
+ /**
+ * Subclasses should override this instead of query(). See query()
+ * for details.
+ *
+ * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
+ * block for performance reasons. If an implementation needs atomic access
+ * to the database the lock can be acquired then.
+ */
+ protected abstract Cursor queryInternal(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder);
+
+ /**
+ * Make sure that there are no entries for accounts that no longer exist
+ * @param accountsArray the array of currently-existing accounts
+ */
+ protected void onAccountsChanged(String[] accountsArray) {
+ Map<String, Boolean> accounts = new HashMap<String, Boolean>();
+ for (String account : accountsArray) {
+ accounts.put(account, false);
+ }
+ accounts.put(SyncConstValue.NON_SYNCABLE_ACCOUNT, false);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Map<String, String> tableMap = db.getSyncedTables();
+ Vector<String> tables = new Vector<String>();
+ tables.addAll(tableMap.keySet());
+ tables.addAll(tableMap.values());
+
+ db.beginTransaction();
+ try {
+ mSyncState.onAccountsChanged(accountsArray);
+ for (String table : tables) {
+ deleteRowsForRemovedAccounts(accounts, table,
+ SyncConstValue._SYNC_ACCOUNT);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * A helper method to delete all rows whose account is not in the accounts
+ * map. The accountColumnName is the name of the column that is expected
+ * to hold the account. If a row has an empty account it is never deleted.
+ *
+ * @param accounts a map of existing accounts
+ * @param table the table to delete from
+ * @param accountColumnName the name of the column that is expected
+ * to hold the account.
+ */
+ protected void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
+ String table, String accountColumnName) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor c = db.query(table, sAccountProjection, null, null,
+ accountColumnName, null, null);
+ try {
+ while (c.moveToNext()) {
+ String account = c.getString(0);
+ if (TextUtils.isEmpty(account)) {
+ continue;
+ }
+ if (!accounts.containsKey(account)) {
+ int numDeleted;
+ numDeleted = db.delete(table, accountColumnName + "=?", new String[]{account});
+ if (Config.LOGV) {
+ Log.v(TAG, "deleted " + numDeleted
+ + " records from table " + table
+ + " for account " + account);
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ /**
+ * Called when the sync system determines that this provider should no longer
+ * contain records for the specified account.
+ */
+ public void wipeAccount(String account) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Map<String, String> tableMap = db.getSyncedTables();
+ ArrayList<String> tables = new ArrayList<String>();
+ tables.addAll(tableMap.keySet());
+ tables.addAll(tableMap.values());
+
+ db.beginTransaction();
+
+ try {
+ // remove the SyncState data
+ mSyncState.discardSyncData(db, account);
+
+ // remove the data in the synced tables
+ for (String table : tables) {
+ db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE, new String[]{account});
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+ */
+ public byte[] readSyncDataBytes(String account) {
+ return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
+ }
+
+ /**
+ * Sets the SyncData bytes for the given account. The byte array may be null.
+ */
+ public void writeSyncDataBytes(String account, byte[] data) {
+ mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
+ }
+}
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
new file mode 100644
index 0000000..700f1d8
--- /dev/null
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -0,0 +1,582 @@
+/*
+ * 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.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Debug;
+import android.provider.BaseColumns;
+import static android.provider.SyncConstValue.*;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public abstract class AbstractTableMerger
+{
+ private ContentValues mValues;
+
+ protected SQLiteDatabase mDb;
+ protected String mTable;
+ protected Uri mTableURL;
+ protected String mDeletedTable;
+ protected Uri mDeletedTableURL;
+ static protected ContentValues mSyncMarkValues;
+ static private boolean TRACE;
+
+ static {
+ mSyncMarkValues = new ContentValues();
+ mSyncMarkValues.put(_SYNC_MARK, 1);
+ TRACE = false;
+ }
+
+ private static final String TAG = "AbstractTableMerger";
+ private static final String[] syncDirtyProjection =
+ new String[] {_SYNC_DIRTY, BaseColumns._ID, _SYNC_ID, _SYNC_VERSION};
+ private static final String[] syncIdAndVersionProjection =
+ new String[] {_SYNC_ID, _SYNC_VERSION};
+
+ private volatile boolean mIsMergeCancelled;
+
+ private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and " + _SYNC_ACCOUNT + "=?";
+
+ private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
+ _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
+ private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
+
+ private static final String SELECT_UNSYNCED = ""
+ + _SYNC_DIRTY + " > 0 and (" + _SYNC_ACCOUNT + "=? or " + _SYNC_ACCOUNT + " is null)";
+
+ public AbstractTableMerger(SQLiteDatabase database,
+ String table, Uri tableURL, String deletedTable,
+ Uri deletedTableURL)
+ {
+ mDb = database;
+ mTable = table;
+ mTableURL = tableURL;
+ mDeletedTable = deletedTable;
+ mDeletedTableURL = deletedTableURL;
+ mValues = new ContentValues();
+ }
+
+ public abstract void insertRow(ContentProvider diffs,
+ Cursor diffsCursor);
+ public abstract void updateRow(long localPersonID,
+ ContentProvider diffs, Cursor diffsCursor);
+ public abstract void resolveRow(long localPersonID,
+ String syncID, ContentProvider diffs, Cursor diffsCursor);
+
+ /**
+ * This is called when it is determined that a row should be deleted from the
+ * ContentProvider. The localCursor is on a table from the local ContentProvider
+ * and its current position is of the row that should be deleted. The localCursor
+ * is only guaranteed to contain the BaseColumns.ID column so the implementation
+ * of deleteRow() must query the database directly if other columns are needed.
+ * <p>
+ * It is the responsibility of the implementation of this method to ensure that the cursor
+ * points to the next row when this method returns, either by calling Cursor.deleteRow() or
+ * Cursor.next().
+ *
+ * @param localCursor The Cursor into the local table, which points to the row that
+ * is to be deleted.
+ */
+ public void deleteRow(Cursor localCursor) {
+ localCursor.deleteRow();
+ }
+
+ /**
+ * After {@link #merge} has completed, this method is called to send
+ * notifications to {@link android.database.ContentObserver}s of changes
+ * to the containing {@link ContentProvider}. These notifications likely
+ * do not want to request a sync back to the network.
+ */
+ protected abstract void notifyChanges();
+
+ private static boolean findInCursor(Cursor cursor, int column, String id) {
+ while (!cursor.isAfterLast() && !cursor.isNull(column)) {
+ int comp = id.compareTo(cursor.getString(column));
+ if (comp > 0) {
+ cursor.moveToNext();
+ continue;
+ }
+ return comp == 0;
+ }
+ return false;
+ }
+
+ public void onMergeCancelled() {
+ mIsMergeCancelled = true;
+ }
+
+ /**
+ * Carry out a merge of the given diffs, and add the results to
+ * the given MergeResult. If we are the first merge to find
+ * client-side diffs, we'll use the given ContentProvider to
+ * construct a temporary instance to hold them.
+ */
+ public void merge(final SyncContext context,
+ final String account,
+ final SyncableContentProvider serverDiffs,
+ TempProviderSyncResult result,
+ SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
+ mIsMergeCancelled = false;
+ if (serverDiffs != null) {
+ if (!mDb.isDbLockedByCurrentThread()) {
+ throw new IllegalStateException("this must be called from within a DB transaction");
+ }
+ mergeServerDiffs(context, account, serverDiffs, syncResult);
+ notifyChanges();
+ }
+
+ if (result != null) {
+ findLocalChanges(result, temporaryInstanceFactory, account, syncResult);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
+ }
+
+ /**
+ * @hide this is public for testing purposes only
+ */
+ public void mergeServerDiffs(SyncContext context,
+ String account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
+ boolean diffsArePartial = serverDiffs.getContainsDiffs();
+ // mark the current rows so that we can distinguish these from new
+ // inserts that occur during the merge
+ mDb.update(mTable, mSyncMarkValues, null, null);
+ if (mDeletedTable != null) {
+ mDb.update(mDeletedTable, mSyncMarkValues, null, null);
+ }
+
+ // load the local database entries, so we can merge them with the server
+ final String[] accountSelectionArgs = new String[]{account};
+ Cursor localCursor = mDb.query(mTable, syncDirtyProjection,
+ SELECT_MARKED, accountSelectionArgs, null, null,
+ mTable + "." + _SYNC_ID);
+ Cursor deletedCursor;
+ if (mDeletedTable != null) {
+ deletedCursor = mDb.query(mDeletedTable, syncIdAndVersionProjection,
+ SELECT_MARKED, accountSelectionArgs, null, null,
+ mDeletedTable + "." + _SYNC_ID);
+ } else {
+ deletedCursor =
+ mDb.rawQuery("select 'a' as _sync_id, 'b' as _sync_version limit 0", null);
+ }
+
+ // Apply updates and insertions from the server
+ Cursor diffsCursor = serverDiffs.query(mTableURL,
+ null, null, null, mTable + "." + _SYNC_ID);
+ int deletedSyncIDColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_ID);
+ int deletedSyncVersionColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_VERSION);
+ int serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
+ int serverSyncVersionColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_VERSION);
+ int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
+
+ String lastSyncId = null;
+ int diffsCount = 0;
+ int localCount = 0;
+ localCursor.moveToFirst();
+ deletedCursor.moveToFirst();
+ while (diffsCursor.moveToNext()) {
+ if (mIsMergeCancelled) {
+ localCursor.close();
+ deletedCursor.close();
+ diffsCursor.close();
+ return;
+ }
+ mDb.yieldIfContended();
+ String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
+ String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
+ long localRowId = 0;
+ String localSyncVersion = null;
+
+ diffsCount++;
+ context.setStatusText("Processing " + diffsCount + "/"
+ + diffsCursor.getCount());
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processing server entry " +
+ diffsCount + ", " + serverSyncId);
+
+ if (TRACE) {
+ if (diffsCount == 10) {
+ Debug.startMethodTracing("atmtrace");
+ }
+ if (diffsCount == 20) {
+ Debug.stopMethodTracing();
+ }
+ }
+
+ boolean conflict = false;
+ boolean update = false;
+ boolean insert = false;
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "found event with serverSyncID " + serverSyncId);
+ }
+ if (TextUtils.isEmpty(serverSyncId)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.e(TAG, "server entry doesn't have a serverSyncID");
+ }
+ continue;
+ }
+
+ // It is possible that the sync adapter wrote the same record multiple times,
+ // e.g. if the same record came via multiple feeds. If this happens just ignore
+ // the duplicate records.
+ if (serverSyncId.equals(lastSyncId)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "skipping record with duplicate remote server id " + lastSyncId);
+ }
+ continue;
+ }
+ lastSyncId = serverSyncId;
+
+ String localSyncID = null;
+ boolean localSyncDirty = false;
+
+ while (!localCursor.isAfterLast()) {
+ if (mIsMergeCancelled) {
+ localCursor.deactivate();
+ deletedCursor.deactivate();
+ diffsCursor.deactivate();
+ return;
+ }
+ localCount++;
+ localSyncID = localCursor.getString(2);
+
+ // If the local record doesn't have a _sync_id then
+ // it is new. Ignore it for now, we will send an insert
+ // the the server later.
+ if (TextUtils.isEmpty(localSyncID)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "local record " +
+ localCursor.getLong(1) +
+ " has no _sync_id, ignoring");
+ }
+ localCursor.moveToNext();
+ localSyncID = null;
+ continue;
+ }
+
+ int comp = serverSyncId.compareTo(localSyncID);
+
+ // the local DB has a record that the server doesn't have
+ if (comp > 0) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "local record " +
+ localCursor.getLong(1) +
+ " has _sync_id " + localSyncID +
+ " that is < server _sync_id " + serverSyncId);
+ }
+ if (diffsArePartial) {
+ localCursor.moveToNext();
+ } else {
+ deleteRow(localCursor);
+ if (mDeletedTable != null) {
+ mDb.delete(mDeletedTable, _SYNC_ID +"=?", new String[] {localSyncID});
+ }
+ syncResult.stats.numDeletes++;
+ mDb.yieldIfContended();
+ }
+ localSyncID = null;
+ continue;
+ }
+
+ // the server has a record that the local DB doesn't have
+ if (comp < 0) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "local record " +
+ localCursor.getLong(1) +
+ " has _sync_id " + localSyncID +
+ " that is > server _sync_id " + serverSyncId);
+ }
+ localSyncID = null;
+ }
+
+ // the server and the local DB both have this record
+ if (comp == 0) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "local record " +
+ localCursor.getLong(1) +
+ " has _sync_id " + localSyncID +
+ " that matches the server _sync_id");
+ }
+ localSyncDirty = localCursor.getInt(0) != 0;
+ localRowId = localCursor.getLong(1);
+ localSyncVersion = localCursor.getString(3);
+ localCursor.moveToNext();
+ }
+
+ break;
+ }
+
+ // If this record is in the deleted table then update the server version
+ // in the deleted table, if necessary, and then ignore it here.
+ // We will send a deletion indication to the server down a
+ // little further.
+ if (findInCursor(deletedCursor, deletedSyncIDColumn, serverSyncId)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "remote record " + serverSyncId + " is in the deleted table");
+ }
+ final String deletedSyncVersion = deletedCursor.getString(deletedSyncVersionColumn);
+ if (!TextUtils.equals(deletedSyncVersion, serverSyncVersion)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setting version of deleted record " + serverSyncId + " to "
+ + serverSyncVersion);
+ }
+ ContentValues values = new ContentValues();
+ values.put(_SYNC_VERSION, serverSyncVersion);
+ mDb.update(mDeletedTable, values, "_sync_id=?", new String[]{serverSyncId});
+ }
+ continue;
+ }
+
+ // If the _sync_local_id is present in the diffsCursor
+ // then this record corresponds to a local record that was just
+ // inserted into the server and the _sync_local_id is the row id
+ // of the local record. Set these fields so that the next check
+ // treats this record as an update, which will allow the
+ // merger to update the record with the server's sync id
+ if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
+ localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "the remote record with sync id " + serverSyncId
+ + " has a local sync id, " + localRowId);
+ }
+ localSyncID = serverSyncId;
+ localSyncDirty = false;
+ localSyncVersion = null;
+ }
+
+ if (!TextUtils.isEmpty(localSyncID)) {
+ // An existing server item has changed
+ boolean recordChanged = (localSyncVersion == null) ||
+ !serverSyncVersion.equals(localSyncVersion);
+ if (recordChanged) {
+ if (localSyncDirty) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "remote record " + serverSyncId
+ + " conflicts with local _sync_id " + localSyncID
+ + ", local _id " + localRowId);
+ }
+ conflict = true;
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "remote record " +
+ serverSyncId +
+ " updates local _sync_id " +
+ localSyncID + ", local _id " +
+ localRowId);
+ }
+ update = true;
+ }
+ }
+ } else {
+ // the local db doesn't know about this record so add it
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
+ }
+ insert = true;
+ }
+
+ if (update) {
+ updateRow(localRowId, serverDiffs, diffsCursor);
+ syncResult.stats.numUpdates++;
+ } else if (conflict) {
+ resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
+ syncResult.stats.numUpdates++;
+ } else if (insert) {
+ insertRow(serverDiffs, diffsCursor);
+ syncResult.stats.numInserts++;
+ }
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "processed " + diffsCount + " server entries");
+ }
+
+ // If tombstones aren't in use delete any remaining local rows that
+ // don't have corresponding server rows. Keep the rows that don't
+ // have a sync id since those were created locally and haven't been
+ // synced to the server yet.
+ if (!diffsArePartial) {
+ while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
+ if (mIsMergeCancelled) {
+ localCursor.deactivate();
+ deletedCursor.deactivate();
+ diffsCursor.deactivate();
+ return;
+ }
+ localCount++;
+ final String localSyncId = localCursor.getString(2);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "deleting local record " +
+ localCursor.getLong(1) +
+ " _sync_id " + localSyncId);
+ }
+ deleteRow(localCursor);
+ if (mDeletedTable != null) {
+ mDb.delete(mDeletedTable, _SYNC_ID + "=?", new String[] {localSyncId});
+ }
+ syncResult.stats.numDeletes++;
+ mDb.yieldIfContended();
+ }
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "checked " + localCount +
+ " local entries");
+ diffsCursor.deactivate();
+ localCursor.deactivate();
+ deletedCursor.deactivate();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "applying deletions from the server");
+
+ // Apply deletions from the server
+ if (mDeletedTableURL != null) {
+ diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
+
+ while (diffsCursor.moveToNext()) {
+ if (mIsMergeCancelled) {
+ diffsCursor.deactivate();
+ return;
+ }
+ // delete all rows that match each element in the diffsCursor
+ fullyDeleteMatchingRows(diffsCursor, account, syncResult);
+ mDb.yieldIfContended();
+ }
+ diffsCursor.deactivate();
+ }
+ }
+
+ private void fullyDeleteMatchingRows(Cursor diffsCursor, String account,
+ SyncResult syncResult) {
+ int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
+ final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
+
+ // delete the rows explicitly so that the delete operation can be overridden
+ final Cursor c;
+ final String[] selectionArgs;
+ if (deleteBySyncId) {
+ selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn), account};
+ c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
+ selectionArgs, null, null, null);
+ } else {
+ int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
+ selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
+ c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
+ null, null, null);
+ }
+ try {
+ c.moveToFirst();
+ while (!c.isAfterLast()) {
+ deleteRow(c); // advances the cursor
+ syncResult.stats.numDeletes++;
+ }
+ } finally {
+ c.deactivate();
+ }
+ if (deleteBySyncId && mDeletedTable != null) {
+ mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
+ }
+ }
+
+ /**
+ * Converts cursor into a Map, using the correct types for the values.
+ */
+ protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
+ DatabaseUtils.cursorRowToContentValues(cursor, map);
+ }
+
+ /**
+ * Finds local changes, placing the results in the given result object.
+ * @param temporaryInstanceFactory As an optimization for the case
+ * where there are no client-side diffs, mergeResult may initially
+ * have no {@link android.content.TempProviderSyncResult#tempContentProvider}. If this is
+ * the first in the sequence of AbstractTableMergers to find
+ * client-side diffs, it will use the given ContentProvider to
+ * create a temporary instance and store its {@link
+ * ContentProvider} in the mergeResult.
+ * @param account
+ * @param syncResult
+ */
+ private void findLocalChanges(TempProviderSyncResult mergeResult,
+ SyncableContentProvider temporaryInstanceFactory, String account,
+ SyncResult syncResult) {
+ SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
+
+ final String[] accountSelectionArgs = new String[]{account};
+
+ // Generate the client updates and insertions
+ // Create a cursor for dirty records
+ Cursor localChangesCursor = mDb.query(mTable, null, SELECT_UNSYNCED, accountSelectionArgs,
+ null, null, null);
+ long numInsertsOrUpdates = localChangesCursor.getCount();
+ while (localChangesCursor.moveToNext()) {
+ if (mIsMergeCancelled) {
+ localChangesCursor.close();
+ return;
+ }
+ if (clientDiffs == null) {
+ clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
+ }
+ mValues.clear();
+ cursorRowToContentValues(localChangesCursor, mValues);
+ mValues.remove("_id");
+ DatabaseUtils.cursorLongToContentValues(localChangesCursor, "_id", mValues,
+ _SYNC_LOCAL_ID);
+ clientDiffs.insert(mTableURL, mValues);
+ }
+ localChangesCursor.close();
+
+ // Generate the client deletions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client deletions");
+ long numEntries = DatabaseUtils.queryNumEntries(mDb, mTable);
+ long numDeletedEntries = 0;
+ if (mDeletedTable != null) {
+ Cursor deletedCursor = mDb.query(mDeletedTable,
+ syncIdAndVersionProjection,
+ _SYNC_ACCOUNT + "=? AND " + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
+ null, null, mDeletedTable + "." + _SYNC_ID);
+
+ numDeletedEntries = deletedCursor.getCount();
+ while (deletedCursor.moveToNext()) {
+ if (mIsMergeCancelled) {
+ deletedCursor.close();
+ return;
+ }
+ if (clientDiffs == null) {
+ clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
+ }
+ mValues.clear();
+ DatabaseUtils.cursorRowToContentValues(deletedCursor, mValues);
+ clientDiffs.insert(mDeletedTableURL, mValues);
+ }
+ deletedCursor.close();
+ }
+
+ if (clientDiffs != null) {
+ mergeResult.tempContentProvider = clientDiffs;
+ }
+ syncResult.stats.numDeletes += numDeletedEntries;
+ syncResult.stats.numUpdates += numInsertsOrUpdates;
+ syncResult.stats.numEntries += numEntries;
+ }
+}
diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java
new file mode 100644
index 0000000..16149bb
--- /dev/null
+++ b/core/java/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/core/java/android/content/AsyncQueryHandler.java b/core/java/android/content/AsyncQueryHandler.java
new file mode 100644
index 0000000..ac851cc
--- /dev/null
+++ b/core/java/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) {
+ 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 that's 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/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
new file mode 100644
index 0000000..08f6191
--- /dev/null
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -0,0 +1,425 @@
+/*
+ * 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.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Base class for code that will receive intents sent by sendBroadcast().
+ * You can either dynamically register an instance of this class with
+ * {@link Context#registerReceiver Context.registerReceiver()}
+ * or statically publish an implementation through the
+ * {@link android.R.styleable#AndroidManifestReceiver <receiver>}
+ * tag in your <code>AndroidManifest.xml</code>. <em><strong>Note:</strong></em>
+ * If registering a receiver in your
+ * {@link android.app.Activity#onResume() Activity.onResume()}
+ * implementation, you should unregister it in
+ * {@link android.app.Activity#onPause() Activity.onPause()}.
+ * (You won't receive intents when paused,
+ * and this will cut down on unnecessary system overhead). Do not unregister in
+ * {@link android.app.Activity#onSaveInstanceState(android.os.Bundle) Activity.onSaveInstanceState()},
+ * because this won't be called if the user moves back in the history
+ * stack.
+ *
+ * <p>There are two major classes of broadcasts that can be received:</p>
+ * <ul>
+ * <li> <b>Normal broadcasts</b> (sent with {@link Context#sendBroadcast(Intent)
+ * Context.sendBroadcast}) are completely asynchronous. All receivers of the
+ * broadcast are run, in an undefined order, often at the same time. This is
+ * more efficient, but means that receivers can not use the result or abort
+ * APIs included here.
+ * <li> <b>Ordered broadcasts</b> (sent with {@link Context#sendOrderedBroadcast(Intent, String)
+ * Context.sendOrderedBroadcast}) are delivered to one receiver at a time.
+ * As each receiver executes in turn, it can propagate a result to the next
+ * receiver, or it can completely abort the broadcast so that it won't be passed
+ * to other receivers. The order receivers runs in can be controlled with the
+ * {@link android.R.styleable#AndroidManifestIntentFilter_priority
+ * android:priority} attribute of the matching intent-filter; receivers with
+ * the same priority will be run in an arbitrary order.
+ * </ul>
+ *
+ * <p>Even in the case of normal broadcasts, the system may in some
+ * situations revert to delivering the broadcast one receiver at a time. In
+ * particular, for receivers that may require the creation of a process, only
+ * one will be run at a time to avoid overloading the system with new processes.
+ * In this situation, however, the non-ordered semantics hold: these receivers
+ * can not return results or abort their broadcast.</p>
+ *
+ * <p>Note that, although the Intent class is used for sending and receiving
+ * these broadcasts, the Intent broadcast mechanism here is completely separate
+ * from Intents that are used to start Activities with
+ * {@link Context#startActivity Context.startActivity()}.
+ * There is no way for an BroadcastReceiver
+ * to see or capture Intents used with startActivity(); likewise, when
+ * you broadcast an Intent, you will never find or start an Activity.
+ * These two operations are semantically very different: starting an
+ * Activity with an Intent is a foreground operation that modifies what the
+ * user is currently interacting with; broadcasting an Intent is a background
+ * operation that the user is not normally aware of.
+ *
+ * <p>The BroadcastReceiver class (when launched as a component through
+ * a manifest's {@link android.R.styleable#AndroidManifestReceiver <receiver>}
+ * tag) is an important part of an
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ReceiverLifecycle">Receiver Lifecycle</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * </ol>
+ *
+ * <a name="ReceiverLifecycle"></a>
+ * <h3>Receiver Lifecycle</h3>
+ *
+ * <p>A BroadcastReceiver object is only valid for the duration of the call
+ * to {@link #onReceive}. Once your code returns from this function,
+ * the system considers the object to be finished and no longer active.
+ *
+ * <p>This has important repercussions to what you can do in an
+ * {@link #onReceive} implementation: anything that requires asynchronous
+ * operation is not available, because you will need to return from the
+ * function to handle the asynchronous operation, but at that point the
+ * BroadcastReceiver is no longer active and thus the system is free to kill
+ * its process before the asynchronous operation completes.
+ *
+ * <p>In particular, you may <i>not</i> show a dialog or bind to a service from
+ * within an BroadcastReceiver. For the former, you should instead use the
+ * {@link android.app.NotificationManager} API. For the latter, you can
+ * use {@link android.content.Context#startService Context.startService()} to
+ * send a command to the service.
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ *
+ * <p>Access permissions can be enforced by either the sender or receiver
+ * of an Intent.
+ *
+ * <p>To enforce a permission when sending, you supply a non-null
+ * <var>permission</var> argument to
+ * {@link Context#sendBroadcast(Intent, String)} or
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}.
+ * Only receivers who have been granted this permission
+ * (by requesting it with the
+ * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
+ * tag in their <code>AndroidManifest.xml</code>) will be able to receive
+ * the broadcast.
+ *
+ * <p>To enforce a permission when receiving, you supply a non-null
+ * <var>permission</var> when registering your receiver -- either when calling
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)}
+ * or in the static
+ * {@link android.R.styleable#AndroidManifestReceiver <receiver>}
+ * tag in your <code>AndroidManifest.xml</code>. Only broadcasters who have
+ * been granted this permission (by requesting it with the
+ * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
+ * tag in their <code>AndroidManifest.xml</code>) will be able to send an
+ * Intent to the receiver.
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+ * document for more information on permissions and security in general.
+ *
+ * <a name="ProcessLifecycle"></a>
+ * <h3>Process Lifecycle</h3>
+ *
+ * <p>A process that is currently executing an BroadcastReceiver (that is,
+ * currently running the code in its {@link #onReceive} method) is
+ * considered to be a foreground process and will be kept running by the
+ * system except under cases of extreme memory pressure.
+ *
+ * <p>Once you return from onReceive(), the BroadcastReceiver is no longer
+ * active, and its hosting process is only as important as any other application
+ * components that are running in it. This is especially important because if
+ * that process was only hosting the BroadcastReceiver (a common case for
+ * applications that the user has never or not recently interacted with), then
+ * upon returning from onReceive() the system will consider its process
+ * to be empty and aggressively kill it so that resources are available for other
+ * more important processes.
+ *
+ * <p>This means that for longer-running operations you will often use
+ * a {@link android.app.Service} in conjunction with an BroadcastReceiver to keep
+ * the containing process active for the entire time of your operation.
+ */
+public abstract class BroadcastReceiver {
+ 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. The function
+ * is normally called from the main thread of its process, so 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 -- in particular, for interacting
+ * with services, you should use
+ * {@link Context#startService(Intent)} instead of
+ * {@link Context#bindService(Intent, ServiceConnection, int)}. If you wish
+ * to interact with a service that is already running, you can use
+ * {@link #peekService}.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ public abstract void onReceive(Context context, Intent intent);
+
+ /**
+ * Provide a binder to an already-running 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}.
+ *
+ * @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)}
+ * @param service The Intent indicating the service you wish to use. See {@link
+ * Context#startService(Intent)} for more information.
+ */
+ public IBinder peekService(Context myContext, Intent service) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ IBinder binder = null;
+ try {
+ binder = am.peekService(service, service.resolveTypeIfNeeded(
+ myContext.getContentResolver()));
+ } 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><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.
+ *
+ * @see #setResult(int, String, Bundle)
+ */
+ public final void setResultCode(int code) {
+ checkSynchronousHint();
+ mResultCode = code;
+ }
+
+ /**
+ * Retrieve the current result code, as set by the previous receiver.
+ *
+ * @return int The current result code.
+ */
+ public final int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * 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();
+ 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 mResultData;
+ }
+
+ /**
+ * 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();
+ 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) {
+ Bundle e = mResultExtras;
+ if (!makeMap) return e;
+ if (e == null) 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();
+ mResultCode = code;
+ mResultData = data;
+ 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 mAbortBroadcast;
+ }
+
+ /**
+ * 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();
+ mAbortBroadcast = true;
+ }
+
+ /**
+ * Clears the flag indicating that this receiver should abort the current
+ * broadcast.
+ */
+ public final void clearAbortBroadcast() {
+ mAbortBroadcast = false;
+ }
+
+ /**
+ * For internal use, sets the hint about whether this BroadcastReceiver is
+ * running in ordered mode.
+ */
+ public final void setOrderedHint(boolean isOrdered) {
+ mOrderedHint = isOrdered;
+ }
+
+ /**
+ * Control inclusion of debugging help for mismatched
+ * calls to {@ 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 (mOrderedHint) {
+ return;
+ }
+ RuntimeException e = new RuntimeException(
+ "BroadcastReceiver trying to return result during a non-ordered broadcast");
+ e.fillInStackTrace();
+ Log.e("BroadcastReceiver", e.getMessage(), e);
+ }
+
+ private int mResultCode;
+ private String mResultData;
+ private Bundle mResultExtras;
+ private boolean mAbortBroadcast;
+ private boolean mDebugUnregister;
+ private boolean mOrderedHint;
+}
+
diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java
new file mode 100644
index 0000000..dad60b0
--- /dev/null
+++ b/core/java/android/content/ComponentCallbacks.java
@@ -0,0 +1,54 @@
+/*
+ * 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}).
+ */
+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.
+ *
+ * @param newConfig The new device configuration.
+ */
+ void onConfigurationChanged(Configuration newConfig);
+
+ /**
+ * This is called when the overall system is running low on memory, and
+ * would like actively running process to try to tighten their belt. While
+ * the exact point at which this will be called is not defined, generally
+ * it will happen around the time 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>Applications that want to be nice can implement this method to release
+ * any caches or other unnecessary resources they may be holding on to.
+ * The system will perform a gc for you after returning from this method.
+ */
+ void onLowMemory();
+}
diff --git a/core/java/android/content/ComponentName.aidl b/core/java/android/content/ComponentName.aidl
new file mode 100644
index 0000000..40dc8de
--- /dev/null
+++ b/core/java/android/content/ComponentName.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable ComponentName;
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
new file mode 100644
index 0000000..32c6864
--- /dev/null
+++ b/core/java/android/content/ComponentName.java
@@ -0,0 +1,276 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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 {
+ private final String mPackage;
+ private final String mClass;
+
+ /**
+ * 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(String pkg, 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(Context pkg, 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(Context pkg, Class<?> cls) {
+ mPackage = pkg.getPackageName();
+ mClass = cls.getName();
+ }
+
+ /**
+ * Return the package name of this component.
+ */
+ public String getPackageName() {
+ return mPackage;
+ }
+
+ /**
+ * Return the class name of this component.
+ */
+ public 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;
+ }
+
+ /**
+ * 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 String flattenToString() {
+ return mPackage + "/" + mClass;
+ }
+
+ /**
+ * The samee 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 String flattenToShortString() {
+ return mPackage + "/" + getShortClassName();
+ }
+
+ /**
+ * 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 ComponentName unflattenFromString(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 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/core/java/android/content/ContentInsertHandler.java b/core/java/android/content/ContentInsertHandler.java
new file mode 100644
index 0000000..fbf726e
--- /dev/null
+++ b/core/java/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/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
new file mode 100644
index 0000000..25544de
--- /dev/null
+++ b/core/java/android/content/ContentProvider.java
@@ -0,0 +1,609 @@
+/*
+ * 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.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.database.CursorToBulkCursorAdaptor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * 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>For more information, read <a href="{@docRoot}guide/topics/providers/content-providers.html">Content
+ * Providers</a>.</p>
+ *
+ * <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 #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>This class takes care of cross process calls so subclasses don't have to worry about which
+ * process a request is coming from.</p>
+ */
+public abstract class ContentProvider implements ComponentCallbacks {
+ private Context mContext = null;
+ private String mReadPermission;
+ private String mWritePermission;
+
+ private Transport mTransport = new Transport();
+
+ /**
+ * 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-null and local, returns its actual
+ * ContentProvider instance. Otherwise returns 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 {
+ ContentProvider getContentProvider() {
+ return ContentProvider.this;
+ }
+
+ /**
+ * Remote version of a query, which returns an IBulkCursor. The bulk
+ * cursor should be wrapped with BulkCursorToCursorAdaptor before use.
+ */
+ public IBulkCursor bulkQuery(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder,
+ IContentObserver observer, CursorWindow window) {
+ checkReadPermission(uri);
+ Cursor cursor = ContentProvider.this.query(uri, projection,
+ selection, selectionArgs, sortOrder);
+ if (cursor == null) {
+ return null;
+ }
+ String wperm = getWritePermission();
+ return new CursorToBulkCursorAdaptor(cursor, observer,
+ ContentProvider.this.getClass().getName(),
+ wperm == null ||
+ getContext().checkCallingOrSelfPermission(getWritePermission())
+ == PackageManager.PERMISSION_GRANTED,
+ window);
+ }
+
+ public Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ checkReadPermission(uri);
+ return ContentProvider.this.query(uri, projection, selection,
+ selectionArgs, sortOrder);
+ }
+
+ public String getType(Uri uri) {
+ return ContentProvider.this.getType(uri);
+ }
+
+
+ public Uri insert(Uri uri, ContentValues initialValues) {
+ checkWritePermission(uri);
+ return ContentProvider.this.insert(uri, initialValues);
+ }
+
+ public int bulkInsert(Uri uri, ContentValues[] initialValues) {
+ checkWritePermission(uri);
+ return ContentProvider.this.bulkInsert(uri, initialValues);
+ }
+
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ checkWritePermission(uri);
+ return ContentProvider.this.delete(uri, selection, selectionArgs);
+ }
+
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ checkWritePermission(uri);
+ return ContentProvider.this.update(uri, values, selection, selectionArgs);
+ }
+
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+ else checkReadPermission(uri);
+ return ContentProvider.this.openFile(uri, mode);
+ }
+
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+ else checkReadPermission(uri);
+ return ContentProvider.this.openAssetFile(uri, mode);
+ }
+
+ public ISyncAdapter getSyncAdapter() {
+ checkWritePermission(null);
+ return ContentProvider.this.getSyncAdapter().getISyncAdapter();
+ }
+
+ private void checkReadPermission(Uri uri) {
+ final String rperm = getReadPermission();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ if (getContext().checkUriPermission(uri, rperm, null, pid, uid,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ String msg = "Permission Denial: reading "
+ + ContentProvider.this.getClass().getName()
+ + " uri " + uri + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + rperm;
+ throw new SecurityException(msg);
+ }
+
+ private void checkWritePermission(Uri uri) {
+ final String wperm = getWritePermission();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ if (getContext().checkUriPermission(uri, null, wperm, pid, uid,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ String msg = "Permission Denial: writing "
+ + ContentProvider.this.getClass().getName()
+ + " uri " + uri + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + wperm;
+ throw new SecurityException(msg);
+ }
+ }
+
+
+ /**
+ * Retrieve the Context this provider is running in. Only available once
+ * onCreate(Map icicle) has been called -- this will be null in the
+ * constructor.
+ */
+ public final Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * 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(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.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ */
+ public final 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(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.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ */
+ public final String getWritePermission() {
+ return mWritePermission;
+ }
+
+ /**
+ * Called when the provider is being started.
+ *
+ * @return true if the provider was successfully loaded, false otherwise
+ */
+ public abstract boolean onCreate();
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ public void onLowMemory() {
+ }
+
+ /**
+ * Receives a query request from a client in a local process, and
+ * returns a Cursor. This is called internally by the {@link ContentResolver}.
+ * This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ * <p>
+ * Example client call:<p>
+ * <pre>// Request a specific record.
+ * Cursor managedCursor = managedQuery(
+ Contacts.People.CONTENT_URI.addId(2),
+ projection, // Which columns to return.
+ null, // WHERE clause.
+ 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
+ * null all columns are included.
+ * @param selection A selection criteria to apply when filtering rows.
+ * If null then all rows are included.
+ * @param sortOrder How the rows in the cursor should be sorted.
+ * If null then the provider is free to define the sort order.
+ * @return a Cursor or null.
+ */
+ public abstract Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder);
+
+ /**
+ * Return the MIME type of the data at the given URI. This 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.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ *
+ * @param uri the URI to query.
+ * @return a MIME type string, or null if there is no type.
+ */
+ public abstract String getType(Uri uri);
+
+ /**
+ * Implement this 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.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ * @param uri The content:// URI of the insertion request.
+ * @param values A set of column_name/value pairs to add to the database.
+ * @return The URI for the newly inserted item.
+ */
+ public abstract Uri insert(Uri uri, ContentValues values);
+
+ /**
+ * Implement this 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.html#procthread">Application Fundamentals:
+ * 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.
+ * @return The number of values that were inserted.
+ */
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ int numValues = values.length;
+ for (int i = 0; i < numValues; i++) {
+ insert(uri, values[i]);
+ }
+ return numValues;
+ }
+
+ /**
+ * A request to delete one or more rows. The selection clause is applied when performing
+ * the deletion, allowing the operation to affect multiple rows in a
+ * directory.
+ * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()}
+ * after deleting.
+ * This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * 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(Uri uri, String selection, String[] selectionArgs);
+
+ /**
+ * Update a content URI. All rows matching the optionally provided selection
+ * will have their columns listed as the keys in the values map with the
+ * values of those keys.
+ * 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.html#procthread">Application Fundamentals:
+ * 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 Bundle mapping from column names to new column values (NULL is a
+ * valid value).
+ * @param selection An optional filter to match rows to update.
+ * @return the number of rows affected.
+ */
+ public abstract int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs);
+
+ /**
+ * Open a file blob associated with a content URI.
+ * This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ *
+ * <p>Returns a
+ * ParcelFileDescriptor, from which you can obtain a
+ * {@link java.io.FileDescriptor} for use with
+ * {@link java.io.FileInputStream}, {@link java.io.FileOutputStream}, etc.
+ * This can be used to store large data (such as an image) associated with
+ * a particular piece of 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.
+ *
+ * @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)
+ */
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ throw new FileNotFoundException("No files supported by provider at "
+ + uri);
+ }
+
+ /**
+ * 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. Note that when implementing this your clients
+ * must be able to deal with such files, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ *
+ * <p><em>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 can not handle sub-sections of files.</em></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)
+ */
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ ParcelFileDescriptor fd = openFile(uri, mode);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
+
+ /**
+ * 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 ParcelFileDescriptor openFileHelper(Uri uri,
+ 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 = ContentResolver.modeToMode(uri, mode);
+ return ParcelFileDescriptor.open(new File(path), modeBits);
+ }
+
+ /**
+ * Get the sync adapter that is to be used by this content provider.
+ * This is intended for use by the sync system. If null then this
+ * content provider is considered not syncable.
+ * This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ *
+ * @return the SyncAdapter that is to be used by this ContentProvider, or null
+ * if this ContentProvider is not syncable
+ * @hide
+ */
+ public SyncAdapter getSyncAdapter() {
+ return null;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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) {
+
+ /*
+ * 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 (info != null) {
+ setReadPermission(info.readPermission);
+ setWritePermission(info.writePermission);
+ }
+ ContentProvider.this.onCreate();
+ }
+ }
+}
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
new file mode 100644
index 0000000..e5e3f74
--- /dev/null
+++ b/core/java/android/content/ContentProviderNative.java
@@ -0,0 +1,478 @@
+/*
+ * 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.AssetFileDescriptor;
+import android.database.BulkCursorNative;
+import android.database.BulkCursorToCursorAdaptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.DatabaseUtils;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.FileNotFoundException;
+
+/**
+ * {@hide}
+ */
+abstract public class ContentProviderNative extends Binder implements IContentProvider {
+ private static final String TAG = "ContentProvider";
+
+ 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);
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ switch (code) {
+ case QUERY_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ 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();
+ }
+ }
+ String selection = data.readString();
+ num = data.readInt();
+ String[] selectionArgs = null;
+ if (num > 0) {
+ selectionArgs = new String[num];
+ for (int i = 0; i < num; i++) {
+ selectionArgs[i] = data.readString();
+ }
+ }
+ String sortOrder = data.readString();
+ IContentObserver observer = IContentObserver.Stub.
+ asInterface(data.readStrongBinder());
+ CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
+
+ IBulkCursor bulkCursor = bulkQuery(url, projection, selection,
+ selectionArgs, sortOrder, observer, window);
+ reply.writeNoException();
+ if (bulkCursor != null) {
+ reply.writeStrongBinder(bulkCursor.asBinder());
+ } else {
+ reply.writeStrongBinder(null);
+ }
+ 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);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+
+ Uri out = insert(url, values);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case BULK_INSERT_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
+
+ int count = bulkInsert(url, values);
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case DELETE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String selection = data.readString();
+ String[] selectionArgs = data.readStringArray();
+
+ int count = delete(url, selection, selectionArgs);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case UPDATE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ ContentValues values = ContentValues.CREATOR.createFromParcel(data);
+ String selection = data.readString();
+ String[] selectionArgs = data.readStringArray();
+
+ int count = update(url, values, selection, selectionArgs);
+
+ reply.writeNoException();
+ reply.writeInt(count);
+ return true;
+ }
+
+ case OPEN_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+
+ ParcelFileDescriptor fd;
+ fd = openFile(url, mode);
+ 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);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(url, mode);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
+ case GET_SYNC_ADAPTER_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ ISyncAdapter sa = getSyncAdapter();
+ reply.writeNoException();
+ reply.writeStrongBinder(sa != null ? sa.asBinder() : null);
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ DatabaseUtils.writeExceptionToParcel(reply, e);
+ return true;
+ }
+
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ public IBinder asBinder()
+ {
+ return this;
+ }
+}
+
+
+final class ContentProviderProxy implements IContentProvider
+{
+ public ContentProviderProxy(IBinder remote)
+ {
+ mRemote = remote;
+ }
+
+ public IBinder asBinder()
+ {
+ return mRemote;
+ }
+
+ public IBulkCursor bulkQuery(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
+ CursorWindow window) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ 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.writeString(selection);
+ if (selectionArgs != null) {
+ length = selectionArgs.length;
+ } else {
+ length = 0;
+ }
+ data.writeInt(length);
+ for (int i = 0; i < length; i++) {
+ data.writeString(selectionArgs[i]);
+ }
+ data.writeString(sortOrder);
+ data.writeStrongBinder(observer.asBinder());
+ window.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+
+ IBulkCursor bulkCursor = null;
+ IBinder bulkCursorBinder = reply.readStrongBinder();
+ if (bulkCursorBinder != null) {
+ bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
+ }
+
+ data.recycle();
+ reply.recycle();
+
+ return bulkCursor;
+ }
+
+ public Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) throws RemoteException {
+ //TODO make a pool of windows so we can reuse memory dealers
+ CursorWindow window = new CursorWindow(false /* window will be used remotely */);
+ BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
+ IBulkCursor bulkCursor = bulkQuery(url, projection, selection, selectionArgs, sortOrder,
+ adaptor.getObserver(), window);
+
+ if (bulkCursor == null) {
+ return null;
+ }
+ adaptor.set(bulkCursor);
+ return adaptor;
+ }
+
+ public String getType(Uri url) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String out = reply.readString();
+
+ data.recycle();
+ reply.recycle();
+
+ return out;
+ }
+
+ public Uri insert(Uri url, ContentValues values) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ 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);
+
+ data.recycle();
+ reply.recycle();
+
+ return out;
+ }
+
+ public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeTypedArray(values, 0);
+
+ mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+
+ data.recycle();
+ reply.recycle();
+
+ return count;
+ }
+
+ public int delete(Uri url, String selection, String[] selectionArgs)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ 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();
+
+ data.recycle();
+ reply.recycle();
+
+ return count;
+ }
+
+ public int update(Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ 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();
+
+ data.recycle();
+ reply.recycle();
+
+ return count;
+ }
+
+ public ParcelFileDescriptor openFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+
+ mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+
+ 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;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
+ public ISyncAdapter getSyncAdapter() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ mRemote.transact(IContentProvider.GET_SYNC_ADAPTER_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ ISyncAdapter syncAdapter = ISyncAdapter.Stub.asInterface(reply.readStrongBinder());
+
+ data.recycle();
+ reply.recycle();
+
+ return syncAdapter;
+ }
+
+ private IBinder mRemote;
+}
+
diff --git a/core/java/android/content/ContentQueryMap.java b/core/java/android/content/ContentQueryMap.java
new file mode 100644
index 0000000..dbcb4a7
--- /dev/null
+++ b/core/java/android/content/ContentQueryMap.java
@@ -0,0 +1,172 @@
+/*
+ * 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 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();
+ }
+ }
+
+ /**
+ * 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() {
+ mDirty = false;
+ mCursor.requery();
+ readCursorIntoCache();
+ setChanged();
+ notifyObservers();
+ }
+
+ private synchronized void readCursorIntoCache() {
+ // 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 (mCursor.moveToNext()) {
+ ContentValues values = new ContentValues();
+ for (int i = 0; i < mColumnNames.length; i++) {
+ if (i != mKeyColumn) {
+ values.put(mColumnNames[i], mCursor.getString(i));
+ }
+ }
+ mValues.put(mCursor.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/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
new file mode 100644
index 0000000..0d886ee
--- /dev/null
+++ b/core/java/android/content/ContentResolver.java
@@ -0,0 +1,784 @@
+/*
+ * 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.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+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.util.List;
+
+
+/**
+ * This class provides applications access to the content model.
+ */
+public abstract class ContentResolver {
+ public final static String SYNC_EXTRAS_ACCOUNT = "account";
+ public static final String SYNC_EXTRAS_EXPEDITED = "expedited";
+ public static final String SYNC_EXTRAS_FORCE = "force";
+ public static final String SYNC_EXTRAS_UPLOAD = "upload";
+ public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
+ public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
+
+ public static final String SCHEME_CONTENT = "content";
+ public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
+ public static final String SCHEME_FILE = "file";
+
+ /**
+ * 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";
+
+ public ContentResolver(Context context)
+ {
+ mContext = context;
+ }
+
+ /** @hide */
+ protected abstract IContentProvider acquireProvider(Context c, String name);
+ /** @hide */
+ public abstract boolean releaseProvider(IContentProvider icp);
+
+ /**
+ * 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 String getType(Uri url)
+ {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+ try {
+ return provider.getType(url);
+ } catch (RemoteException e) {
+ return null;
+ } catch (java.lang.Exception e) {
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Query the given URI, returning a {@link Cursor} over the result set.
+ *
+ * @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 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 Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ return null;
+ }
+ try {
+ Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
+ if(qCursor == null) {
+ releaseProvider(provider);
+ return null;
+ }
+ //Wrap the cursor object into CursorWrapperInner object
+ return new CursorWrapperInner(qCursor, provider);
+ } catch (RemoteException e) {
+ releaseProvider(provider);
+ return null;
+ } catch(RuntimeException e) {
+ releaseProvider(provider);
+ throw e;
+ }
+ }
+
+ /**
+ * 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 InputStream openInputStream(Uri uri)
+ throws FileNotFoundException {
+ 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");
+ 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 OutputStream openOutputStream(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 OutputStream openOutputStream(Uri uri, String mode)
+ throws FileNotFoundException {
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode);
+ 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 "content:" 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.
+ *
+ * @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 of no
+ * file exists under the URI or the mode is invalid.
+ * @see #openAssetFileDescriptor(Uri, String)
+ */
+ public final ParcelFileDescriptor openFileDescriptor(Uri uri,
+ String mode) throws FileNotFoundException {
+ AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode);
+ 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 "content:" URI. This
+ * interacts with the underlying {@link ContentProvider#openAssetFile}
+ * 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>
+ *
+ * @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 AssetFileDescriptor openAssetFileDescriptor(Uri uri,
+ String mode) throws FileNotFoundException {
+ 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()), modeToMode(uri, mode));
+ return new AssetFileDescriptor(pfd, 0, -1);
+ } else {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ try {
+ AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+ if(fd == null) {
+ releaseProvider(provider);
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
+ releaseProvider(provider);
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ releaseProvider(provider);
+ throw e;
+ } catch (RuntimeException e) {
+ releaseProvider(provider);
+ throw e;
+ }
+ }
+ }
+
+ class OpenResourceIdResult {
+ Resources r;
+ int id;
+ }
+
+ 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;
+ }
+
+ /** @hide */
+ static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
+ int modeBits;
+ if ("r".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+ } else if ("w".equals(mode) || "wt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else if ("wa".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_APPEND;
+ } else if ("rw".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE;
+ } else if ("rwt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else {
+ throw new FileNotFoundException("Bad mode for " + uri + ": "
+ + mode);
+ }
+ return modeBits;
+ }
+
+ /**
+ * 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 Uri insert(Uri url, ContentValues values)
+ {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ return provider.insert(url, values);
+ } catch (RemoteException e) {
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * 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(Uri url, ContentValues[] values)
+ {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ return provider.bulkInsert(url, values);
+ } catch (RemoteException e) {
+ 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(Uri url, String where, String[] selectionArgs)
+ {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ try {
+ return provider.delete(url, where, selectionArgs);
+ } catch (RemoteException e) {
+ 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 deleting, formatted as an SQL WHERE clause
+ (excluding the WHERE itself).
+ * @return the URL of the newly created row
+ * @throws NullPointerException if uri or values are null
+ */
+ public final int update(Uri uri, ContentValues values, String where,
+ String[] selectionArgs) {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ return provider.update(uri, values, where, selectionArgs);
+ } catch (RemoteException e) {
+ return -1;
+ } 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;
+ }
+ String auth = uri.getAuthority();
+ if (auth != null) {
+ return acquireProvider(mContext, uri.getAuthority());
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public final IContentProvider acquireProvider(String name) {
+ if(name == null) {
+ return null;
+ }
+ return acquireProvider(mContext, name);
+ }
+
+ /**
+ * Register an observer class that gets callbacks when data identified by a
+ * given content URI changes.
+ *
+ * @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 notifyForDescendents If <code>true</code> changes to URIs beginning with <code>uri</code>
+ * will also cause notifications to be sent. If <code>false</code> only changes to the exact URI
+ * specified by <em>uri</em> will cause notifications to be sent. If true, than any URI values
+ * at or below the specified URI will also trigger a match.
+ * @param observer The object that receives callbacks when changes occur.
+ * @see #unregisterContentObserver
+ */
+ public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ ContentObserver observer)
+ {
+ try {
+ ContentServiceNative.getDefault().registerContentObserver(uri, notifyForDescendents,
+ observer.getContentObserver());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Unregisters a change observer.
+ *
+ * @param observer The previously registered observer that is no longer needed.
+ * @see #registerContentObserver
+ */
+ public final void unregisterContentObserver(ContentObserver observer) {
+ try {
+ IContentObserver contentObserver = observer.releaseContentObserver();
+ if (contentObserver != null) {
+ ContentServiceNative.getDefault().unregisterContentObserver(
+ contentObserver);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Notify registered observers that a row was updated.
+ * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
+ * By default, CursorAdapter objects will get this notification.
+ *
+ * @param uri
+ * @param observer The observer that originated the change, may be <code>null</null>
+ */
+ public void notifyChange(Uri uri, ContentObserver observer) {
+ notifyChange(uri, observer, true /* sync to network */);
+ }
+
+ /**
+ * Notify registered observers that a row was updated.
+ * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
+ * By default, CursorAdapter objects will get this notification.
+ *
+ * @param uri
+ * @param observer The observer that originated the change, may be <code>null</null>
+ * @param syncToNetwork If true, attempt to sync the change to the network.
+ */
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ try {
+ ContentServiceNative.getDefault().notifyChange(
+ uri, observer == null ? null : observer.getContentObserver(),
+ observer != null && observer.deliverSelfNotifications(), syncToNetwork);
+ } catch (RemoteException 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>
+ * </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.
+ */
+ public void startSync(Uri uri, Bundle extras) {
+ validateSyncExtrasBundle(extras);
+ try {
+ ContentServiceNative.getDefault().startSync(uri, extras);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * 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>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;
+ throw new IllegalArgumentException("unexpected value type: "
+ + value.getClass().getName());
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (RuntimeException exc) {
+ throw new IllegalArgumentException("error unparceling Bundle", exc);
+ }
+ }
+
+ public void cancelSync(Uri uri) {
+ try {
+ ContentServiceNative.getDefault().cancelSync(uri);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private final class CursorWrapperInner extends CursorWrapper {
+ private IContentProvider mContentProvider;
+ public static final String TAG="CursorWrapperInner";
+ private boolean mCloseFlag = false;
+
+ CursorWrapperInner(Cursor cursor, IContentProvider icp) {
+ super(cursor);
+ mContentProvider = icp;
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ ContentResolver.this.releaseProvider(mContentProvider);
+ mCloseFlag = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if(!mCloseFlag) {
+ ContentResolver.this.releaseProvider(mContentProvider);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
+ private IContentProvider mContentProvider;
+ public static final String TAG="ParcelFileDescriptorInner";
+ private boolean mReleaseProviderFlag = false;
+
+ ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) {
+ super(pfd);
+ mContentProvider = icp;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if(!mReleaseProviderFlag) {
+ super.close();
+ ContentResolver.this.releaseProvider(mContentProvider);
+ mReleaseProviderFlag = true;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (!mReleaseProviderFlag) {
+ close();
+ }
+ }
+ }
+
+ private final Context mContext;
+ private static final String TAG = "ContentResolver";
+}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
new file mode 100644
index 0000000..b028868
--- /dev/null
+++ b/core/java/android/content/ContentService.java
@@ -0,0 +1,376 @@
+/*
+ * 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.database.IContentObserver;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Config;
+import android.util.Log;
+import android.Manifest;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class ContentService extends ContentServiceNative {
+ private static final String TAG = "ContentService";
+ private Context mContext;
+ private boolean mFactoryTest;
+ private final ObserverNode mRootNode = new ObserverNode("");
+ private SyncManager mSyncManager = null;
+ private final Object mSyncManagerLock = new Object();
+
+ private SyncManager getSyncManager() {
+ synchronized(mSyncManagerLock) {
+ try {
+ // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
+ if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Can't create SyncManager", e);
+ }
+ return mSyncManager;
+ }
+ }
+
+ @Override
+ protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
+ "caller doesn't have the DUMP permission");
+
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ if (mSyncManager == null) {
+ pw.println("No SyncManager created! (Disk full?)");
+ } else {
+ mSyncManager.dump(fd, pw);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /*package*/ ContentService(Context context, boolean factoryTest) {
+ mContext = context;
+ mFactoryTest = factoryTest;
+ getSyncManager();
+ }
+
+ public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ IContentObserver observer) {
+ if (observer == null || uri == null) {
+ throw new IllegalArgumentException("You must pass a valid uri and observer");
+ }
+ synchronized (mRootNode) {
+ mRootNode.addObserver(uri, observer, notifyForDescendents);
+ if (Config.LOGV) Log.v(TAG, "Registered observer " + observer + " at " + uri +
+ " with notifyForDescendents " + notifyForDescendents);
+ }
+ }
+
+ public void unregisterContentObserver(IContentObserver observer) {
+ if (observer == null) {
+ throw new IllegalArgumentException("You must pass a valid observer");
+ }
+ synchronized (mRootNode) {
+ mRootNode.removeObserver(observer);
+ if (Config.LOGV) Log.v(TAG, "Unregistered observer " + observer);
+ }
+ }
+
+ public void notifyChange(Uri uri, IContentObserver observer,
+ boolean observerWantsSelfNotifications, boolean syncToNetwork) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Notifying update of " + uri + " from observer " + observer
+ + ", syncToNetwork " + syncToNetwork);
+ }
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+ synchronized (mRootNode) {
+ mRootNode.collectObservers(uri, 0, observer, observerWantsSelfNotifications,
+ calls);
+ }
+ final int numCalls = calls.size();
+ for (int i=0; i<numCalls; i++) {
+ ObserverCall oc = calls.get(i);
+ try {
+ oc.mObserver.onChange(oc.mSelfNotify);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
+ }
+ } catch (RemoteException ex) {
+ synchronized (mRootNode) {
+ Log.w(TAG, "Found dead observer, removing");
+ IBinder binder = oc.mObserver.asBinder();
+ final ArrayList<ObserverNode.ObserverEntry> list
+ = oc.mNode.mObservers;
+ int numList = list.size();
+ for (int j=0; j<numList; j++) {
+ ObserverNode.ObserverEntry oe = list.get(j);
+ if (oe.observer.asBinder() == binder) {
+ list.remove(j);
+ j--;
+ numList--;
+ }
+ }
+ }
+ }
+ }
+ if (syncToNetwork) {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) syncManager.scheduleLocalSync(uri);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
+ * Hide this class since it is not part of api,
+ * but current unittest framework requires it to be public
+ * @hide
+ *
+ */
+ public static final class ObserverCall {
+ final ObserverNode mNode;
+ final IContentObserver mObserver;
+ final boolean mSelfNotify;
+
+ ObserverCall(ObserverNode node, IContentObserver observer,
+ boolean selfNotify) {
+ mNode = node;
+ mObserver = observer;
+ mSelfNotify = selfNotify;
+ }
+ }
+
+ public void startSync(Uri url, Bundle extras) {
+ ContentResolver.validateSyncExtrasBundle(extras);
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) syncManager.startSync(url, extras);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
+ * Clear all scheduled sync operations that match the uri and cancel the active sync
+ * if it matches the uri. If the uri is null, clear all scheduled syncs and cancel
+ * the active one, if there is one.
+ * @param uri Filter on the sync operations to cancel, or all if null.
+ */
+ public void cancelSync(Uri uri) {
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.clearScheduledSyncOperations(uri);
+ syncManager.cancelActiveSync(uri);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public static IContentService main(Context context, boolean factoryTest) {
+ ContentService service = new ContentService(context, factoryTest);
+ ServiceManager.addService("content", service);
+ return service;
+ }
+
+ /**
+ * Hide this class since it is not part of api,
+ * but current unittest framework requires it to be public
+ * @hide
+ */
+ public static final class ObserverNode {
+ private class ObserverEntry implements IBinder.DeathRecipient {
+ public IContentObserver observer;
+ public boolean notifyForDescendents;
+
+ public ObserverEntry(IContentObserver o, boolean n) {
+ observer = o;
+ notifyForDescendents = n;
+ try {
+ observer.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ public void binderDied() {
+ removeObserver(observer);
+ }
+ }
+
+ public static final int INSERT_TYPE = 0;
+ public static final int UPDATE_TYPE = 1;
+ public static final int DELETE_TYPE = 2;
+
+ private String mName;
+ private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
+ private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
+
+ public ObserverNode(String name) {
+ mName = name;
+ }
+
+ private String getUriSegment(Uri uri, int index) {
+ if (uri != null) {
+ if (index == 0) {
+ return uri.getAuthority();
+ } else {
+ return uri.getPathSegments().get(index - 1);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ private int countUriSegments(Uri uri) {
+ if (uri == null) {
+ return 0;
+ }
+ return uri.getPathSegments().size() + 1;
+ }
+
+ public void addObserver(Uri uri, IContentObserver observer, boolean notifyForDescendents) {
+ addObserver(uri, 0, observer, notifyForDescendents);
+ }
+
+ private void addObserver(Uri uri, int index, IContentObserver observer,
+ boolean notifyForDescendents) {
+
+ // If this is the leaf node add the observer
+ if (index == countUriSegments(uri)) {
+ mObservers.add(new ObserverEntry(observer, notifyForDescendents));
+ return;
+ }
+
+ // Look to see if the proper child already exists
+ String segment = getUriSegment(uri, index);
+ int N = mChildren.size();
+ for (int i = 0; i < N; i++) {
+ ObserverNode node = mChildren.get(i);
+ if (node.mName.equals(segment)) {
+ node.addObserver(uri, index + 1, observer, notifyForDescendents);
+ return;
+ }
+ }
+
+ // No child found, create one
+ ObserverNode node = new ObserverNode(segment);
+ mChildren.add(node);
+ node.addObserver(uri, index + 1, observer, notifyForDescendents);
+ }
+
+ public boolean removeObserver(IContentObserver observer) {
+ int size = mChildren.size();
+ for (int i = 0; i < size; i++) {
+ boolean empty = mChildren.get(i).removeObserver(observer);
+ if (empty) {
+ mChildren.remove(i);
+ i--;
+ size--;
+ }
+ }
+
+ IBinder observerBinder = observer.asBinder();
+ size = mObservers.size();
+ for (int i = 0; i < size; i++) {
+ ObserverEntry entry = mObservers.get(i);
+ if (entry.observer.asBinder() == observerBinder) {
+ mObservers.remove(i);
+ // We no longer need to listen for death notifications. Remove it.
+ observerBinder.unlinkToDeath(entry, 0);
+ break;
+ }
+ }
+
+ if (mChildren.size() == 0 && mObservers.size() == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private void collectMyObservers(Uri uri,
+ boolean leaf, IContentObserver observer, boolean selfNotify,
+ ArrayList<ObserverCall> calls)
+ {
+ int N = mObservers.size();
+ IBinder observerBinder = observer == null ? null : observer.asBinder();
+ for (int i = 0; i < N; i++) {
+ ObserverEntry entry = mObservers.get(i);
+
+ // Don't notify the observer if it sent the notification and isn't interesed
+ // in self notifications
+ if (entry.observer.asBinder() == observerBinder && !selfNotify) {
+ continue;
+ }
+
+ // Make sure the observer is interested in the notification
+ if (leaf || (!leaf && entry.notifyForDescendents)) {
+ calls.add(new ObserverCall(this, entry.observer, selfNotify));
+ }
+ }
+ }
+
+ public void collectObservers(Uri uri, int index, IContentObserver observer,
+ boolean selfNotify, ArrayList<ObserverCall> calls) {
+ String segment = null;
+ int segmentCount = countUriSegments(uri);
+ if (index >= segmentCount) {
+ // This is the leaf node, notify all observers
+ collectMyObservers(uri, true, observer, selfNotify, calls);
+ } else if (index < segmentCount){
+ segment = getUriSegment(uri, index);
+ // Notify any observers at this level who are interested in descendents
+ collectMyObservers(uri, false, observer, selfNotify, calls);
+ }
+
+ int N = mChildren.size();
+ for (int i = 0; i < N; i++) {
+ ObserverNode node = mChildren.get(i);
+ if (segment == null || node.mName.equals(segment)) {
+ // We found the child,
+ node.collectObservers(uri, index + 1, observer, selfNotify, calls);
+ if (segment != null) {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/content/ContentServiceNative.java b/core/java/android/content/ContentServiceNative.java
new file mode 100644
index 0000000..364f9ee
--- /dev/null
+++ b/core/java/android/content/ContentServiceNative.java
@@ -0,0 +1,216 @@
+/*
+ * 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.database.IContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ServiceManager;
+import android.os.Bundle;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+abstract class ContentServiceNative extends Binder implements IContentService
+{
+ public ContentServiceNative()
+ {
+ attachInterface(this, descriptor);
+ }
+
+ /**
+ * Cast a Binder object into a content resolver interface, generating
+ * a proxy if needed.
+ */
+ static public IContentService asInterface(IBinder obj)
+ {
+ if (obj == null) {
+ return null;
+ }
+ IContentService in =
+ (IContentService)obj.queryLocalInterface(descriptor);
+ if (in != null) {
+ return in;
+ }
+
+ return new ContentServiceProxy(obj);
+ }
+
+ /**
+ * Retrieve the system's default/global content service.
+ */
+ static public IContentService getDefault()
+ {
+ if (gDefault != null) {
+ return gDefault;
+ }
+ IBinder b = ServiceManager.getService("content");
+ if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
+ gDefault = asInterface(b);
+ if (Config.LOGV) Log.v("ContentService", "default service = " + gDefault);
+ return gDefault;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ {
+ try {
+ switch (code) {
+ case 5038: {
+ data.readString(); // ignore the interface token that service generated
+ Uri uri = Uri.parse(data.readString());
+ notifyChange(uri, null, false, false);
+ return true;
+ }
+
+ case REGISTER_CONTENT_OBSERVER_TRANSACTION: {
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ boolean notifyForDescendents = data.readInt() != 0;
+ IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
+ registerContentObserver(uri, notifyForDescendents, observer);
+ return true;
+ }
+
+ case UNREGISTER_CHANGE_OBSERVER_TRANSACTION: {
+ IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
+ unregisterContentObserver(observer);
+ return true;
+ }
+
+ case NOTIFY_CHANGE_TRANSACTION: {
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
+ boolean observerWantsSelfNotifications = data.readInt() != 0;
+ boolean syncToNetwork = data.readInt() != 0;
+ notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork);
+ return true;
+ }
+
+ case START_SYNC_TRANSACTION: {
+ Uri url = null;
+ int hasUrl = data.readInt();
+ if (hasUrl != 0) {
+ url = Uri.CREATOR.createFromParcel(data);
+ }
+ startSync(url, data.readBundle());
+ return true;
+ }
+
+ case CANCEL_SYNC_TRANSACTION: {
+ Uri url = null;
+ int hasUrl = data.readInt();
+ if (hasUrl != 0) {
+ url = Uri.CREATOR.createFromParcel(data);
+ }
+ cancelSync(url);
+ return true;
+ }
+
+ default:
+ return super.onTransact(code, data, reply, flags);
+ }
+ } catch (Exception e) {
+ Log.e("ContentServiceNative", "Caught exception in transact", e);
+ }
+
+ return false;
+ }
+
+ public IBinder asBinder()
+ {
+ return this;
+ }
+
+ private static IContentService gDefault;
+}
+
+
+final class ContentServiceProxy implements IContentService
+{
+ public ContentServiceProxy(IBinder remote)
+ {
+ mRemote = remote;
+ }
+
+ public IBinder asBinder()
+ {
+ return mRemote;
+ }
+
+ public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ IContentObserver observer) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ uri.writeToParcel(data, 0);
+ data.writeInt(notifyForDescendents ? 1 : 0);
+ data.writeStrongInterface(observer);
+ mRemote.transact(REGISTER_CONTENT_OBSERVER_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
+ public void unregisterContentObserver(IContentObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeStrongInterface(observer);
+ mRemote.transact(UNREGISTER_CHANGE_OBSERVER_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
+ public void notifyChange(Uri uri, IContentObserver observer,
+ boolean observerWantsSelfNotifications, boolean syncToNetwork)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ uri.writeToParcel(data, 0);
+ data.writeStrongInterface(observer);
+ data.writeInt(observerWantsSelfNotifications ? 1 : 0);
+ data.writeInt(syncToNetwork ? 1 : 0);
+ mRemote.transact(NOTIFY_CHANGE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
+
+ public void startSync(Uri url, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ if (url == null) {
+ data.writeInt(0);
+ } else {
+ data.writeInt(1);
+ url.writeToParcel(data, 0);
+ }
+ extras.writeToParcel(data, 0);
+ mRemote.transact(START_SYNC_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
+
+ public void cancelSync(Uri url) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ if (url == null) {
+ data.writeInt(0);
+ } else {
+ data.writeInt(1);
+ url.writeToParcel(data, 0);
+ }
+ mRemote.transact(CANCEL_SYNC_TRANSACTION, data, null /* reply */, IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
+
+ private IBinder mRemote;
+}
+
diff --git a/core/java/android/content/ContentUris.java b/core/java/android/content/ContentUris.java
new file mode 100644
index 0000000..aa7603470b
--- /dev/null
+++ b/core/java/android/content/ContentUris.java
@@ -0,0 +1,67 @@
+/*
+ * 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 content {@link android.net.Uri}s,
+ * those with a "content" scheme.
+ */
+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/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
new file mode 100644
index 0000000..532cc03
--- /dev/null
+++ b/core/java/android/content/ContentValues.java
@@ -0,0 +1,501 @@
+/*
+ * 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 from 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();
+ }
+
+ /**
+ * 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}, and
+ * {@link Number} implementations.
+ *
+ * @param key the value to get
+ * @return the data for the value
+ */
+ 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 ? mValues.get(key).toString() : null;
+ }
+
+ /**
+ * Gets a value and converts it to a Long.
+ *
+ * @param key the value to get
+ * @return the Long value, or 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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to an Integer.
+ *
+ * @param key the value to get
+ * @return the Integer value, or 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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Short.
+ *
+ * @param key the value to get
+ * @return the Short value, or 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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Byte.
+ *
+ * @param key the value to get
+ * @return the Byte value, or 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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Double.
+ *
+ * @param key the value to get
+ * @return the Double value, or 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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Float.
+ *
+ * @param key the value to get
+ * @return the Float value, or 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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets a value and converts it to a Boolean.
+ *
+ * @param key the value to get
+ * @return the Boolean value, or 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) {
+ return Boolean.valueOf(value.toString());
+ } else {
+ Log.e(TAG, "Cannot cast value for " + key + " to a Boolean");
+ 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 byte[] value, or null is the value is missing or not a 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();
+ }
+
+ 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);
+ }
+
+ @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/core/java/android/content/Context.java b/core/java/android/content/Context.java
new file mode 100644
index 0000000..e0fe533
--- /dev/null
+++ b/core/java/android/content/Context.java
@@ -0,0 +1,1654 @@
+/*
+ * 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.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+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.Looper;
+import android.util.AttributeSet;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 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 {
+ /**
+ * 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).
+ * @see #MODE_WORLD_READABLE
+ * @see #MODE_WORLD_WRITEABLE
+ */
+ public static final int MODE_PRIVATE = 0x0000;
+ /**
+ * File creation mode: allow all other applications to have read access
+ * to the created file.
+ * @see #MODE_PRIVATE
+ * @see #MODE_WORLD_WRITEABLE
+ */
+ public static final int MODE_WORLD_READABLE = 0x0001;
+ /**
+ * File creation mode: allow all other applications to have write access
+ * to the created file.
+ * @see #MODE_PRIVATE
+ * @see #MODE_WORLD_READABLE
+ */
+ 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;
+
+ /**
+ * 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#onStart} 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>Specifying this flag also tells the system to treat the service
+ * as being as important as your own process -- that is, when deciding
+ * which process should be killed to free memory, the service will only
+ * be considered a candidate as long as the processes of any such bindings
+ * is also a candidate to be killed. This is to avoid situations where
+ * the service is being continually created and killed due to low memory.
+ */
+ 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;
+
+ /** Return an AssetManager instance for your application's package. */
+ public abstract AssetManager getAssets();
+
+ /** Return a Resources instance for your application's package. */
+ 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).
+ */
+ public abstract Looper getMainLooper();
+
+ /**
+ * Return the context of the single, global Application object of the
+ * current process.
+ */
+ public abstract Context getApplicationContext();
+
+ /**
+ * 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(int resId) {
+ return getResources().getText(resId);
+ }
+
+ /**
+ * Return a localized string from the application's package's
+ * default string table.
+ *
+ * @param resId Resource id for the string
+ */
+ public final String getString(int resId) {
+ return getResources().getString(resId);
+ }
+
+ /**
+ * Return 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.
+ */
+
+ public final String getString(int resId, Object... formatArgs) {
+ return getResources().getString(resId, formatArgs);
+ }
+
+ /**
+ * 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(int resid);
+
+ /**
+ * Return the Theme object associated with this Context.
+ */
+ public abstract Resources.Theme getTheme();
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link Resources.Theme#obtainStyledAttributes(int[])}
+ * for more information.
+ *
+ * @see Resources.Theme#obtainStyledAttributes(int[])
+ */
+ public final TypedArray obtainStyledAttributes(
+ int[] attrs) {
+ return getTheme().obtainStyledAttributes(attrs);
+ }
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link Resources.Theme#obtainStyledAttributes(int, int[])}
+ * for more information.
+ *
+ * @see Resources.Theme#obtainStyledAttributes(int, int[])
+ */
+ public final TypedArray obtainStyledAttributes(
+ int resid, int[] attrs) throws Resources.NotFoundException {
+ return getTheme().obtainStyledAttributes(resid, attrs);
+ }
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * for more information.
+ *
+ * @see Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ public final TypedArray obtainStyledAttributes(
+ AttributeSet set, int[] attrs) {
+ return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ /**
+ * Retrieve styled attribute information in this Context's theme. See
+ * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * for more information.
+ *
+ * @see Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ */
+ public final TypedArray obtainStyledAttributes(
+ AttributeSet set, int[] attrs, int defStyleAttr, 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 full path to this context's resource files. This is the ZIP files
+ * containing the application's 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();
+
+ /**
+ * {@hide}
+ * Return the full path to this context's code and asset files. This is the ZIP files
+ * containing the application's 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();
+
+ /**
+ * 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. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_WORLD_READABLE}
+ * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+ *
+ * @return Returns the single SharedPreferences instance that can be used
+ * to retrieve and modify the preference values.
+ *
+ * @see #MODE_PRIVATE
+ * @see #MODE_WORLD_READABLE
+ * @see #MODE_WORLD_WRITEABLE
+ */
+ public abstract SharedPreferences getSharedPreferences(String name,
+ int mode);
+
+ /**
+ * 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 FileInputStream Resulting input stream.
+ *
+ * @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.
+ *
+ * @param name The name of the file to open; can not contain path
+ * separators.
+ * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_APPEND} to append to an existing file,
+ * {@link #MODE_WORLD_READABLE} and {@link #MODE_WORLD_WRITEABLE} to control
+ * permissions.
+ *
+ * @return FileOutputStream Resulting output stream.
+ *
+ * @see #MODE_APPEND
+ * @see #MODE_PRIVATE
+ * @see #MODE_WORLD_READABLE
+ * @see #MODE_WORLD_WRITEABLE
+ * @see #openFileInput
+ * @see #fileList
+ * @see #deleteFile
+ * @see java.io.FileOutputStream#FileOutputStream(String)
+ */
+ public abstract FileOutputStream openFileOutput(String name, 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 True if the file was successfully deleted; else
+ * 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.
+ *
+ * @param name The name of the file for which you would like to get
+ * its path.
+ *
+ * @return Returns an absolute path to the given file.
+ *
+ * @see #openFileOutput
+ * @see #getFilesDir
+ * @see #getDir
+ */
+ public abstract File getFileStreamPath(String name);
+
+ /**
+ * Returns the absolute path to the directory on the filesystem where
+ * files created with {@link #openFileOutput} are stored.
+ *
+ * @return Returns the path of the directory holding application files.
+ *
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ */
+ public abstract File getFilesDir();
+
+ /**
+ * Returns the absolute path to the application specific cache directory
+ * on the filesystem. These files will be ones that get deleted first when the
+ * device runs low on storage
+ * There is no guarantee when these files will be deleted.
+ *
+ * @return Returns the path of the directory holding application cache files.
+ *
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ */
+ public abstract File getCacheDir();
+
+ /**
+ * 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.
+ *
+ * @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. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_WORLD_READABLE} and
+ * {@link #MODE_WORLD_WRITEABLE} to control permissions.
+ *
+ * @return Returns a 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, 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. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_WORLD_READABLE}
+ * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+ * @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_WORLD_READABLE
+ * @see #MODE_WORLD_WRITEABLE
+ * @see #deleteDatabase
+ */
+ public abstract SQLiteDatabase openOrCreateDatabase(String name,
+ int mode, CursorFactory factory);
+
+ /**
+ * 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 True if the database was successfully deleted; else 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.
+ *
+ * @param name The name of the database for which you would like to get
+ * its path.
+ *
+ * @return Returns 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();
+
+ /**
+ * Like {@link #peekWallpaper}, but always returns a valid Drawable. If
+ * no wallpaper is set, the system default wallpaper is returned.
+ *
+ * @return Returns a Drawable object that will draw the wallpaper.
+ */
+ public abstract Drawable getWallpaper();
+
+ /**
+ * Retrieve the current system wallpaper. This is returned as an
+ * abstract Drawable that you can install in a View to display whatever
+ * wallpaper the user has currently set. If there is no wallpaper set,
+ * a null pointer is returned.
+ *
+ * @return Returns a Drawable object that will draw the wallpaper or a
+ * null pointer if these is none.
+ */
+ public abstract Drawable peekWallpaper();
+
+ /**
+ * Returns the desired minimum width for the wallpaper. Callers of
+ * {@link #setWallpaper(android.graphics.Bitmap)} or
+ * {@link #setWallpaper(java.io.InputStream)} should check this value
+ * beforehand to make sure the supplied wallpaper respects the desired
+ * minimum width.
+ *
+ * If the returned value is <= 0, the caller should use the width of
+ * the default display instead.
+ *
+ * @return The desired minimum width for the wallpaper. This value should
+ * be honored by applications that set the wallpaper but it is not
+ * mandatory.
+ */
+ public abstract int getWallpaperDesiredMinimumWidth();
+
+ /**
+ * Returns the desired minimum height for the wallpaper. Callers of
+ * {@link #setWallpaper(android.graphics.Bitmap)} or
+ * {@link #setWallpaper(java.io.InputStream)} should check this value
+ * beforehand to make sure the supplied wallpaper respects the desired
+ * minimum height.
+ *
+ * If the returned value is <= 0, the caller should use the height of
+ * the default display instead.
+ *
+ * @return The desired minimum height for the wallpaper. This value should
+ * be honored by applications that set the wallpaper but it is not
+ * mandatory.
+ */
+ public abstract int getWallpaperDesiredMinimumHeight();
+
+ /**
+ * Change the current system wallpaper to a bitmap. The given bitmap is
+ * converted to a PNG and stored as the wallpaper. On success, the intent
+ * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
+ *
+ * @param bitmap The bitmap to save.
+ *
+ * @throws IOException If an error occurs reverting to the default
+ * wallpaper.
+ */
+ public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+
+ /**
+ * Change the current system wallpaper to a specific byte stream. The
+ * give InputStream is copied into persistent storage and will now be
+ * used as the wallpaper. Currently it must be either a JPEG or PNG
+ * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
+ * is broadcast.
+ *
+ * @param data A stream containing the raw data to install as a wallpaper.
+ *
+ * @throws IOException If an error occurs reverting to the default
+ * wallpaper.
+ */
+ public abstract void setWallpaper(InputStream data) throws IOException;
+
+ /**
+ * Remove any currently set wallpaper, reverting to the system's default
+ * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
+ * is broadcast.
+ *
+ * @throws IOException If an error occurs reverting to the default
+ * wallpaper.
+ */
+ public abstract void clearWallpaper() throws IOException;
+
+ /**
+ * 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.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivity(Intent intent);
+
+ /**
+ * 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(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 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)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendBroadcast(Intent intent,
+ String receiverPermission);
+
+ /**
+ * 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(Intent intent,
+ 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. If you use
+ * an <var>resultReceiver</var> with this method, then 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 #sendStickyBroadcast(Intent)
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see android.app.Activity#RESULT_OK
+ */
+ public abstract void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras);
+
+ /**
+ * 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)}.
+ *
+ * <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.
+ *
+ * @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)
+ */
+ public abstract void sendStickyBroadcast(Intent intent);
+
+ /**
+ * Remove the data previously sent with {@link #sendStickyBroadcast},
+ * so that it is as if the sticky broadcast had never happened.
+ *
+ * <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.
+ *
+ * @param intent The Intent that was previously broadcast.
+ *
+ * @see #sendStickyBroadcast
+ */
+ public abstract void removeStickyBroadcast(Intent intent);
+
+ /**
+ * Register an 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 as 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 class="note">Note: this method <em>can not be called from an
+ * {@link BroadcastReceiver} component</em>. It is okay, however, to use
+ * this method from another BroadcastReceiver that has itself been registered with
+ * {@link #registerReceiver}, since the lifetime of such an BroadcastReceiver
+ * is tied to another object (the one 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
+ */
+ public abstract Intent registerReceiver(BroadcastReceiver receiver,
+ IntentFilter filter);
+
+ /**
+ * 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.
+ *
+ * @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
+ */
+ public abstract Intent registerReceiver(BroadcastReceiver receiver,
+ IntentFilter filter,
+ String broadcastPermission,
+ 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
+ * can either contain the complete class name of a specific service
+ * implementation to start, or an abstract definition through the
+ * action and other fields of the kind of service to start. 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#onStart} 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()
+ * are not nesting: 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.
+ *
+ * @param service Identifies the service to be started. The Intent may
+ * specify either an explicit component name to start, or a logical
+ * description (action, category, etc) to match an
+ * {@link IntentFilter} published by a service. 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
+ *
+ * @see #stopService
+ * @see #bindService
+ */
+ public abstract ComponentName startService(Intent service);
+
+ /**
+ * 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 may
+ * specify either an explicit component name to start, or a logical
+ * description (action, category, etc) to match an
+ * {@link IntentFilter} published by a service.
+ *
+ * @return If there is a service matching the given Intent that is already
+ * running, then it is stopped and true is returned; else false is returned.
+ *
+ * @throws SecurityException
+ *
+ * @see #startService
+ */
+ public abstract boolean stopService(Intent service);
+
+ /**
+ * 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 its 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 an
+ * {@link BroadcastReceiver} component</em>. A pattern you can use to
+ * communicate from an 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 an 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 may
+ * specify either an explicit component name, or a logical
+ * description (action, category, etc) to match an
+ * {@link IntentFilter} published by a service.
+ * @param conn Receives information as the service is started and stopped.
+ * @param flags Operation options for the binding. May be 0 or
+ * {@link #BIND_AUTO_CREATE}.
+ * @return If you have successfully bound to the service, true is returned;
+ * false is returned if the connection is not made so you will not
+ * receive the service object.
+ *
+ * @throws SecurityException
+ *
+ * @see #unbindService
+ * @see #startService
+ * @see #BIND_AUTO_CREATE
+ */
+ public abstract boolean bindService(Intent service, ServiceConnection conn,
+ int flags);
+
+ /**
+ * 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().
+ *
+ * @see #bindService
+ */
+ public abstract void unbindService(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 Returns true if the instrumentation was successfully started,
+ * else false if it could not be found.
+ */
+ public abstract boolean startInstrumentation(ComponentName className,
+ String profileFile, Bundle arguments);
+
+ /**
+ * 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.
+ * <dt> {@link #INPUT_METHOD_SERVICE} ("input_method")
+ * <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager}
+ * for management of input methods.
+ * </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 #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 #TELEPHONY_SERVICE
+ * @see android.telephony.TelephonyManager
+ * @see #INPUT_METHOD_SERVICE
+ * @see android.view.inputmethod.InputMethodManager
+ */
+ public abstract Object getSystemService(String name);
+
+ /**
+ * 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.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.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.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.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.bluetooth.BluetoothDevice} for interacting with Bluetooth.
+ *
+ * @see #getSystemService
+ * @see android.bluetooth.BluetoothDevice
+ * @hide
+ */
+ public static final String BLUETOOTH_SERVICE = "bluetooth";
+ /**
+ * 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.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.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.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.text.ClipboardManager} for accessing and modifying
+ * the contents of the global clipboard.
+ *
+ * @see #getSystemService
+ * @see android.text.ClipboardManager
+ */
+ public static final String CLIPBOARD_SERVICE = "clipboard";
+
+ /**
+ * 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
+ * {@blink android.gadget.GadgetManager} for accessing wallpapers.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String GADGET_SERVICE = "gadget";
+
+ /**
+ * 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 Returns {@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
+ */
+ public abstract int checkPermission(String permission, int pid, int uid);
+
+ /**
+ * 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 Returns {@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
+ */
+ public abstract int checkCallingPermission(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 Returns {@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
+ */
+ public abstract int checkCallingOrSelfPermission(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(
+ String permission, int pid, int uid, 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(
+ String permission, 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(
+ String permission, 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. Any combination of
+ * {@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}.
+ *
+ * @see #revokeUriPermission
+ */
+ public abstract void grantUriPermission(String toPackage, Uri uri,
+ int modeFlags);
+
+ /**
+ * Remove all permissions to access a particular content provider Uri
+ * that were previously added with {@link #grantUriPermission}. 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/one" will
+ * revoke both "content://foo/target" and "content://foo/target/sub", but not
+ * "content://foo".
+ *
+ * @param uri The Uri you would like to revoke access to.
+ * @param modeFlags The desired access modes. Any combination of
+ * {@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}.
+ *
+ * @see #grantUriPermission
+ */
+ public abstract void revokeUriPermission(Uri uri, 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 type of access to grant. May be one or both of
+ * {@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}.
+ *
+ * @return Returns {@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
+ */
+ public abstract int checkUriPermission(Uri uri, int pid, int uid, int modeFlags);
+
+ /**
+ * 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 type of access to grant. May be one or both of
+ * {@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}.
+ *
+ * @return Returns {@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)
+ */
+ public abstract int checkCallingUriPermission(Uri uri, 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 type of access to grant. May be one or both of
+ * {@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}.
+ *
+ * @return Returns {@link PackageManager#PERMISSION_GRANTED} if the caller
+ * is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkCallingUriPermission
+ */
+ public abstract int checkCallingOrSelfUriPermission(Uri uri, 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
+ * acess, 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 type of access to grant. May be one or both of
+ * {@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}.
+ *
+ * @return Returns {@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.
+ */
+ public abstract int checkUriPermission(Uri uri, String readPermission,
+ String writePermission, int pid, int uid, 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 type of access to grant. May be one or both of
+ * {@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}.
+ * @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, 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 type of access to grant. May be one or both of
+ * {@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}.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkCallingUriPermission(Uri, int)
+ */
+ public abstract void enforceCallingUriPermission(
+ Uri uri, 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 type of access to grant. May be one or both of
+ * {@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}.
+ * @param message A message to include in the exception if it is thrown.
+ *
+ * @see #checkCallingOrSelfUriPermission(Uri, int)
+ */
+ public abstract void enforceCallingOrSelfUriPermission(
+ Uri uri, 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
+ * acess, 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 type of access to grant. May be one or both of
+ * {@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}.
+ * @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(
+ Uri uri, String readPermission, String writePermission,
+ int pid, int uid, int modeFlags, String message);
+
+ /**
+ * 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;
+
+ /**
+ * 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 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, one of {@link #CONTEXT_INCLUDE_CODE}
+ * or {@link #CONTEXT_IGNORE_SECURITY}.
+ *
+ * @return A Context for the application.
+ *
+ * @throws java.lang.SecurityException
+ * @throws PackageManager.NameNotFoundException if there is no application with
+ * the given package name
+ */
+ public abstract Context createPackageContext(String packageName,
+ int flags) throws PackageManager.NameNotFoundException;
+}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
new file mode 100644
index 0000000..36e1c34
--- /dev/null
+++ b/core/java/android/content/ContextWrapper.java
@@ -0,0 +1,422 @@
+/*
+ * 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.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+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.Looper;
+
+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);
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return mBase.getTheme();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return mBase.getClassLoader();
+ }
+
+ @Override
+ public String getPackageName() {
+ return mBase.getPackageName();
+ }
+
+ @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);
+ }
+
+ @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);
+ }
+
+ @Override
+ public String[] fileList() {
+ return mBase.fileList();
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mBase.getFilesDir();
+ }
+
+ @Override
+ public File getCacheDir() {
+ return mBase.getCacheDir();
+ }
+
+ @Override
+ public File getDir(String name, int mode) {
+ return mBase.getDir(name, mode);
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
+ return mBase.openOrCreateDatabase(name, mode, factory);
+ }
+
+ @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
+ public Drawable getWallpaper() {
+ return mBase.getWallpaper();
+ }
+
+ @Override
+ public Drawable peekWallpaper() {
+ return mBase.peekWallpaper();
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumWidth() {
+ return mBase.getWallpaperDesiredMinimumWidth();
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumHeight() {
+ return mBase.getWallpaperDesiredMinimumHeight();
+ }
+
+ @Override
+ public void setWallpaper(Bitmap bitmap) throws IOException {
+ mBase.setWallpaper(bitmap);
+ }
+
+ @Override
+ public void setWallpaper(InputStream data) throws IOException {
+ mBase.setWallpaper(data);
+ }
+
+ @Override
+ public void clearWallpaper() throws IOException {
+ mBase.clearWallpaper();
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ mBase.startActivity(intent);
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ mBase.sendBroadcast(intent);
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission) {
+ mBase.sendBroadcast(intent, receiverPermission);
+ }
+
+ @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);
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ mBase.sendStickyBroadcast(intent);
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent intent) {
+ mBase.removeStickyBroadcast(intent);
+ }
+
+ @Override
+ public Intent registerReceiver(
+ BroadcastReceiver receiver, IntentFilter filter) {
+ return mBase.registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public Intent registerReceiver(
+ BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return mBase.registerReceiver(receiver, filter, broadcastPermission,
+ scheduler);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBase.unregisterReceiver(receiver);
+ }
+
+ @Override
+ public ComponentName startService(Intent service) {
+ return mBase.startService(service);
+ }
+
+ @Override
+ public boolean stopService(Intent name) {
+ return mBase.stopService(name);
+ }
+
+ @Override
+ public boolean bindService(Intent service, ServiceConnection conn,
+ int flags) {
+ return mBase.bindService(service, conn, flags);
+ }
+
+ @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 int checkPermission(String permission, int pid, int uid) {
+ return mBase.checkPermission(permission, pid, uid);
+ }
+
+ @Override
+ public int checkCallingPermission(String permission) {
+ return mBase.checkCallingPermission(permission);
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return mBase.checkCallingOrSelfPermission(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 int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ return mBase.checkUriPermission(uri, pid, uid, modeFlags);
+ }
+
+ @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);
+ }
+}
diff --git a/core/java/android/content/DefaultDataHandler.java b/core/java/android/content/DefaultDataHandler.java
new file mode 100644
index 0000000..863c9f6
--- /dev/null
+++ b/core/java/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/core/java/android/content/DialogInterface.java b/core/java/android/content/DialogInterface.java
new file mode 100644
index 0000000..4afa294
--- /dev/null
+++ b/core/java/android/content/DialogInterface.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+/**
+ *
+ */
+public interface DialogInterface {
+ /**
+ * The identifier for the positive button.
+ */
+ public static final int BUTTON_POSITIVE = -1;
+
+ /**
+ * The identifier for the negative button.
+ */
+ public static final int BUTTON_NEGATIVE = -2;
+
+ /**
+ * The identifier for the neutral button.
+ */
+ public static final int BUTTON_NEUTRAL = -3;
+
+ /**
+ * @deprecated Use {@link #BUTTON_POSITIVE}
+ */
+ @Deprecated
+ public static final int BUTTON1 = BUTTON_POSITIVE;
+
+ /**
+ * @deprecated Use {@link #BUTTON_NEGATIVE}
+ */
+ @Deprecated
+ public static final int BUTTON2 = BUTTON_NEGATIVE;
+
+ /**
+ * @deprecated Use {@link #BUTTON_NEUTRAL}
+ */
+ @Deprecated
+ public static final int BUTTON3 = BUTTON_NEUTRAL;
+
+ public void cancel();
+
+ public 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.
+ */
+ public 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.
+ */
+ public void onDismiss(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 (e.g.
+ * {@link DialogInterface#BUTTON1}) or the position
+ * of the item clicked.
+ */
+ /* TODO: Change to use BUTTON_POSITIVE after API council */
+ public 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 True if the click checked the item, else false.
+ */
+ public 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 True if the listener has consumed the event, false otherwise.
+ */
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event);
+ }
+}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
new file mode 100644
index 0000000..0606956
--- /dev/null
+++ b/core/java/android/content/IContentProvider.java
@@ -0,0 +1,72 @@
+/*
+ * 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.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileNotFoundException;
+
+/**
+ * The ipc interface to talk to a content provider.
+ * @hide
+ */
+public interface IContentProvider extends IInterface {
+ /**
+ * @hide - hide this because return type IBulkCursor and parameter
+ * IContentObserver are system private classes.
+ */
+ public IBulkCursor bulkQuery(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
+ CursorWindow window) throws RemoteException;
+ public Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) throws RemoteException;
+ public String getType(Uri url) throws RemoteException;
+ public Uri insert(Uri url, ContentValues initialValues)
+ throws RemoteException;
+ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException;
+ public int delete(Uri url, String selection, String[] selectionArgs)
+ throws RemoteException;
+ public int update(Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException;
+ public ParcelFileDescriptor openFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException;
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException;
+ public ISyncAdapter getSyncAdapter() throws RemoteException;
+
+ /* 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 GET_SYNC_ADAPTER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
+ 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;
+}
diff --git a/core/java/android/content/IContentService.java b/core/java/android/content/IContentService.java
new file mode 100644
index 0000000..a3047da
--- /dev/null
+++ b/core/java/android/content/IContentService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.database.IContentObserver;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+public interface IContentService extends IInterface
+{
+ public void registerContentObserver(Uri uri, boolean notifyForDescendentsn,
+ IContentObserver observer) throws RemoteException;
+ public void unregisterContentObserver(IContentObserver observer) throws RemoteException;
+
+ public void notifyChange(Uri uri, IContentObserver observer,
+ boolean observerWantsSelfNotifications, boolean syncToNetwork)
+ throws RemoteException;
+
+ public void startSync(Uri url, Bundle extras) throws RemoteException;
+ public void cancelSync(Uri uri) throws RemoteException;
+
+ static final String SERVICE_NAME = "content";
+
+ /* IPC constants */
+ static final String descriptor = "android.content.IContentService";
+
+ static final int REGISTER_CONTENT_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+ static final int UNREGISTER_CHANGE_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+ static final int NOTIFY_CHANGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+ static final int START_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
+ static final int CANCEL_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
+}
+
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
new file mode 100644
index 0000000..671188c
--- /dev/null
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.Bundle;
+import android.content.ISyncContext;
+
+/**
+ * Interface used to control the sync activity on a SyncAdapter
+ * @hide
+ */
+oneway interface ISyncAdapter {
+ /**
+ * Initiate a sync for this account. SyncAdapter-specific parameters may
+ * be specified in extras, which is guaranteed to not be null.
+ *
+ * @param syncContext the ISyncContext used to indicate the progress of the sync. When
+ * the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
+ * @param account the account that should be synced
+ * @param extras SyncAdapter-specific parameters
+ */
+ void startSync(ISyncContext syncContext, String account, in Bundle extras);
+
+ /**
+ * Cancel the most recently initiated sync. Due to race conditions, this may arrive
+ * after the ISyncContext.onFinished() for that sync was called.
+ */
+ void cancelSync();
+}
diff --git a/core/java/android/content/ISyncContext.aidl b/core/java/android/content/ISyncContext.aidl
new file mode 100644
index 0000000..6d18a1c
--- /dev/null
+++ b/core/java/android/content/ISyncContext.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.content.SyncResult;
+
+/**
+ * Interface used by the SyncAdapter to indicate its progress.
+ * @hide
+ */
+interface ISyncContext {
+ /**
+ * 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.
+ */
+ void sendHeartbeat();
+
+ /**
+ * Signal that the corresponding sync session is completed.
+ * @param result information about this sync session
+ */
+ void onFinished(in SyncResult result);
+}
diff --git a/core/java/android/content/Intent.aidl b/core/java/android/content/Intent.aidl
new file mode 100644
index 0000000..568986b
--- /dev/null
+++ b/core/java/android/content/Intent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 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;
+
+parcelable Intent;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
new file mode 100644
index 0000000..e1c1f64
--- /dev/null
+++ b/core/java/android/content/Intent.java
@@ -0,0 +1,4530 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+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. 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/1</i></b> -- Display
+ * information about the person whose identifier is "1".</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_DIAL} <i>content://contacts/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 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/1</i></b> -- Edit
+ * information about the person whose identifier is "1".</p>
+ * </li>
+ * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/</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/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.app.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:value="android.intent.action.MAIN" />
+ * <category android:value="android.intent.category.LAUNCHER" />
+ * </intent-filter>
+ * <intent-filter>
+ * <action android:value="android.intent.action.VIEW" />
+ * <action android:value="android.intent.action.EDIT" />
+ * <action android:value="android.intent.action.PICK" />
+ * <category android:value="android.intent.category.DEFAULT" />
+ * <type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ * <intent-filter>
+ * <action android:value="android.intent.action.GET_CONTENT" />
+ * <category android:value="android.intent.category.DEFAULT" />
+ * <type android:value="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:value="android.intent.action.VIEW" />
+ * <action android:value="android.intent.action.EDIT" />
+ * <category android:value="android.intent.category.DEFAULT" />
+ * <type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter>
+ *
+ * <intent-filter>
+ * <action android:value="android.intent.action.INSERT" />
+ * <category android:value="android.intent.category.DEFAULT" />
+ * <type android:value="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:value="<i>com.android.notepad.action.EDIT_TITLE</i>" />
+ * <category android:value="android.intent.category.DEFAULT" />
+ * <category android:value="android.intent.category.ALTERNATIVE" />
+ * <category android:value="android.intent.category.SELECTED_ALTERNATIVE" />
+ * <type android:value="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:value="{@link #ACTION_MAIN android.intent.action.MAIN}" />
+ * <category android:value="{@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:value="{@link #ACTION_VIEW android.intent.action.VIEW}" />
+ * <action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" />
+ * <action android:value="{@link #ACTION_PICK android.intent.action.PICK}" />
+ * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <type android:value="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:value="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" />
+ * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" />
+ * </intent-filter></pre>
+ * <p>This filter describes the ability 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.app.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:value="{@link #ACTION_VIEW android.intent.action.VIEW}" />
+ * <action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" />
+ * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <type android:value="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:value="{@link #ACTION_INSERT android.intent.action.INSERT}" />
+ * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <type android:value="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.app.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:value="<i>com.android.notepad.action.EDIT_TITLE</i>" />
+ * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <category android:value="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" />
+ * <category android:value="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" />
+ * <type android:value="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_UID_REMOVED}
+ * <li> {@link #ACTION_BATTERY_CHANGED}
+ * </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_GADGET}
+ * <li> {@link #CATEGORY_TEST}
+ * </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_TEMPLATE}
+ * <li> {@link #EXTRA_INTENT}
+ * <li> {@link #EXTRA_STREAM}
+ * <li> {@link #EXTRA_TEXT}
+ * </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 {
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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";
+
+ /**
+ * 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;
+
+ /**
+ * 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 shortcut. The intent must contain three
+ * extras: SHORTCUT_INTENT (value: Intent), SHORTCUT_NAME (value: String),
+ * and SHORTCUT_ICON (value: Bitmap) or SHORTCUT_ICON_RESOURCE
+ * (value: ShortcutIconResource).</p>
+ *
+ * @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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ public static final String EXTRA_SHORTCUT_ICON_RESOURCE =
+ "android.intent.extra.shortcut.ICON_RESOURCE";
+
+ /**
+ * 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 idenfitier for the icon.
+ * @return A new ShortcutIconResource with the specified's context package name
+ * and icon resource idenfitier.
+ */
+ public static ShortcutIconResource fromContext(Context context, 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>
+ * 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.
+ *
+ * @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) {
+ Intent intent = new Intent(ACTION_CHOOSER);
+ intent.putExtra(EXTRA_INTENT, target);
+ if (title != null) {
+ intent.putExtra(EXTRA_TITLE, title);
+ }
+ 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. A 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 browser over the web and download the desired data,
+ * etc.
+ * <p>
+ * There are two main ways to use this action: if you want an 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 desireable 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>
+ * 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.
+ * <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.
+ */
+ @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
+ */
+ 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
+ */
+ public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
+ /**
+ * 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).
+ * <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: 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: 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: 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: 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
+ */
+ 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";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Standard intent broadcast actions (see action variable).
+
+ /**
+ * Broadcast Action: Sent after the screen turns off.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
+ /**
+ * Broadcast Action: Sent after the screen turns on.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
+ /**
+ * Broadcast Action: The current time has changed. Sent every
+ * minute. You can <em>not</em> receive this through components declared
+ * in manifests, only by exlicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ */
+ @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>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TIMEZONE_CHANGED = "android.intent.action.TIMEZONE_CHANGED";
+ /**
+ * Alarm Changed Action: This is broadcast when the AlarmClock
+ * application's alarm is set or unset. It is used by the
+ * AlarmClock application and the StatusBar service.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ALARM_CHANGED = "android.intent.action.ALARM_CHANGED";
+ /**
+ * Sync State Changed Action: This is broadcast when the sync starts or stops or when one has
+ * been failing for a long time. It is used by the SyncManager and the StatusBar service.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SYNC_STATE_CHANGED
+ = "android.intent.action.SYNC_STATE_CHANGED";
+ /**
+ * Broadcast Action: This is broadcast once, after the system 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.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ 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.
+ */
+ 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.
+ */
+ @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.
+ * <p>My 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>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
+ /**
+ * 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 installed 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>
+ */
+ @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 changed (e.g. 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.
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
+ /**
+ * 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. The data contains the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * </ul>
+ */
+ @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. The data contains
+ * the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
+ /**
+ * 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}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED";
+ /**
+ * Broadcast Action: The current system wallpaper has changed. See
+ * {@link Context#getWallpaper} for retrieving the new wallpaper.
+ */
+ @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.
+ *
+ * @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 charging state, or charge level of the battery has
+ * changed.
+ *
+ * <p class="note">
+ * You can <em>not</em> receive this through components declared
+ * in manifests, only by exlicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ */
+ @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.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
+ /**
+ * Broadcast Action: Indicates low memory condition on the device
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW";
+ /**
+ * Broadcast Action: Indicates low memory condition on the device no longer exists
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK";
+ /**
+ * 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
+ */
+ @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
+ */
+ @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 removed 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 removed 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 removed 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 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 removed 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: An 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: An 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.
+ * {@hide pending API Council approval}
+ */
+ @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>
+ */
+ @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.
+ *
+ * <p>The intent will have the following extra values:
+ * <ul>
+ * <li><em>state</em> - 0 for unplugged, 1 for plugged. </li>
+ * <li><em>name</em> - Headset type, human readable string </li>
+ * </ul>
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_HEADSET_PLUG =
+ "android.intent.action.HEADSET_PLUG";
+
+ /**
+ * Broadcast Action: An outgoing call is about to be placed.
+ *
+ * <p>The Intent will have the following extra value:
+ * <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>You must hold the
+ * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
+ * permission to receive this Intent.</p>
+ */
+ @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.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_REBOOT =
+ "android.intent.action.REBOOT";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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 normal -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";
+ /**
+ * 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 an containing TabActivity.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_TAB = "android.intent.category.TAB";
+ /**
+ * This activity can be embedded inside of another activity that is hosting
+ * gadgets.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_GADGET = "android.intent.category.GADGET";
+ /**
+ * Should be displayed in the top-level launcher.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER";
+ /**
+ * 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 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.
+ *
+ * <p>Note: being removed in favor of more explicit categories such as
+ * CATEGORY_GADGET
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_EMBED = "android.intent.category.EMBED";
+ /**
+ * 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 an 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 a GET_CONTENT intent only wants URIs that can be opened with
+ * ContentResolver.openInputStream. Openable URIs must support the columns in OpenableColumns
+ * when queried, though it is allowable for those columns to be blank.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_OPENABLE = "android.intent.category.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";
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // 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 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}.
+ */
+ public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+
+ /**
+ * 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 {@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";
+
+ /**
+ * Used as an 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";
+
+ /**
+ * 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";
+
+ /**
+ * 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_VOICE_COMMAND}
+ * intents to request which audio route the voice command should prefer.
+ * The value should be a route from {@link android.media.AudioManager}, for
+ * example ROUTE_BLUETOOTH_SCO. Providing this value is optional.
+ * {@hide pending API Council approval}
+ */
+ public static final String EXTRA_AUDIO_ROUTE = "android.intent.extra.AUDIO_ROUTE";
+
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // Intent flags (see mFlags variable).
+
+ /**
+ * If set, the recipient of this Intent will be granted permission to
+ * perform read operations on the Uri in the Intent's data.
+ */
+ 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.
+ */
+ 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, 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.
+ */
+ 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.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on 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;
+ /**
+ * <strong>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>
+ *
+ * <p>This flag is ignored if
+ * {@link #FLAG_ACTIVITY_NEW_TASK} is not set.
+ *
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
+ */
+ 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 task 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) it will be finished and re-created; for all other launch modes
+ * it will receive the Intent in the current instance.
+ *
+ * <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.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on 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;
+ /**
+ * If set, this marks a point in the task's activity stack that should
+ * be cleared when the task is reset. That is, the next time the task
+ * is broad to the foreground with
+ * {@link #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED} (typically as a result of
+ * the user re-launching it from home), this activity and all on top of
+ * it will be finished so that the user does not return to them, but
+ * instead returns to whatever activity preceeded it.
+ *
+ * <p>This is useful for cases where you have a logical break in your
+ * application. For example, an e-mail application may have a command
+ * to view an attachment, which launches an image view activity to
+ * display it. This activity should be part of the e-mail application's
+ * task, since it is a part of the task the user is involved in. However,
+ * if the user leaves that task, and later selects the e-mail app from
+ * home, we may like them to return to the conversation they were
+ * viewing, not the picture attachment, since that is confusing. By
+ * setting this flag when launching the image viewer, that viewer and
+ * any activities it starts will be removed the next time the user returns
+ * to mail.
+ */
+ public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
+ /**
+ * 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, 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 <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 = 0x20000000;
+
+ // ---------------------------------------------------------------------
+
+ private String mAction;
+ private Uri mData;
+ private String mType;
+ private ComponentName mComponent;
+ private int mFlags;
+ private HashSet<String> mCategories;
+ private Bundle mExtras;
+
+ // ---------------------------------------------------------------------
+
+ /**
+ * Create an empty intent.
+ */
+ public Intent() {
+ }
+
+ /**
+ * Copy constructor.
+ */
+ public Intent(Intent o) {
+ this.mAction = o.mAction;
+ this.mData = o.mData;
+ this.mType = o.mType;
+ this.mComponent = o.mComponent;
+ this.mFlags = o.mFlags;
+ if (o.mCategories != null) {
+ this.mCategories = new HashSet<String>(o.mCategories);
+ }
+ if (o.mExtras != null) {
+ this.mExtras = new Bundle(o.mExtras);
+ }
+ }
+
+ @Override
+ public Object clone() {
+ return new Intent(this);
+ }
+
+ private Intent(Intent o, boolean all) {
+ this.mAction = o.mAction;
+ this.mData = o.mData;
+ this.mType = o.mType;
+ this.mComponent = o.mComponent;
+ if (o.mCategories != null) {
+ this.mCategories = new HashSet<String>(o.mCategories);
+ }
+ }
+
+ /**
+ * Make a clone of only the parts of the Intent that are relevant for
+ * filter matching: the action, data, type, component, and categories.
+ */
+ public Intent cloneFilter() {
+ return new Intent(this, false);
+ }
+
+ /**
+ * 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) {
+ mAction = 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.
+ *
+ * @param action The Intent action, such as ACTION_VIEW.
+ * @param uri The Intent data URI.
+ */
+ public Intent(String action, Uri uri) {
+ mAction = 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 using {@link #Intent(String, android.net.Uri)} to
+ * construct the Intent and then calling {@link #setClass} to set its
+ * class.
+ *
+ * @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) {
+ mAction = action;
+ mData = uri;
+ mComponent = new ComponentName(packageContext, cls);
+ }
+
+ /**
+ * Create an intent from a URI. This URI may encode the action,
+ * category, and other intent fields, if it was returned by 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.
+ *
+ * @return Intent The newly created Intent object.
+ *
+ * @see #toURI
+ */
+ public static Intent getIntent(String uri) throws URISyntaxException {
+ int i = 0;
+ try {
+ // simple case
+ i = uri.lastIndexOf("#");
+ if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
+
+ // old format Intent URI
+ if (!uri.startsWith("#Intent;", i)) return getIntentOld(uri);
+
+ // new format
+ Intent intent = new Intent(ACTION_VIEW);
+
+ // fetch data part, if present
+ if (i > 0) {
+ intent.mData = Uri.parse(uri.substring(0, i));
+ }
+ i += "#Intent;".length();
+
+ // loop over contents of Intent, all name=value;
+ while (!uri.startsWith("end", i)) {
+ int eq = uri.indexOf('=', i);
+ int semi = uri.indexOf(';', eq);
+ String value = uri.substring(eq + 1, semi);
+
+ // action
+ if (uri.startsWith("action=", i)) {
+ intent.mAction = value;
+ }
+
+ // 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();
+ }
+
+ // component
+ else if (uri.startsWith("component=", i)) {
+ intent.mComponent = ComponentName.unflattenFromString(value);
+ }
+
+ // extra
+ else {
+ String key = Uri.decode(uri.substring(i + 2, eq));
+ value = Uri.decode(value);
+ // 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;
+ }
+
+ return intent;
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new URISyntaxException(uri, "illegal Intent URI format", i);
+ }
+ }
+
+ public static Intent getIntentOld(String uri) throws URISyntaxException {
+ Intent intent;
+
+ int i = uri.lastIndexOf('#');
+ if (i >= 0) {
+ Uri data = null;
+ String action = null;
+ if (i > 0) {
+ data = Uri.parse(uri.substring(0, i));
+ }
+
+ i++;
+
+ if (uri.regionMatches(i, "action(", 0, 7)) {
+ i += 7;
+ int j = uri.indexOf(')', i);
+ action = uri.substring(i, j);
+ i = j + 1;
+ }
+
+ intent = new Intent(action, data);
+
+ if (uri.regionMatches(i, "categories(", 0, 11)) {
+ i += 11;
+ int j = uri.indexOf(')', i);
+ while (i < j) {
+ int sep = uri.indexOf('!', i);
+ if (sep < 0) sep = j;
+ if (i < sep) {
+ intent.addCategory(uri.substring(i, sep));
+ }
+ i = sep + 1;
+ }
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "type(", 0, 5)) {
+ i += 5;
+ int j = uri.indexOf(')', i);
+ intent.mType = uri.substring(i, j);
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "launchFlags(", 0, 12)) {
+ i += 12;
+ int j = uri.indexOf(')', i);
+ intent.mFlags = Integer.decode(uri.substring(i, j)).intValue();
+ i = j + 1;
+ }
+
+ if (uri.regionMatches(i, "component(", 0, 10)) {
+ 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)) {
+ 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 (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;
+ }
+
+ /**
+ * 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 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 Uri getData() {
+ return mData;
+ }
+
+ /**
+ * The same as {@link #getData()}, but returns the URI as an encoded
+ * String.
+ */
+ public 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 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 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 String resolveType(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 String resolveType(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 String resolveTypeIfNeeded(ContentResolver resolver) {
+ if (mComponent != null) {
+ return mType;
+ }
+ return resolveType(resolver);
+ }
+
+ /**
+ * Check if an 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 Set The set of categories you can examine. Do not modify!
+ *
+ * @see #hasCategory
+ * @see #addCategory
+ */
+ public Set<String> getCategories() {
+ return mCategories;
+ }
+
+ /**
+ * 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(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();
+ }
+
+ /**
+ * 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 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 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 Bundle getExtras() {
+ return (mExtras != null)
+ ? new Bundle(mExtras)
+ : null;
+ }
+
+ /**
+ * 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 int The currently set flags.
+ *
+ * @see #setFlags
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * 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 ComponentName getComponent() {
+ return mComponent;
+ }
+
+ /**
+ * 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 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(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(PackageManager pm, 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);
+ if (info != null) {
+ ai = info.activityInfo;
+ }
+ }
+
+ return ai;
+ }
+
+ /**
+ * 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 Intent setAction(String action) {
+ mAction = action;
+ return this;
+ }
+
+ /**
+ * Set the data this intent is operating on. This method automatically
+ * clears any type that was previously set by {@link #setType}.
+ *
+ * @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 #setDataAndType
+ */
+ public Intent setData(Uri data) {
+ mData = data;
+ mType = null;
+ return this;
+ }
+
+ /**
+ * Set an explicit MIME data type. This is used to create intents that
+ * only specify a type and not data, for example to indicate the type of
+ * data to return. This method automatically clears any data that was
+ * previously set by {@link #setData}.
+ *
+ * @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 #setDataAndType
+ */
+ public Intent setType(String type) {
+ mData = null;
+ mType = type;
+ return this;
+ }
+
+ /**
+ * (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.
+ *
+ * @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 #setData
+ * @see #setType
+ */
+ public Intent setDataAndType(Uri data, String type) {
+ mData = data;
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Add a new category to the intent. Categories provide additional detail
+ * about the action the intent is perform. 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 Intent addCategory(String category) {
+ if (mCategories == null) {
+ mCategories = new HashSet<String>();
+ }
+ mCategories.add(category);
+ return this;
+ }
+
+ /**
+ * Remove an 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;
+ }
+ }
+ }
+
+ /**
+ * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Intent putExtras(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 Intent putExtras(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 Intent replaceExtras(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 Intent replaceExtras(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.html#acttask">Application Fundamentals:
+ * Activities and Tasks</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 #FLAG_GRANT_READ_URI_PERMISSION
+ * @see #FLAG_GRANT_WRITE_URI_PERMISSION
+ * @see #FLAG_DEBUG_LOG_RESOLUTION
+ * @see #FLAG_FROM_BACKGROUND
+ * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
+ * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+ * @see #FLAG_ACTIVITY_CLEAR_TOP
+ * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ * @see #FLAG_ACTIVITY_FORWARD_RESULT
+ * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+ * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+ * @see #FLAG_ACTIVITY_NEW_TASK
+ * @see #FLAG_ACTIVITY_NO_HISTORY
+ * @see #FLAG_ACTIVITY_NO_USER_ACTION
+ * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP
+ * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ * @see #FLAG_ACTIVITY_SINGLE_TOP
+ * @see #FLAG_RECEIVER_REGISTERED_ONLY
+ */
+ public Intent setFlags(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
+ */
+ public Intent addFlags(int flags) {
+ mFlags |= flags;
+ 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 Intent setComponent(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 Intent setClassName(Context packageContext, 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 Intent setClassName(String packageName, 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 Intent setClass(Context packageContext, Class<?> cls) {
+ mComponent = new ComponentName(packageContext, cls);
+ return this;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * 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> component, as set by {@link #setComponent(ComponentName)} or
+ * related methods.
+ * <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}, and
+ * {@link #FILL_IN_COMPONENT} to override the restriction where the
+ * corresponding field will not be replaced if it is already set.
+ *
+ * <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}, and
+ * {@link #FILL_IN_COMPONENT} indicating which fields were changed.
+ */
+ public int fillIn(Intent other, int flags) {
+ int changes = 0;
+ if ((mAction == null && other.mAction == null)
+ || (flags&FILL_IN_ACTION) != 0) {
+ mAction = other.mAction;
+ changes |= FILL_IN_ACTION;
+ }
+ if ((mData == null && mType == null &&
+ (other.mData != null || other.mType != null))
+ || (flags&FILL_IN_DATA) != 0) {
+ mData = other.mData;
+ mType = other.mType;
+ changes |= FILL_IN_DATA;
+ }
+ if ((mCategories == null && other.mCategories == null)
+ || (flags&FILL_IN_CATEGORIES) != 0) {
+ if (other.mCategories != null) {
+ mCategories = new HashSet<String>(other.mCategories);
+ }
+ changes |= FILL_IN_CATEGORIES;
+ }
+ if ((mComponent == null && other.mComponent == null)
+ || (flags&FILL_IN_COMPONENT) != 0) {
+ mComponent = other.mComponent;
+ changes |= FILL_IN_COMPONENT;
+ }
+ mFlags |= other.mFlags;
+ if (mExtras == null) {
+ if (other.mExtras != null) {
+ mExtras = new Bundle(other.mExtras);
+ }
+ } else if (other.mExtras != null) {
+ try {
+ Bundle newb = new Bundle(other.mExtras);
+ newb.putAll(mExtras);
+ mExtras = newb;
+ } 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);
+ }
+ }
+ 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 (mAction != other.mAction) {
+ if (mAction != null) {
+ if (!mAction.equals(other.mAction)) {
+ return false;
+ }
+ } else {
+ if (!other.mAction.equals(mAction)) {
+ return false;
+ }
+ }
+ }
+ if (mData != other.mData) {
+ if (mData != null) {
+ if (!mData.equals(other.mData)) {
+ return false;
+ }
+ } else {
+ if (!other.mData.equals(mData)) {
+ return false;
+ }
+ }
+ }
+ if (mType != other.mType) {
+ if (mType != null) {
+ if (!mType.equals(other.mType)) {
+ return false;
+ }
+ } else {
+ if (!other.mType.equals(mType)) {
+ return false;
+ }
+ }
+ }
+ if (mComponent != other.mComponent) {
+ if (mComponent != null) {
+ if (!mComponent.equals(other.mComponent)) {
+ return false;
+ }
+ } else {
+ if (!other.mComponent.equals(mComponent)) {
+ return false;
+ }
+ }
+ }
+ if (mCategories != other.mCategories) {
+ if (mCategories != null) {
+ if (!mCategories.equals(other.mCategories)) {
+ return false;
+ }
+ } else {
+ if (!other.mCategories.equals(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 (mComponent != null) {
+ code += mComponent.hashCode();
+ }
+ if (mCategories != null) {
+ code += mCategories.hashCode();
+ }
+ return code;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+
+ b.append("Intent {");
+ if (mAction != null) b.append(" action=").append(mAction);
+ if (mCategories != null) {
+ b.append(" categories={");
+ Iterator<String> i = mCategories.iterator();
+ boolean didone = false;
+ while (i.hasNext()) {
+ if (didone) b.append(",");
+ didone = true;
+ b.append(i.next());
+ }
+ b.append("}");
+ }
+ if (mData != null) b.append(" data=").append(mData);
+ if (mType != null) b.append(" type=").append(mType);
+ if (mFlags != 0) b.append(" flags=0x").append(Integer.toHexString(mFlags));
+ if (mComponent != null) b.append(" comp=").append(mComponent.toShortString());
+ if (mExtras != null) b.append(" (has extras)");
+ b.append(" }");
+
+ return b.toString();
+ }
+
+ public String toURI() {
+ StringBuilder uri = new StringBuilder(mData != null ? mData.toString() : "");
+
+ uri.append("#Intent;");
+
+ if (mAction != null) {
+ uri.append("action=").append(mAction).append(';');
+ }
+ if (mCategories != null) {
+ for (String category : mCategories) {
+ uri.append("category=").append(category).append(';');
+ }
+ }
+ if (mType != null) {
+ uri.append("type=").append(mType).append(';');
+ }
+ if (mFlags != 0) {
+ uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
+ }
+ if (mComponent != null) {
+ uri.append("component=").append(mComponent.flattenToShortString()).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(';');
+ }
+ }
+ }
+
+ uri.append("end");
+
+ return uri.toString();
+ }
+
+ 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);
+ ComponentName.writeToParcel(mComponent, out);
+
+ if (mCategories != null) {
+ out.writeInt(mCategories.size());
+ for (String category : mCategories) {
+ out.writeString(category);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ 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];
+ }
+ };
+
+ private Intent(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ mAction = in.readString();
+ mData = Uri.CREATOR.createFromParcel(in);
+ mType = in.readString();
+ mFlags = in.readInt();
+ mComponent = ComponentName.readFromParcel(in);
+
+ int N = in.readInt();
+ if (N > 0) {
+ mCategories = new HashSet<String>();
+ int i;
+ for (i=0; i<N; i++) {
+ mCategories.add(in.readString());
+ }
+ } else {
+ mCategories = null;
+ }
+
+ 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 Intent parseIntent(Resources resources, 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("category")) {
+ 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("extra")) {
+ if (intent.mExtras == null) {
+ intent.mExtras = new Bundle();
+ }
+ resources.parseBundleExtra("extra", attrs, intent.mExtras);
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ return intent;
+ }
+}
diff --git a/core/java/android/content/IntentFilter.aidl b/core/java/android/content/IntentFilter.aidl
new file mode 100644
index 0000000..a9bcd5e
--- /dev/null
+++ b/core/java/android/content/IntentFilter.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable IntentFilter;
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
new file mode 100644
index 0000000..e81bc86
--- /dev/null
+++ b/core/java/android/content/IntentFilter.java
@@ -0,0 +1,1408 @@
+/*
+ * 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 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.Iterator;
+import java.util.Set;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.util.AndroidException;
+import android.util.Config;
+import android.util.Log;
+import android.util.Printer;
+import com.android.internal.util.XmlUtils;
+
+/**
+ * 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 #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.
+ *
+ * <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.
+ *
+ * <p><strong>Action</strong> matches if any of the given values match the
+ * Intent action, <em>or</em> if no actions were specified in the filter.
+ *
+ * <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.
+ *
+ * <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.
+ *
+ * <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 scheme's 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.
+ *
+ * <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 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 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";
+
+ /**
+ * 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_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 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;
+
+ private int mPriority;
+ private final ArrayList<String> mActions;
+ private ArrayList<String> mCategories = null;
+ private ArrayList<String> mDataSchemes = null;
+ private ArrayList<AuthorityEntry> mDataAuthorities = null;
+ private ArrayList<PatternMatcher> mDataPaths = null;
+ private ArrayList<String> mDataTypes = null;
+ private boolean mHasPartialTypes = false;
+
+ // 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, i.e. Intent.ACTION_VIEW.
+ * @param dataType The type to match, i.e. "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, i.e. 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>Throws {@link MalformedMimeTypeException} if the given MIME type is
+ * not syntactically correct.
+ *
+ * @param action The action to match, i.e. Intent.ACTION_VIEW.
+ * @param dataType The type to match, i.e. "vnd.android.cursor.dir/person".
+ *
+ */
+ public IntentFilter(String action, String dataType)
+ throws MalformedMimeTypeException {
+ mPriority = 0;
+ mActions = new ArrayList<String>();
+ 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;
+ 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.mDataAuthorities != null) {
+ mDataAuthorities = new ArrayList<AuthorityEntry>(o.mDataAuthorities);
+ }
+ if (o.mDataPaths != null) {
+ mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
+ }
+ mHasPartialTypes = o.mHasPartialTypes;
+ }
+
+ /**
+ * Modify priority of this filter. The default priority is 0. Positive
+ * values will be before the default, lower values will be after it.
+ * Applications must 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;
+ }
+
+ /**
+ * 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, i.e. 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 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 or the filter does
+ * not specify any actions.
+ */
+ public final boolean matchAction(String action) {
+ if (action == null || mActions == null || mActions.size() == 0) {
+ return false;
+ }
+ return mActions.contains(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>Throws {@link MalformedMimeTypeException} if the given MIME type is
+ * not syntactically correct.
+ *
+ * @param type Name of the data type to match, i.e. "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);
+ }
+
+ /**
+ * 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.
+ *
+ * @param scheme Name of the scheme to match, i.e. "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;
+ }
+
+ public int match(Uri data) {
+ String host = data.getHost();
+ if (host == null) {
+ return NO_MATCH_DATA;
+ }
+ if (Config.LOGV) 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 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.
+ *
+ * @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 (mDataAuthorities == null) mDataAuthorities =
+ new ArrayList<AuthorityEntry>();
+ if (port != null) port = port.intern();
+ mDataAuthorities.add(new AuthorityEntry(host.intern(), port));
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 oath 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) {
+ if (mDataPaths == null) mDataPaths = new ArrayList<PatternMatcher>();
+ mDataPaths.add(new PatternMatcher(path.intern(), type));
+ }
+
+ /**
+ * 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;
+ }
+ Iterator<PatternMatcher> i = mDataPaths.iterator();
+ while (i.hasNext()) {
+ final PatternMatcher pe = i.next();
+ if (pe.match(data)) {
+ 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) {
+ return NO_MATCH_DATA;
+ }
+ Iterator<AuthorityEntry> i = mDataAuthorities.iterator();
+ while (i.hasNext()) {
+ final AuthorityEntry ae = i.next();
+ 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.
+ *
+ * <p>Note 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).
+ *
+ * @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;
+ final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
+ final ArrayList<PatternMatcher> paths = mDataPaths;
+
+ 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;
+ }
+
+ if (authorities != null) {
+ int authMatch = matchDataAuthority(data);
+ if (authMatch >= 0) {
+ if (paths == null) {
+ match = authMatch;
+ } else if (hasDataPath(data.getPath())) {
+ match = MATCH_CATEGORY_PATH;
+ } else {
+ return NO_MATCH_DATA;
+ }
+ } else {
+ 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, i.e. 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.
+ */
+ 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.
+ *
+ * @return How well the filter matches. Negative if it doesn't match,
+ * zero or positive positive value if it does with a higher
+ * value representing a better 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 (Config.LOGV) Log.v(
+ logTag, "No matching action " + action + " for " + this);
+ return NO_MATCH_ACTION;
+ }
+
+ int dataMatch = matchData(type, scheme, data);
+ if (dataMatch < 0) {
+ if (Config.LOGV) {
+ 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 categoryMatch = matchCategories(categories);
+ if (categoryMatch != null) {
+ if (Config.LOGV) Log.v(
+ logTag, "No matching category "
+ + categoryMatch + " 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 {
+ 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 = 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;
+ }
+ serializer.endTag(null, PATH_STR);
+ }
+ }
+
+ public void readFromXml(XmlPullParser parser) 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 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(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 {
+ Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ public void dump(Printer du, String prefix) {
+ if (mActions.size() > 0) {
+ Iterator<String> it = mActions.iterator();
+ while (it.hasNext()) {
+ du.println(prefix + "Action: \"" + it.next() + "\"");
+ }
+ }
+ if (mCategories != null) {
+ Iterator<String> it = mCategories.iterator();
+ while (it.hasNext()) {
+ du.println(prefix + "Category: \"" + it.next() + "\"");
+ }
+ }
+ if (mDataSchemes != null) {
+ Iterator<String> it = mDataSchemes.iterator();
+ while (it.hasNext()) {
+ du.println(prefix + "Data Scheme: \"" + it.next() + "\"");
+ }
+ }
+ if (mDataAuthorities != null) {
+ Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+ while (it.hasNext()) {
+ AuthorityEntry ae = it.next();
+ du.println(prefix + "Data Authority: \"" + ae.mHost + "\":"
+ + ae.mPort + (ae.mWild ? " WILD" : ""));
+ }
+ }
+ if (mDataPaths != null) {
+ Iterator<PatternMatcher> it = mDataPaths.iterator();
+ while (it.hasNext()) {
+ PatternMatcher pe = it.next();
+ du.println(prefix + "Data Path: \"" + pe + "\"");
+ }
+ }
+ if (mDataTypes != null) {
+ Iterator<String> it = mDataTypes.iterator();
+ while (it.hasNext()) {
+ du.println(prefix + "Data Type: \"" + it.next() + "\"");
+ }
+ }
+ du.println(prefix + "mPriority=" + mPriority
+ + ", mHasPartialTypes=" + mHasPartialTypes);
+ }
+
+ 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 (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, 0);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mPriority);
+ dest.writeInt(mHasPartialTypes ? 1 : 0);
+ }
+
+ /**
+ * 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;
+ */
+ }
+
+ private 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) {
+ mDataAuthorities = new ArrayList<AuthorityEntry>();
+ for (int i=0; i<N; i++) {
+ mDataAuthorities.add(new AuthorityEntry(source));
+ }
+ }
+ N = source.readInt();
+ if (N > 0) {
+ mDataPaths = new ArrayList<PatternMatcher>();
+ for (int i=0; i<N; i++) {
+ mDataPaths.add(new PatternMatcher(source));
+ }
+ }
+ mPriority = source.readInt();
+ mHasPartialTypes = source.readInt() > 0;
+ }
+
+ 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 Iterator<String> it = t.iterator();
+ while (it.hasNext()) {
+ String v = it.next();
+ if (type.regionMatches(0, v, 0, slashpos+1)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/core/java/android/content/MutableContextWrapper.java b/core/java/android/content/MutableContextWrapper.java
new file mode 100644
index 0000000..820479c
--- /dev/null
+++ b/core/java/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/core/java/android/content/ReceiverCallNotAllowedException.java b/core/java/android/content/ReceiverCallNotAllowedException.java
new file mode 100644
index 0000000..96b269c
--- /dev/null
+++ b/core/java/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/core/java/android/content/SearchRecentSuggestionsProvider.java b/core/java/android/content/SearchRecentSuggestionsProvider.java
new file mode 100644
index 0000000..3d89e92
--- /dev/null
+++ b/core/java/android/content/SearchRecentSuggestionsProvider.java
@@ -0,0 +1,385 @@
+/*
+ * 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>
+ *
+ * @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,
+ "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,
+ "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/core/java/android/content/ServiceConnection.java b/core/java/android/content/ServiceConnection.java
new file mode 100644
index 0000000..d115ce4
--- /dev/null
+++ b/core/java/android/content/ServiceConnection.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+ public 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.
+ */
+ public void onServiceDisconnected(ComponentName name);
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
new file mode 100644
index 0000000..a15e29e
--- /dev/null
+++ b/core/java/android/content/SharedPreferences.java
@@ -0,0 +1,282 @@
+/*
+ * 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 java.util.Map;
+
+/**
+ * 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.
+ *
+ * <p><em>Note: currently this class does not support use across multiple
+ * processes. This will be added later.</em>
+ *
+ * @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.
+ *
+ * @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} or persistent storage
+ * until you call {@link #commit}.
+ */
+ public interface Editor {
+ /**
+ * Set a String value in the preferences editor, to be written back once
+ * {@link #commit} is 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 putString(String key, String value);
+
+ /**
+ * Set an int value in the preferences editor, to be written back once
+ * {@link #commit} is 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} is 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} is 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} is 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.
+ *
+ * @return Returns true if the new values were successfully written
+ * to persistent storage.
+ */
+ boolean commit();
+ }
+
+ /**
+ * Retrieve all values from the preferences.
+ *
+ * @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
+ */
+ String getString(String key, String defValue);
+
+ /**
+ * 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.
+ *
+ * @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/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
new file mode 100644
index 0000000..7826e50
--- /dev/null
+++ b/core/java/android/content/SyncAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * 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.Bundle;
+import android.os.RemoteException;
+
+/**
+ * @hide
+ */
+public abstract class SyncAdapter {
+ private static final String TAG = "SyncAdapter";
+
+ /** Kernel event log tag. Also listed in data/etc/event-log-tags. */
+ public static final int LOG_SYNC_DETAILS = 2743;
+
+ class Transport extends ISyncAdapter.Stub {
+ public void startSync(ISyncContext syncContext, String account,
+ Bundle extras) throws RemoteException {
+ SyncAdapter.this.startSync(new SyncContext(syncContext), account, extras);
+ }
+
+ public void cancelSync() throws RemoteException {
+ SyncAdapter.this.cancelSync();
+ }
+ }
+
+ Transport mTransport = new Transport();
+
+ /**
+ * Get the Transport object. (note this is package private).
+ */
+ final ISyncAdapter getISyncAdapter()
+ {
+ return mTransport;
+ }
+
+ /**
+ * Initiate a sync for this account. SyncAdapter-specific parameters may
+ * be specified in extras, which is guaranteed to not be null. IPC invocations
+ * of this method and cancelSync() are guaranteed to be serialized.
+ *
+ * @param syncContext the ISyncContext used to indicate the progress of the sync. When
+ * the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
+ * @param account the account that should be synced
+ * @param extras SyncAdapter-specific parameters
+ */
+ public abstract void startSync(SyncContext syncContext, String account, Bundle extras);
+
+ /**
+ * Cancel the most recently initiated sync. Due to race conditions, this may arrive
+ * after the ISyncContext.onFinished() for that sync was called. IPC invocations
+ * of this method and startSync() are guaranteed to be serialized.
+ */
+ public abstract void cancelSync();
+}
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
new file mode 100644
index 0000000..f4faa04
--- /dev/null
+++ b/core/java/android/content/SyncContext.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * @hide
+ */
+public class SyncContext {
+ private ISyncContext mSyncContext;
+ private long mLastHeartbeatSendTime;
+
+ private static final long HEARTBEAT_SEND_INTERVAL_IN_MS = 1000;
+
+ 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
+ */
+ 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.
+ */
+ public void updateHeartbeat() {
+ final long now = SystemClock.elapsedRealtime();
+ if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
+ try {
+ mLastHeartbeatSendTime = now;
+ mSyncContext.sendHeartbeat();
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onFinished(SyncResult result) {
+ try {
+ mSyncContext.onFinished(result);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public ISyncContext getISyncContext() {
+ return mSyncContext;
+ }
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
new file mode 100644
index 0000000..96470c3
--- /dev/null
+++ b/core/java/android/content/SyncManager.java
@@ -0,0 +1,2175 @@
+/*
+ * 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 com.google.android.collect.Maps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+import android.accounts.AccountMonitor;
+import android.accounts.AccountMonitorListener;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.provider.Sync;
+import android.provider.Settings;
+import android.provider.Sync.History;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Random;
+import java.util.Observer;
+import java.util.Observable;
+
+/**
+ * @hide
+ */
+class SyncManager {
+ private static final String TAG = "SyncManager";
+
+ // used during dumping of the Sync history
+ private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;
+ private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
+ private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
+ private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4;
+
+ /** Delay a sync due to local changes this long. In milliseconds */
+ private static final long LOCAL_SYNC_DELAY = 30 * 1000; // 30 seconds
+
+ /**
+ * If a sync takes longer than this and the sync queue is not empty then we will
+ * cancel it and add it back to the end of the sync queue. In milliseconds.
+ */
+ private static final long MAX_TIME_PER_SYNC = 5 * 60 * 1000; // 5 minutes
+
+ private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
+
+ /**
+ * When retrying a sync for the first time use this delay. After that
+ * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
+ * In milliseconds.
+ */
+ private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
+
+ /**
+ * Default the max sync retry time to this value.
+ */
+ private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
+
+ /**
+ * An error notification is sent if sync of any of the providers has been failing for this long.
+ */
+ private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
+
+ private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
+ private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ private String mStatusText = "";
+ private long mHeartbeatTime = 0;
+
+ private AccountMonitor mAccountMonitor;
+
+ private volatile String[] mAccounts = null;
+
+ volatile private PowerManager.WakeLock mSyncWakeLock;
+ volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+ volatile private boolean mDataConnectionIsConnected = false;
+ volatile private boolean mStorageIsLow = false;
+ private Sync.Settings.QueryMap mSyncSettings;
+
+ private final NotificationManager mNotificationMgr;
+ private AlarmManager mAlarmService = null;
+ private HandlerThread mSyncThread;
+
+ private volatile IPackageManager mPackageManager;
+
+ private final SyncStorageEngine mSyncStorageEngine;
+ private final SyncQueue mSyncQueue;
+
+ private ActiveSyncContext mActiveSyncContext = null;
+
+ // set if the sync error indicator should be reported.
+ private boolean mNeedSyncErrorNotification = false;
+ // set if the sync active indicator should be reported
+ private boolean mNeedSyncActiveNotification = false;
+
+ private volatile boolean mSyncPollInitialized;
+ private final PendingIntent mSyncAlarmIntent;
+ private final PendingIntent mSyncPollAlarmIntent;
+
+ private BroadcastReceiver mStorageIntentReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ ensureContentResolver();
+ String action = intent.getAction();
+ if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Internal storage is low.");
+ }
+ mStorageIsLow = true;
+ cancelActiveSync(null /* no url */);
+ } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Internal storage is ok.");
+ }
+ mStorageIsLow = false;
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
+ private BroadcastReceiver mConnectivityIntentReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ NetworkInfo networkInfo =
+ intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+ NetworkInfo.State state = (networkInfo == null ? NetworkInfo.State.UNKNOWN :
+ networkInfo.getState());
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "received connectivity action. network info: " + networkInfo);
+ }
+
+ // only pay attention to the CONNECTED and DISCONNECTED states.
+ // if connected, we are connected.
+ // if disconnected, we may not be connected. in some cases, we may be connected on
+ // a different network.
+ // e.g., if switching from GPRS to WiFi, we may receive the CONNECTED to WiFi and
+ // DISCONNECTED for GPRS in any order. if we receive the CONNECTED first, and then
+ // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true
+ // since we still have a WiFi connection.
+ switch (state) {
+ case CONNECTED:
+ mDataConnectionIsConnected = true;
+ break;
+ case DISCONNECTED:
+ if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
+ mDataConnectionIsConnected = false;
+ } else {
+ mDataConnectionIsConnected = true;
+ }
+ break;
+ default:
+ // ignore the rest of the states -- leave our boolean alone.
+ }
+ if (mDataConnectionIsConnected) {
+ initializeSyncPoll();
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
+ private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
+ private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
+ private final SyncHandler mSyncHandler;
+
+ private static final String[] SYNC_ACTIVE_PROJECTION = new String[]{
+ Sync.Active.ACCOUNT,
+ Sync.Active.AUTHORITY,
+ Sync.Active.START_TIME,
+ };
+
+ private static final String[] SYNC_PENDING_PROJECTION = new String[]{
+ Sync.Pending.ACCOUNT,
+ Sync.Pending.AUTHORITY
+ };
+
+ private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours
+ private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
+
+ private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
+
+ public SyncManager(Context context, boolean factoryTest) {
+ // Initialize the SyncStorageEngine first, before registering observers
+ // and creating threads and so on; it may fail if the disk is full.
+ SyncStorageEngine.init(context);
+ mSyncStorageEngine = SyncStorageEngine.getSingleton();
+ mSyncQueue = new SyncQueue(mSyncStorageEngine);
+
+ mContext = context;
+
+ mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
+ mSyncThread.start();
+ mSyncHandler = new SyncHandler(mSyncThread.getLooper());
+
+ mPackageManager = null;
+
+ mSyncAlarmIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
+
+ mSyncPollAlarmIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);
+
+ IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+ context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
+
+ intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ context.registerReceiver(mStorageIntentReceiver, intentFilter);
+
+ if (!factoryTest) {
+ mNotificationMgr = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ context.registerReceiver(new SyncAlarmIntentReceiver(),
+ new IntentFilter(ACTION_SYNC_ALARM));
+ } else {
+ mNotificationMgr = null;
+ }
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
+ mSyncWakeLock.setReferenceCounted(false);
+
+ // This WakeLock is used to ensure that we stay awake between the time that we receive
+ // a sync alarm notification and when we finish processing it. We need to do this
+ // because we don't do the work in the alarm handler, rather we do it in a message
+ // handler.
+ mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ HANDLE_SYNC_ALARM_WAKE_LOCK);
+ mHandleAlarmWakeLock.setReferenceCounted(false);
+
+ if (!factoryTest) {
+ AccountMonitorListener listener = new AccountMonitorListener() {
+ public void onAccountsUpdated(String[] accounts) {
+ final boolean hadAccountsAlready = mAccounts != null;
+ // copy the accounts into a new array and change mAccounts to point to it
+ String[] newAccounts = new String[accounts.length];
+ System.arraycopy(accounts, 0, newAccounts, 0, accounts.length);
+ mAccounts = newAccounts;
+
+ // if a sync is in progress yet it is no longer in the accounts list, cancel it
+ ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ if (activeSyncContext != null) {
+ if (!ArrayUtils.contains(newAccounts,
+ activeSyncContext.mSyncOperation.account)) {
+ Log.d(TAG, "canceling sync since the account has been removed");
+ sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ null /* no result since this is a cancel */);
+ }
+ }
+
+ // we must do this since we don't bother scheduling alarms when
+ // the accounts are not set yet
+ sendCheckAlarmsMessage();
+
+ mSyncStorageEngine.doDatabaseCleanup(accounts);
+
+ if (hadAccountsAlready && mAccounts.length > 0) {
+ // request a sync so that if the password was changed we will retry any sync
+ // that failed when it was wrong
+ startSync(null /* all providers */, null /* no extras */);
+ }
+ }
+ };
+ mAccountMonitor = new AccountMonitor(context, listener);
+ }
+ }
+
+ private synchronized void initializeSyncPoll() {
+ if (mSyncPollInitialized) return;
+ mSyncPollInitialized = true;
+
+ mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));
+
+ // load the next poll time from shared preferences
+ long absoluteAlarmTime = readSyncPollTime();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
+ }
+
+ // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
+ // schedule the poll immediately, if it is too far in the future then cap it at
+ // MAX_SYNC_POLL_DELAY_SECONDS.
+ long absoluteNow = System.currentTimeMillis();
+ long relativeNow = SystemClock.elapsedRealtime();
+ long relativeAlarmTime = relativeNow;
+ if (absoluteAlarmTime > absoluteNow) {
+ long delayInMs = absoluteAlarmTime - absoluteNow;
+ final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
+ if (delayInMs > maxDelayInMs) {
+ delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
+ }
+ relativeAlarmTime += delayInMs;
+ }
+
+ // schedule an alarm for the next poll time
+ scheduleSyncPollAlarm(relativeAlarmTime);
+ }
+
+ private void scheduleSyncPollAlarm(long relativeAlarmTime) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
+ + ", now is " + SystemClock.elapsedRealtime()
+ + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
+ }
+ ensureAlarmService();
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
+ mSyncPollAlarmIntent);
+ }
+
+ /**
+ * Return a random value v that satisfies minValue <= v < maxValue. The difference between
+ * maxValue and minValue must be less than Integer.MAX_VALUE.
+ */
+ private long jitterize(long minValue, long maxValue) {
+ Random random = new Random(SystemClock.elapsedRealtime());
+ long spread = maxValue - minValue;
+ if (spread > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("the difference between the maxValue and the "
+ + "minValue must be less than " + Integer.MAX_VALUE);
+ }
+ return minValue + random.nextInt((int)spread);
+ }
+
+ private void handleSyncPollAlarm() {
+ // determine the next poll time
+ long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
+ long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);
+
+ // write the absolute time to shared preferences
+ writeSyncPollTime(System.currentTimeMillis() + delayMs);
+
+ // schedule an alarm for the next poll time
+ scheduleSyncPollAlarm(nextRelativePollTimeMs);
+
+ // perform a poll
+ scheduleSync(null /* sync all syncable providers */, new Bundle(), 0 /* no delay */);
+ }
+
+ private void writeSyncPollTime(long when) {
+ File f = new File(SYNCMANAGER_PREFS_FILENAME);
+ DataOutputStream str = null;
+ try {
+ str = new DataOutputStream(new FileOutputStream(f));
+ str.writeLong(when);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "error writing to file " + f, e);
+ } catch (IOException e) {
+ Log.w(TAG, "error writing to file " + f, e);
+ } finally {
+ if (str != null) {
+ try {
+ str.close();
+ } catch (IOException e) {
+ Log.w(TAG, "error closing file " + f, e);
+ }
+ }
+ }
+ }
+
+ private long readSyncPollTime() {
+ File f = new File(SYNCMANAGER_PREFS_FILENAME);
+
+ DataInputStream str = null;
+ try {
+ str = new DataInputStream(new FileInputStream(f));
+ return str.readLong();
+ } catch (FileNotFoundException e) {
+ writeSyncPollTime(0);
+ } catch (IOException e) {
+ Log.w(TAG, "error reading file " + f, e);
+ } finally {
+ if (str != null) {
+ try {
+ str.close();
+ } catch (IOException e) {
+ Log.w(TAG, "error closing file " + f, e);
+ }
+ }
+ }
+ return 0;
+ }
+
+ public ActiveSyncContext getActiveSyncContext() {
+ return mActiveSyncContext;
+ }
+
+ private Sync.Settings.QueryMap getSyncSettings() {
+ if (mSyncSettings == null) {
+ mSyncSettings = new Sync.Settings.QueryMap(mContext.getContentResolver(), true,
+ new Handler());
+ mSyncSettings.addObserver(new Observer(){
+ public void update(Observable o, Object arg) {
+ // force the sync loop to run if the settings change
+ sendCheckAlarmsMessage();
+ }
+ });
+ }
+ return mSyncSettings;
+ }
+
+ private void ensureContentResolver() {
+ if (mContentResolver == null) {
+ mContentResolver = mContext.getContentResolver();
+ }
+ }
+
+ private void ensureAlarmService() {
+ if (mAlarmService == null) {
+ mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ }
+ }
+
+ public String getSyncingAccount() {
+ ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
+ }
+
+ /**
+ * Returns whether or not sync is enabled. Sync can be enabled by
+ * setting the system property "ro.config.sync" to the value "yes".
+ * This is normally done at boot time on builds that support sync.
+ * @return true if sync is enabled
+ */
+ private boolean isSyncEnabled() {
+ // Require the precise value "yes" to discourage accidental activation.
+ return "yes".equals(SystemProperties.get("ro.config.sync"));
+ }
+
+ /**
+ * Initiate a sync. This can start a sync for all providers
+ * (pass null to url, set onlyTicklable to false), only those
+ * providers that are marked as ticklable (pass null to url,
+ * set onlyTicklable to true), or a specific provider (set url
+ * to the content url of the provider).
+ *
+ * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
+ * true then initiate a sync that just checks for local changes to send
+ * to the server, otherwise initiate a sync that first gets any
+ * changes from the server before sending local changes back to
+ * the server.
+ *
+ * <p>If a specific provider is being synced (the url is non-null)
+ * then the extras can contain SyncAdapter-specific information
+ * to control what gets synced (e.g. which specific feed to sync).
+ *
+ * <p>You'll start getting callbacks after this.
+ *
+ * @param url The Uri of a specific provider to be synced, or
+ * null to sync all providers.
+ * @param extras a Map of SyncAdapter-specific information to control
+* syncs of a specific provider. Can be null. Is ignored
+* if the url is null.
+ * @param delay how many milliseconds in the future to wait before performing this
+ * sync. -1 means to make this the next sync to perform.
+ */
+ public void scheduleSync(Uri url, Bundle extras, long delay) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync:"
+ + " delay " + delay
+ + ", url " + ((url == null) ? "(null)" : url)
+ + ", extras " + ((extras == null) ? "(null)" : extras));
+ }
+
+ if (!isSyncEnabled()) {
+ if (isLoggable) {
+ Log.v(TAG, "not syncing because sync is disabled");
+ }
+ setStatusText("Sync is disabled.");
+ return;
+ }
+
+ if (mAccounts == null) setStatusText("The accounts aren't known yet.");
+ if (!mDataConnectionIsConnected) setStatusText("No data connection");
+ if (mStorageIsLow) setStatusText("Memory low");
+
+ if (extras == null) extras = new Bundle();
+
+ Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+ if (expedited) {
+ delay = -1; // this means schedule at the front of the queue
+ }
+
+ String[] accounts;
+ String accountFromExtras = extras.getString(ContentResolver.SYNC_EXTRAS_ACCOUNT);
+ if (!TextUtils.isEmpty(accountFromExtras)) {
+ accounts = new String[]{accountFromExtras};
+ } else {
+ // if the accounts aren't configured yet then we can't support an account-less
+ // sync request
+ accounts = mAccounts;
+ if (accounts == null) {
+ // not ready yet
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync: no accounts yet, dropping");
+ }
+ return;
+ }
+ if (accounts.length == 0) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync: no accounts configured, dropping");
+ }
+ setStatusText("No accounts are configured.");
+ return;
+ }
+ }
+
+ final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+ final boolean force = extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
+
+ int source;
+ if (uploadOnly) {
+ source = Sync.History.SOURCE_LOCAL;
+ } else if (force) {
+ source = Sync.History.SOURCE_USER;
+ } else if (url == null) {
+ source = Sync.History.SOURCE_POLL;
+ } else {
+ // this isn't strictly server, since arbitrary callers can (and do) request
+ // a non-forced two-way sync on a specific url
+ source = Sync.History.SOURCE_SERVER;
+ }
+
+ List<String> names = new ArrayList<String>();
+ List<ProviderInfo> providers = new ArrayList<ProviderInfo>();
+ populateProvidersList(url, names, providers);
+
+ final int numProviders = providers.size();
+ for (int i = 0; i < numProviders; i++) {
+ if (!providers.get(i).isSyncable) continue;
+ final String name = names.get(i);
+ for (String account : accounts) {
+ scheduleSyncOperation(new SyncOperation(account, source, name, extras, delay));
+ // TODO: remove this when Calendar supports multiple accounts. Until then
+ // pretend that only the first account exists when syncing calendar.
+ if ("calendar".equals(name)) {
+ break;
+ }
+ }
+ }
+ }
+
+ private void setStatusText(String message) {
+ mStatusText = message;
+ }
+
+ private void populateProvidersList(Uri url, List<String> names, List<ProviderInfo> providers) {
+ try {
+ final IPackageManager packageManager = getPackageManager();
+ if (url == null) {
+ packageManager.querySyncProviders(names, providers);
+ } else {
+ final String authority = url.getAuthority();
+ ProviderInfo info = packageManager.resolveContentProvider(url.getAuthority(), 0);
+ if (info != null) {
+ // only set this provider if the requested authority is the primary authority
+ String[] providerNames = info.authority.split(";");
+ if (url.getAuthority().equals(providerNames[0])) {
+ names.add(authority);
+ providers.add(info);
+ }
+ }
+ }
+ } catch (RemoteException ex) {
+ // we should really never get this, but if we do then clear the lists, which
+ // will result in the dropping of the sync request
+ Log.e(TAG, "error trying to get the ProviderInfo for " + url, ex);
+ names.clear();
+ providers.clear();
+ }
+ }
+
+ public void scheduleLocalSync(Uri url) {
+ final Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ scheduleSync(url, extras, LOCAL_SYNC_DELAY);
+ }
+
+ private IPackageManager getPackageManager() {
+ // Don't bother synchronizing on this. The worst that can happen is that two threads
+ // can try to get the package manager at the same time but only one result gets
+ // used. Since there is only one package manager in the system this doesn't matter.
+ if (mPackageManager == null) {
+ IBinder b = ServiceManager.getService("package");
+ mPackageManager = IPackageManager.Stub.asInterface(b);
+ }
+ return mPackageManager;
+ }
+
+ /**
+ * Initiate a sync for this given URL, or pass null for a full sync.
+ *
+ * <p>You'll start getting callbacks after this.
+ *
+ * @param url The Uri of a specific provider to be synced, or
+ * null to sync all providers.
+ * @param extras a Map of SyncAdapter specific information to control
+ * syncs of a specific provider. Can be null. Is ignored
+ */
+ public void startSync(Uri url, Bundle extras) {
+ scheduleSync(url, extras, 0 /* no delay */);
+ }
+
+ public void updateHeartbeatTime() {
+ mHeartbeatTime = SystemClock.elapsedRealtime();
+ ensureContentResolver();
+ mContentResolver.notifyChange(Sync.Active.CONTENT_URI,
+ null /* this change wasn't made through an observer */);
+ }
+
+ private void sendSyncAlarmMessage() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
+ mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
+ }
+
+ private void sendCheckAlarmsMessage() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+ mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
+ }
+
+ private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
+ SyncResult syncResult) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
+ msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
+ mSyncHandler.sendMessage(msg);
+ }
+
+ class SyncHandlerMessagePayload {
+ public final ActiveSyncContext activeSyncContext;
+ public final SyncResult syncResult;
+
+ SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
+ this.activeSyncContext = syncContext;
+ this.syncResult = syncResult;
+ }
+ }
+
+ class SyncAlarmIntentReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ mHandleAlarmWakeLock.acquire();
+ sendSyncAlarmMessage();
+ }
+ }
+
+ class SyncPollAlarmReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ handleSyncPollAlarm();
+ }
+ }
+
+ private void rescheduleImmediately(SyncOperation syncOperation) {
+ SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
+ rescheduledSyncOperation.setDelay(0);
+ scheduleSyncOperation(rescheduledSyncOperation);
+ }
+
+ private long rescheduleWithDelay(SyncOperation syncOperation) {
+ long newDelayInMs;
+
+ if (syncOperation.delay == 0) {
+ // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
+ newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
+ (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
+ } else {
+ // Subsequent delays are the double of the previous delay
+ newDelayInMs = syncOperation.delay * 2;
+ }
+
+ // Cap the delay
+ ensureContentResolver();
+ long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContentResolver,
+ Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
+ if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
+ newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
+ }
+
+ SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
+ rescheduledSyncOperation.setDelay(newDelayInMs);
+ scheduleSyncOperation(rescheduledSyncOperation);
+ return newDelayInMs;
+ }
+
+ /**
+ * Cancel the active sync if it matches the uri. The uri corresponds to the one passed
+ * in to startSync().
+ * @param uri If non-null, the active sync is only canceled if it matches the uri.
+ * If null, any active sync is canceled.
+ */
+ public void cancelActiveSync(Uri uri) {
+ ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ if (activeSyncContext != null) {
+ // if a Uri was specified then only cancel the sync if it matches the the uri
+ if (uri != null) {
+ if (!uri.getAuthority().equals(activeSyncContext.mSyncOperation.authority)) {
+ return;
+ }
+ }
+ sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ null /* no result since this is a cancel */);
+ }
+ }
+
+ /**
+ * Create and schedule a SyncOperation.
+ *
+ * @param syncOperation the SyncOperation to schedule
+ */
+ public void scheduleSyncOperation(SyncOperation syncOperation) {
+ // If this operation is expedited and there is a sync in progress then
+ // reschedule the current operation and send a cancel for it.
+ final boolean expedited = syncOperation.delay < 0;
+ final ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ if (expedited && activeSyncContext != null) {
+ final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0;
+ final boolean hasSameKey =
+ activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
+ // This request is expedited and there is a sync in progress.
+ // Interrupt the current sync only if it is not expedited and if it has a different
+ // key than the one we are scheduling.
+ if (!activeIsExpedited && !hasSameKey) {
+ rescheduleImmediately(activeSyncContext.mSyncOperation);
+ sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ null /* no result since this is a cancel */);
+ }
+ }
+
+ boolean operationEnqueued;
+ synchronized (mSyncQueue) {
+ operationEnqueued = mSyncQueue.add(syncOperation);
+ }
+
+ if (operationEnqueued) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
+ }
+ sendCheckAlarmsMessage();
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
+ + syncOperation);
+ }
+ }
+ }
+
+ /**
+ * Remove any scheduled sync operations that match uri. The uri corresponds to the one passed
+ * in to startSync().
+ * @param uri If non-null, only operations that match the uri are cleared.
+ * If null, all operations are cleared.
+ */
+ public void clearScheduledSyncOperations(Uri uri) {
+ synchronized (mSyncQueue) {
+ mSyncQueue.clear(null, uri != null ? uri.getAuthority() : null);
+ }
+ }
+
+ void maybeRescheduleSync(SyncResult syncResult, SyncOperation previousSyncOperation) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+ if (isLoggable) {
+ Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", "
+ + previousSyncOperation);
+ }
+
+ // If the operation succeeded to some extent then retry immediately.
+ // If this was a two-way sync then retry soft errors with an exponential backoff.
+ // If this was an upward sync then schedule a two-way sync immediately.
+ // Otherwise do not reschedule.
+
+ if (syncResult.madeSomeProgress()) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation immediately because "
+ + "even though it had an error it achieved some success");
+ }
+ rescheduleImmediately(previousSyncOperation);
+ } else if (previousSyncOperation.extras.getBoolean(
+ ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+ final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation);
+ newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+ newSyncOperation.setDelay(0);
+ if (Config.LOGD) {
+ Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ + "encountered an error: " + previousSyncOperation);
+ }
+ scheduleSyncOperation(newSyncOperation);
+ } else if (syncResult.hasSoftError()) {
+ long delay = rescheduleWithDelay(previousSyncOperation);
+ if (delay >= 0) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation in " + delay + " ms because "
+ + "it encountered a soft error: " + previousSyncOperation);
+ }
+ }
+ } else {
+ if (Config.LOGD) {
+ Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ + previousSyncOperation);
+ }
+ }
+ }
+
+ /**
+ * Value type that represents a sync operation.
+ */
+ static class SyncOperation implements Comparable {
+ final String account;
+ int syncSource;
+ String authority;
+ Bundle extras;
+ final String key;
+ long earliestRunTime;
+ long delay;
+ Long rowId = null;
+
+ SyncOperation(String account, int source, String authority, Bundle extras, long delay) {
+ this.account = account;
+ this.syncSource = source;
+ this.authority = authority;
+ this.extras = new Bundle(extras);
+ this.setDelay(delay);
+ this.key = toKey();
+ }
+
+ SyncOperation(SyncOperation other) {
+ this.account = other.account;
+ this.syncSource = other.syncSource;
+ this.authority = other.authority;
+ this.extras = new Bundle(other.extras);
+ this.delay = other.delay;
+ this.earliestRunTime = other.earliestRunTime;
+ this.key = toKey();
+ }
+
+ public void setDelay(long delay) {
+ this.delay = delay;
+ if (delay >= 0) {
+ this.earliestRunTime = SystemClock.elapsedRealtime() + delay;
+ } else {
+ this.earliestRunTime = 0;
+ }
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account: ").append(account);
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb);
+ sb.append(" syncSource: ").append(syncSource);
+ sb.append(" when: ").append(earliestRunTime);
+ sb.append(" delay: ").append(delay);
+ sb.append(" key: {").append(key).append("}");
+ if (rowId != null) sb.append(" rowId: ").append(rowId);
+ return sb.toString();
+ }
+
+ private String toKey() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account: ").append(account);
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb);
+ return sb.toString();
+ }
+
+ private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+ sb.append("[");
+ for (String key : bundle.keySet()) {
+ sb.append(key).append("=").append(bundle.get(key)).append(" ");
+ }
+ sb.append("]");
+ }
+
+ public int compareTo(Object o) {
+ SyncOperation other = (SyncOperation)o;
+ if (earliestRunTime == other.earliestRunTime) {
+ return 0;
+ }
+ return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ class ActiveSyncContext extends ISyncContext.Stub {
+ final SyncOperation mSyncOperation;
+ final long mHistoryRowId;
+ final IContentProvider mContentProvider;
+ final ISyncAdapter mSyncAdapter;
+ final long mStartTime;
+ long mTimeoutStartTime;
+
+ public ActiveSyncContext(SyncOperation syncOperation, IContentProvider contentProvider,
+ ISyncAdapter syncAdapter, long historyRowId) {
+ super();
+ mSyncOperation = syncOperation;
+ mHistoryRowId = historyRowId;
+ mContentProvider = contentProvider;
+ mSyncAdapter = syncAdapter;
+ mStartTime = SystemClock.elapsedRealtime();
+ mTimeoutStartTime = mStartTime;
+ }
+
+ public void sendHeartbeat() {
+ // ignore this call if it corresponds to an old sync session
+ if (mActiveSyncContext == this) {
+ SyncManager.this.updateHeartbeatTime();
+ }
+ }
+
+ public void onFinished(SyncResult result) {
+ // include "this" in the message so that the handler can ignore it if this
+ // ActiveSyncContext is no longer the mActiveSyncContext at message handling
+ // time
+ sendSyncFinishedOrCanceledMessage(this, result);
+ }
+
+ public void toString(StringBuilder sb) {
+ sb.append("startTime ").append(mStartTime)
+ .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
+ .append(", mHistoryRowId ").append(mHistoryRowId)
+ .append(", syncOperation ").append(mSyncOperation);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(sb);
+ return sb.toString();
+ }
+ }
+
+ protected void dump(FileDescriptor fd, PrintWriter pw) {
+ StringBuilder sb = new StringBuilder();
+ dumpSyncState(sb);
+ sb.append("\n");
+ if (isSyncEnabled()) {
+ dumpSyncHistory(sb);
+ }
+ pw.println(sb.toString());
+ }
+
+ protected void dumpSyncState(StringBuilder sb) {
+ sb.append("sync enabled: ").append(isSyncEnabled()).append("\n");
+ sb.append("data connected: ").append(mDataConnectionIsConnected).append("\n");
+ sb.append("memory low: ").append(mStorageIsLow).append("\n");
+
+ final String[] accounts = mAccounts;
+ sb.append("accounts: ");
+ if (accounts != null) {
+ sb.append(accounts.length);
+ } else {
+ sb.append("none");
+ }
+ sb.append("\n");
+ final long now = SystemClock.elapsedRealtime();
+ sb.append("now: ").append(now).append("\n");
+ sb.append("uptime: ").append(DateUtils.formatElapsedTime(now/1000)).append(" (HH:MM:SS)\n");
+ sb.append("time spent syncing : ")
+ .append(DateUtils.formatElapsedTime(
+ mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000))
+ .append(" (HH:MM:SS), sync ")
+ .append(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ")
+ .append("in progress").append("\n");
+ if (mSyncHandler.mAlarmScheduleTime != null) {
+ sb.append("next alarm time: ").append(mSyncHandler.mAlarmScheduleTime)
+ .append(" (")
+ .append(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000))
+ .append(" (HH:MM:SS) from now)\n");
+ } else {
+ sb.append("no alarm is scheduled (there had better not be any pending syncs)\n");
+ }
+
+ sb.append("active sync: ").append(mActiveSyncContext).append("\n");
+
+ sb.append("notification info: ");
+ mSyncHandler.mSyncNotificationInfo.toString(sb);
+ sb.append("\n");
+
+ synchronized (mSyncQueue) {
+ sb.append("sync queue: ");
+ mSyncQueue.dump(sb);
+ }
+
+ Cursor c = mSyncStorageEngine.query(Sync.Active.CONTENT_URI,
+ SYNC_ACTIVE_PROJECTION, null, null, null);
+ sb.append("\n");
+ try {
+ if (c.moveToNext()) {
+ final long durationInSeconds = (now - c.getLong(2)) / 1000;
+ sb.append("Active sync: ").append(c.getString(0))
+ .append(" ").append(c.getString(1))
+ .append(", duration is ")
+ .append(DateUtils.formatElapsedTime(durationInSeconds)).append(".\n");
+ } else {
+ sb.append("No sync is in progress.\n");
+ }
+ } finally {
+ c.close();
+ }
+
+ c = mSyncStorageEngine.query(Sync.Pending.CONTENT_URI,
+ SYNC_PENDING_PROJECTION, null, null, "account, authority");
+ sb.append("\nPending Syncs\n");
+ try {
+ if (c.getCount() != 0) {
+ dumpSyncPendingHeader(sb);
+ while (c.moveToNext()) {
+ dumpSyncPendingRow(sb, c);
+ }
+ dumpSyncPendingFooter(sb);
+ } else {
+ sb.append("none\n");
+ }
+ } finally {
+ c.close();
+ }
+
+ String currentAccount = null;
+ c = mSyncStorageEngine.query(Sync.Status.CONTENT_URI,
+ STATUS_PROJECTION, null, null, "account, authority");
+ sb.append("\nSync history by account and authority\n");
+ try {
+ while (c.moveToNext()) {
+ if (!TextUtils.equals(currentAccount, c.getString(0))) {
+ if (currentAccount != null) {
+ dumpSyncHistoryFooter(sb);
+ }
+ currentAccount = c.getString(0);
+ dumpSyncHistoryHeader(sb, currentAccount);
+ }
+
+ dumpSyncHistoryRow(sb, c);
+ }
+ if (c.getCount() > 0) dumpSyncHistoryFooter(sb);
+ } finally {
+ c.close();
+ }
+ }
+
+ private void dumpSyncHistoryHeader(StringBuilder sb, String account) {
+ sb.append(" Account: ").append(account).append("\n");
+ sb.append(" ___________________________________________________________________________________________________________________________\n");
+ sb.append(" | | num times synced | total | last success | |\n");
+ sb.append(" | authority | local | poll | server | user | total | duration | source | time | result if failing |\n");
+ }
+
+ private static String[] STATUS_PROJECTION = new String[]{
+ Sync.Status.ACCOUNT, // 0
+ Sync.Status.AUTHORITY, // 1
+ Sync.Status.NUM_SYNCS, // 2
+ Sync.Status.TOTAL_ELAPSED_TIME, // 3
+ Sync.Status.NUM_SOURCE_LOCAL, // 4
+ Sync.Status.NUM_SOURCE_POLL, // 5
+ Sync.Status.NUM_SOURCE_SERVER, // 6
+ Sync.Status.NUM_SOURCE_USER, // 7
+ Sync.Status.LAST_SUCCESS_SOURCE, // 8
+ Sync.Status.LAST_SUCCESS_TIME, // 9
+ Sync.Status.LAST_FAILURE_SOURCE, // 10
+ Sync.Status.LAST_FAILURE_TIME, // 11
+ Sync.Status.LAST_FAILURE_MESG // 12
+ };
+
+ private void dumpSyncHistoryRow(StringBuilder sb, Cursor c) {
+ boolean hasSuccess = !c.isNull(9);
+ boolean hasFailure = !c.isNull(11);
+ Time timeSuccess = new Time();
+ if (hasSuccess) timeSuccess.set(c.getLong(9));
+ Time timeFailure = new Time();
+ if (hasFailure) timeFailure.set(c.getLong(11));
+ sb.append(String.format(" | %-15s | %5d | %5d | %6d | %5d | %5d | %8s | %7s | %19s | %19s |\n",
+ c.getString(1),
+ c.getLong(4),
+ c.getLong(5),
+ c.getLong(6),
+ c.getLong(7),
+ c.getLong(2),
+ DateUtils.formatElapsedTime(c.getLong(3)/1000),
+ hasSuccess ? Sync.History.SOURCES[c.getInt(8)] : "",
+ hasSuccess ? timeSuccess.format("%Y-%m-%d %H:%M:%S") : "",
+ hasFailure ? History.mesgToString(c.getString(12)) : ""));
+ }
+
+ private void dumpSyncHistoryFooter(StringBuilder sb) {
+ sb.append(" |___________________________________________________________________________________________________________________________|\n");
+ }
+
+ private void dumpSyncPendingHeader(StringBuilder sb) {
+ sb.append(" ____________________________________________________\n");
+ sb.append(" | account | authority |\n");
+ }
+
+ private void dumpSyncPendingRow(StringBuilder sb, Cursor c) {
+ sb.append(String.format(" | %-30s | %-15s |\n", c.getString(0), c.getString(1)));
+ }
+
+ private void dumpSyncPendingFooter(StringBuilder sb) {
+ sb.append(" |__________________________________________________|\n");
+ }
+
+ protected void dumpSyncHistory(StringBuilder sb) {
+ Cursor c = mSyncStorageEngine.query(Sync.History.CONTENT_URI, null, "event=?",
+ new String[]{String.valueOf(Sync.History.EVENT_STOP)},
+ Sync.HistoryColumns.EVENT_TIME + " desc");
+ try {
+ long numSyncsLastHour = 0, durationLastHour = 0;
+ long numSyncsLastDay = 0, durationLastDay = 0;
+ long numSyncsLastWeek = 0, durationLastWeek = 0;
+ long numSyncsLast4Weeks = 0, durationLast4Weeks = 0;
+ long numSyncsTotal = 0, durationTotal = 0;
+
+ long now = System.currentTimeMillis();
+ int indexEventTime = c.getColumnIndexOrThrow(Sync.History.EVENT_TIME);
+ int indexElapsedTime = c.getColumnIndexOrThrow(Sync.History.ELAPSED_TIME);
+ while (c.moveToNext()) {
+ long duration = c.getLong(indexElapsedTime);
+ long endTime = c.getLong(indexEventTime) + duration;
+ long millisSinceStart = now - endTime;
+ numSyncsTotal++;
+ durationTotal += duration;
+ if (millisSinceStart < MILLIS_IN_HOUR) {
+ numSyncsLastHour++;
+ durationLastHour += duration;
+ }
+ if (millisSinceStart < MILLIS_IN_DAY) {
+ numSyncsLastDay++;
+ durationLastDay += duration;
+ }
+ if (millisSinceStart < MILLIS_IN_WEEK) {
+ numSyncsLastWeek++;
+ durationLastWeek += duration;
+ }
+ if (millisSinceStart < MILLIS_IN_4WEEKS) {
+ numSyncsLast4Weeks++;
+ durationLast4Weeks += duration;
+ }
+ }
+ dumpSyncIntervalHeader(sb);
+ dumpSyncInterval(sb, "hour", MILLIS_IN_HOUR, numSyncsLastHour, durationLastHour);
+ dumpSyncInterval(sb, "day", MILLIS_IN_DAY, numSyncsLastDay, durationLastDay);
+ dumpSyncInterval(sb, "week", MILLIS_IN_WEEK, numSyncsLastWeek, durationLastWeek);
+ dumpSyncInterval(sb, "4 weeks",
+ MILLIS_IN_4WEEKS, numSyncsLast4Weeks, durationLast4Weeks);
+ dumpSyncInterval(sb, "total", 0, numSyncsTotal, durationTotal);
+ dumpSyncIntervalFooter(sb);
+ } finally {
+ c.close();
+ }
+ }
+
+ private void dumpSyncIntervalHeader(StringBuilder sb) {
+ sb.append("Sync Stats\n");
+ sb.append(" ___________________________________________________________\n");
+ sb.append(" | | | duration in sec | |\n");
+ sb.append(" | interval | count | average | total | % of interval |\n");
+ }
+
+ private void dumpSyncInterval(StringBuilder sb, String label,
+ long interval, long numSyncs, long duration) {
+ sb.append(String.format(" | %-8s | %6d | %8.1f | %8.1f",
+ label, numSyncs, ((float)duration/numSyncs)/1000, (float)duration/1000));
+ if (interval > 0) {
+ sb.append(String.format(" | %13.2f |\n", ((float)duration/interval)*100.0));
+ } else {
+ sb.append(String.format(" | %13s |\n", "na"));
+ }
+ }
+
+ private void dumpSyncIntervalFooter(StringBuilder sb) {
+ sb.append(" |_________________________________________________________|\n");
+ }
+
+ /**
+ * A helper object to keep track of the time we have spent syncing since the last boot
+ */
+ private class SyncTimeTracker {
+ /** True if a sync was in progress on the most recent call to update() */
+ boolean mLastWasSyncing = false;
+ /** Used to track when lastWasSyncing was last set */
+ long mWhenSyncStarted = 0;
+ /** The cumulative time we have spent syncing */
+ private long mTimeSpentSyncing;
+
+ /** Call to let the tracker know that the sync state may have changed */
+ public synchronized void update() {
+ final boolean isSyncInProgress = mActiveSyncContext != null;
+ if (isSyncInProgress == mLastWasSyncing) return;
+ final long now = SystemClock.elapsedRealtime();
+ if (isSyncInProgress) {
+ mWhenSyncStarted = now;
+ } else {
+ mTimeSpentSyncing += now - mWhenSyncStarted;
+ }
+ mLastWasSyncing = isSyncInProgress;
+ }
+
+ /** Get how long we have been syncing, in ms */
+ public synchronized long timeSpentSyncing() {
+ if (!mLastWasSyncing) return mTimeSpentSyncing;
+
+ final long now = SystemClock.elapsedRealtime();
+ return mTimeSpentSyncing + (now - mWhenSyncStarted);
+ }
+ }
+
+ /**
+ * Handles SyncOperation Messages that are posted to the associated
+ * HandlerThread.
+ */
+ class SyncHandler extends Handler {
+ // Messages that can be sent on mHandler
+ private static final int MESSAGE_SYNC_FINISHED = 1;
+ private static final int MESSAGE_SYNC_ALARM = 2;
+ private static final int MESSAGE_CHECK_ALARMS = 3;
+
+ public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
+ private Long mAlarmScheduleTime = null;
+ public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+
+ // used to track if we have installed the error notification so that we don't reinstall
+ // it if sync is still failing
+ private boolean mErrorNotificationInstalled = false;
+
+ /**
+ * Used to keep track of whether a sync notification is active and who it is for.
+ */
+ class SyncNotificationInfo {
+ // only valid if isActive is true
+ public String account;
+
+ // only valid if isActive is true
+ public String authority;
+
+ // true iff the notification manager has been asked to send the notification
+ public boolean isActive = false;
+
+ // Set when we transition from not running a sync to running a sync, and cleared on
+ // the opposite transition.
+ public Long startTime = null;
+
+ public void toString(StringBuilder sb) {
+ sb.append("account ").append(account)
+ .append(", authority ").append(authority)
+ .append(", isActive ").append(isActive)
+ .append(", startTime ").append(startTime);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(sb);
+ return sb.toString();
+ }
+ }
+
+ public SyncHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ handleSyncHandlerMessage(msg);
+ }
+
+ private void handleSyncHandlerMessage(Message msg) {
+ try {
+ switch (msg.what) {
+ case SyncHandler.MESSAGE_SYNC_FINISHED:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
+ }
+ SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
+ if (mActiveSyncContext != payload.activeSyncContext) {
+ if (Config.LOGD) {
+ Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
+ + "dropping: mActiveSyncContext " + mActiveSyncContext
+ + " != " + payload.activeSyncContext);
+ }
+ return;
+ }
+ runSyncFinishedOrCanceled(payload.syncResult);
+
+ // since we are no longer syncing, check if it is time to start a new sync
+ runStateIdle();
+ break;
+
+ case SyncHandler.MESSAGE_SYNC_ALARM: {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
+ }
+ mAlarmScheduleTime = null;
+ try {
+ if (mActiveSyncContext != null) {
+ if (isLoggable) {
+ Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
+ }
+ runStateSyncing();
+ }
+
+ // if the above call to runStateSyncing() resulted in the end of a sync,
+ // check if it is time to start a new sync
+ if (mActiveSyncContext == null) {
+ if (isLoggable) {
+ Log.v(TAG, "handleSyncHandlerMessage: "
+ + "sync context is not active");
+ }
+ runStateIdle();
+ }
+ } finally {
+ mHandleAlarmWakeLock.release();
+ }
+ break;
+ }
+
+ case SyncHandler.MESSAGE_CHECK_ALARMS:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
+ }
+ // we do all the work for this case in the finally block
+ break;
+ }
+ } finally {
+ final boolean isSyncInProgress = mActiveSyncContext != null;
+ if (!isSyncInProgress) {
+ mSyncWakeLock.release();
+ }
+ manageSyncNotification();
+ manageErrorNotification();
+ manageSyncAlarm();
+ mSyncTimeTracker.update();
+ }
+ }
+
+ private void runStateSyncing() {
+ // if the sync timeout has been reached then cancel it
+
+ ActiveSyncContext activeSyncContext = mActiveSyncContext;
+
+ final long now = SystemClock.elapsedRealtime();
+ if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
+ SyncOperation nextSyncOperation;
+ synchronized (mSyncQueue) {
+ nextSyncOperation = mSyncQueue.head();
+ }
+ if (nextSyncOperation != null && nextSyncOperation.earliestRunTime <= now) {
+ if (Config.LOGD) {
+ Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
+ + activeSyncContext.mSyncOperation);
+ }
+ rescheduleImmediately(activeSyncContext.mSyncOperation);
+ sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ null /* no result since this is a cancel */);
+ } else {
+ activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
+ }
+ }
+
+ // no need to schedule an alarm, as that will be done by our caller.
+ }
+
+ private void runStateIdle() {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) Log.v(TAG, "runStateIdle");
+
+ // If we aren't ready to run (e.g. the data connection is down), get out.
+ if (!mDataConnectionIsConnected) {
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: no data connection, skipping");
+ }
+ setStatusText("No data connection");
+ return;
+ }
+
+ if (mStorageIsLow) {
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: memory low, skipping");
+ }
+ setStatusText("Memory low");
+ return;
+ }
+
+ // If the accounts aren't known yet then we aren't ready to run. We will be kicked
+ // when the account lookup request does complete.
+ String[] accounts = mAccounts;
+ if (accounts == null) {
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: accounts not known, skipping");
+ }
+ setStatusText("Accounts not known yet");
+ return;
+ }
+
+ // Otherwise consume SyncOperations from the head of the SyncQueue until one is
+ // found that is runnable (not disabled, etc). If that one is ready to run then
+ // start it, otherwise just get out.
+ SyncOperation syncOperation;
+ final Sync.Settings.QueryMap syncSettings = getSyncSettings();
+ final ConnectivityManager connManager = (ConnectivityManager)
+ mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final boolean backgroundDataSetting = connManager.getBackgroundDataSetting();
+ synchronized (mSyncQueue) {
+ while (true) {
+ syncOperation = mSyncQueue.head();
+ if (syncOperation == null) {
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: no more sync operations, returning");
+ }
+ return;
+ }
+
+ // Sync is disabled, drop this operation.
+ if (!isSyncEnabled()) {
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: sync disabled, dropping " + syncOperation);
+ }
+ mSyncQueue.popHead();
+ continue;
+ }
+
+ // skip the sync if it isn't a force and the settings are off for this provider
+ final boolean force = syncOperation.extras.getBoolean(
+ ContentResolver.SYNC_EXTRAS_FORCE, false);
+ if (!force && (!backgroundDataSetting
+ || !syncSettings.getListenForNetworkTickles()
+ || !syncSettings.getSyncProviderAutomatically(
+ syncOperation.authority))) {
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: sync off, dropping " + syncOperation);
+ }
+ mSyncQueue.popHead();
+ continue;
+ }
+
+ // skip the sync if the account of this operation no longer exists
+ if (!ArrayUtils.contains(accounts, syncOperation.account)) {
+ mSyncQueue.popHead();
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: account not present, dropping "
+ + syncOperation);
+ }
+ continue;
+ }
+
+ // go ahead and try to sync this syncOperation
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: found sync candidate: " + syncOperation);
+ }
+ break;
+ }
+
+ // If the first SyncOperation isn't ready to run schedule a wakeup and
+ // get out.
+ final long now = SystemClock.elapsedRealtime();
+ if (syncOperation.earliestRunTime > now) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "runStateIdle: the time is " + now + " yet the next "
+ + "sync operation is for " + syncOperation.earliestRunTime
+ + ": " + syncOperation);
+ }
+ return;
+ }
+
+ // We will do this sync. Remove it from the queue and run it outside of the
+ // synchronized block.
+ if (isLoggable) {
+ Log.v(TAG, "runStateIdle: we are going to sync " + syncOperation);
+ }
+ mSyncQueue.popHead();
+ }
+
+ String providerName = syncOperation.authority;
+ ensureContentResolver();
+ IContentProvider contentProvider;
+
+ // acquire the provider and update the sync history
+ try {
+ contentProvider = mContentResolver.acquireProvider(providerName);
+ if (contentProvider == null) {
+ Log.e(TAG, "Provider " + providerName + " doesn't exist");
+ return;
+ }
+ if (contentProvider.getSyncAdapter() == null) {
+ Log.e(TAG, "Provider " + providerName + " isn't syncable, " + contentProvider);
+ return;
+ }
+ } catch (RemoteException remoteExc) {
+ Log.e(TAG, "Caught a RemoteException while preparing for sync, rescheduling "
+ + syncOperation, remoteExc);
+ rescheduleWithDelay(syncOperation);
+ return;
+ } catch (RuntimeException exc) {
+ Log.e(TAG, "Caught a RuntimeException while validating sync of " + providerName,
+ exc);
+ return;
+ }
+
+ final long historyRowId = insertStartSyncEvent(syncOperation);
+
+ try {
+ ISyncAdapter syncAdapter = contentProvider.getSyncAdapter();
+ ActiveSyncContext activeSyncContext = new ActiveSyncContext(syncOperation,
+ contentProvider, syncAdapter, historyRowId);
+ mSyncWakeLock.acquire();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "starting sync of " + syncOperation);
+ }
+ syncAdapter.startSync(activeSyncContext, syncOperation.account,
+ syncOperation.extras);
+ mActiveSyncContext = activeSyncContext;
+ mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ } catch (RemoteException remoteExc) {
+ if (Config.LOGD) {
+ Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
+ }
+ mActiveSyncContext = null;
+ mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ rescheduleWithDelay(syncOperation);
+ } catch (RuntimeException exc) {
+ mActiveSyncContext = null;
+ mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation,
+ exc);
+ }
+
+ // no need to schedule an alarm, as that will be done by our caller.
+ }
+
+ private void runSyncFinishedOrCanceled(SyncResult syncResult) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
+ ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ mActiveSyncContext = null;
+ mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+
+ final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+
+ final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
+
+ String historyMessage;
+ int downstreamActivity;
+ int upstreamActivity;
+ if (syncResult != null) {
+ if (isLoggable) {
+ Log.v(TAG, "runSyncFinishedOrCanceled: is a finished: operation "
+ + syncOperation + ", result " + syncResult);
+ }
+
+ if (!syncResult.hasError()) {
+ if (isLoggable) {
+ Log.v(TAG, "finished sync operation " + syncOperation);
+ }
+ historyMessage = History.MESG_SUCCESS;
+ // TODO: set these correctly when the SyncResult is extended to include it
+ downstreamActivity = 0;
+ upstreamActivity = 0;
+ } else {
+ maybeRescheduleSync(syncResult, syncOperation);
+ if (Config.LOGD) {
+ Log.d(TAG, "failed sync operation " + syncOperation);
+ }
+ historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
+ // TODO: set these correctly when the SyncResult is extended to include it
+ downstreamActivity = 0;
+ upstreamActivity = 0;
+ }
+ } else {
+ if (isLoggable) {
+ Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation "
+ + syncOperation);
+ }
+ try {
+ activeSyncContext.mSyncAdapter.cancelSync();
+ } catch (RemoteException e) {
+ // we don't need to retry this in this case
+ }
+ historyMessage = History.MESG_CANCELED;
+ downstreamActivity = 0;
+ upstreamActivity = 0;
+ }
+
+ stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
+ upstreamActivity, downstreamActivity, elapsedTime);
+
+ mContentResolver.releaseProvider(activeSyncContext.mContentProvider);
+
+ if (syncResult != null && syncResult.tooManyDeletions) {
+ installHandleTooManyDeletesNotification(syncOperation.account,
+ syncOperation.authority, syncResult.stats.numDeletes);
+ } else {
+ mNotificationMgr.cancel(
+ syncOperation.account.hashCode() ^ syncOperation.authority.hashCode());
+ }
+
+ if (syncResult != null && syncResult.fullSyncRequested) {
+ scheduleSyncOperation(new SyncOperation(syncOperation.account,
+ syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
+ }
+ // no need to schedule an alarm, as that will be done by our caller.
+ }
+
+ /**
+ * Convert the error-containing SyncResult into the Sync.History error number. Since
+ * the SyncResult may indicate multiple errors at once, this method just returns the
+ * most "serious" error.
+ * @param syncResult the SyncResult from which to read
+ * @return the most "serious" error set in the SyncResult
+ * @throws IllegalStateException if the SyncResult does not indicate any errors.
+ * If SyncResult.error() is true then it is safe to call this.
+ */
+ private int syncResultToErrorNumber(SyncResult syncResult) {
+ if (syncResult.syncAlreadyInProgress) return History.ERROR_SYNC_ALREADY_IN_PROGRESS;
+ if (syncResult.stats.numAuthExceptions > 0) return History.ERROR_AUTHENTICATION;
+ if (syncResult.stats.numIoExceptions > 0) return History.ERROR_IO;
+ if (syncResult.stats.numParseExceptions > 0) return History.ERROR_PARSE;
+ if (syncResult.stats.numConflictDetectedExceptions > 0) return History.ERROR_CONFLICT;
+ if (syncResult.tooManyDeletions) return History.ERROR_TOO_MANY_DELETIONS;
+ if (syncResult.tooManyRetries) return History.ERROR_TOO_MANY_RETRIES;
+ if (syncResult.databaseError) return History.ERROR_INTERNAL;
+ throw new IllegalStateException("we are not in an error state, " + syncResult);
+ }
+
+ private void manageSyncNotification() {
+ boolean shouldCancel;
+ boolean shouldInstall;
+
+ if (mActiveSyncContext == null) {
+ mSyncNotificationInfo.startTime = null;
+
+ // we aren't syncing. if the notification is active then remember that we need
+ // to cancel it and then clear out the info
+ shouldCancel = mSyncNotificationInfo.isActive;
+ shouldInstall = false;
+ } else {
+ // we are syncing
+ final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+
+ final long now = SystemClock.elapsedRealtime();
+ if (mSyncNotificationInfo.startTime == null) {
+ mSyncNotificationInfo.startTime = now;
+ }
+
+ // cancel the notification if it is up and the authority or account is wrong
+ shouldCancel = mSyncNotificationInfo.isActive &&
+ (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
+ || !syncOperation.account.equals(mSyncNotificationInfo.account));
+
+ // there are four cases:
+ // - the notification is up and there is no change: do nothing
+ // - the notification is up but we should cancel since it is stale:
+ // need to install
+ // - the notification is not up but it isn't time yet: don't install
+ // - the notification is not up and it is time: need to install
+
+ if (mSyncNotificationInfo.isActive) {
+ shouldInstall = shouldCancel;
+ } else {
+ final boolean timeToShowNotification =
+ now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
+ final boolean syncIsForced = syncOperation.extras
+ .getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
+ shouldInstall = timeToShowNotification || syncIsForced;
+ }
+ }
+
+ if (shouldCancel && !shouldInstall) {
+ mNeedSyncActiveNotification = false;
+ sendSyncStateIntent();
+ mSyncNotificationInfo.isActive = false;
+ }
+
+ if (shouldInstall) {
+ SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+ mNeedSyncActiveNotification = true;
+ sendSyncStateIntent();
+ mSyncNotificationInfo.isActive = true;
+ mSyncNotificationInfo.account = syncOperation.account;
+ mSyncNotificationInfo.authority = syncOperation.authority;
+ }
+ }
+
+ /**
+ * Check if there were any long-lasting errors, if so install the error notification,
+ * otherwise cancel the error notification.
+ */
+ private void manageErrorNotification() {
+ //
+ long when = mSyncStorageEngine.getInitialSyncFailureTime();
+ if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
+ if (!mErrorNotificationInstalled) {
+ mNeedSyncErrorNotification = true;
+ sendSyncStateIntent();
+ }
+ mErrorNotificationInstalled = true;
+ } else {
+ if (mErrorNotificationInstalled) {
+ mNeedSyncErrorNotification = false;
+ sendSyncStateIntent();
+ }
+ mErrorNotificationInstalled = false;
+ }
+ }
+
+ private void manageSyncAlarm() {
+ // in each of these cases the sync loop will be kicked, which will cause this
+ // method to be called again
+ if (!mDataConnectionIsConnected) return;
+ if (mAccounts == null) return;
+ if (mStorageIsLow) return;
+
+ // Compute the alarm fire time:
+ // - not syncing: time of the next sync operation
+ // - syncing, no notification: time from sync start to notification create time
+ // - syncing, with notification: time till timeout of the active sync operation
+ Long alarmTime = null;
+ ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ if (activeSyncContext == null) {
+ SyncOperation syncOperation;
+ synchronized (mSyncQueue) {
+ syncOperation = mSyncQueue.head();
+ }
+ if (syncOperation != null) {
+ alarmTime = syncOperation.earliestRunTime;
+ }
+ } else {
+ final long notificationTime =
+ mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
+ final long timeoutTime =
+ mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+ if (mSyncHandler.mSyncNotificationInfo.isActive) {
+ alarmTime = timeoutTime;
+ } else {
+ alarmTime = Math.min(notificationTime, timeoutTime);
+ }
+ }
+
+ // adjust the alarmTime so that we will wake up when it is time to
+ // install the error notification
+ if (!mErrorNotificationInstalled) {
+ long when = mSyncStorageEngine.getInitialSyncFailureTime();
+ if (when > 0) {
+ when += ERROR_NOTIFICATION_DELAY_MS;
+ // convert when fron absolute time to elapsed run time
+ long delay = when - System.currentTimeMillis();
+ when = SystemClock.elapsedRealtime() + delay;
+ alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
+ }
+ }
+
+ // determine if we need to set or cancel the alarm
+ boolean shouldSet = false;
+ boolean shouldCancel = false;
+ final boolean alarmIsActive = mAlarmScheduleTime != null;
+ final boolean needAlarm = alarmTime != null;
+ if (needAlarm) {
+ if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
+ shouldSet = true;
+ }
+ } else {
+ shouldCancel = alarmIsActive;
+ }
+
+ // set or cancel the alarm as directed
+ ensureAlarmService();
+ if (shouldSet) {
+ mAlarmScheduleTime = alarmTime;
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
+ mSyncAlarmIntent);
+ } else if (shouldCancel) {
+ mAlarmScheduleTime = null;
+ mAlarmService.cancel(mSyncAlarmIntent);
+ }
+ }
+
+ private void sendSyncStateIntent() {
+ Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
+ syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
+ syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
+ mContext.sendBroadcast(syncStateIntent);
+ }
+
+ private void installHandleTooManyDeletesNotification(String account, String authority,
+ long numDeletes) {
+ if (mNotificationMgr == null) return;
+ Intent clickIntent = new Intent();
+ clickIntent.setClassName("com.android.providers.subscribedfeeds",
+ "com.android.settings.SyncActivityTooManyDeletes");
+ clickIntent.putExtra("account", account);
+ clickIntent.putExtra("provider", authority);
+ clickIntent.putExtra("numDeletes", numDeletes);
+
+ if (!isActivityAvailable(clickIntent)) {
+ Log.w(TAG, "No activity found to handle too many deletes.");
+ return;
+ }
+
+ final PendingIntent pendingIntent = PendingIntent
+ .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
+ R.string.contentServiceTooManyDeletesNotificationDesc);
+
+ String[] authorities = authority.split(";");
+ Notification notification =
+ new Notification(R.drawable.stat_notify_sync_error,
+ mContext.getString(R.string.contentServiceSync),
+ System.currentTimeMillis());
+ notification.setLatestEventInfo(mContext,
+ mContext.getString(R.string.contentServiceSyncNotificationTitle),
+ String.format(tooManyDeletesDescFormat.toString(), authorities[0]),
+ pendingIntent);
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification);
+ }
+
+ /**
+ * Checks whether an activity exists on the system image for the given intent.
+ *
+ * @param intent The intent for an activity.
+ * @return Whether or not an activity exists.
+ */
+ private boolean isActivityAvailable(Intent intent) {
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ int listSize = list.size();
+ for (int i = 0; i < listSize; i++) {
+ ResolveInfo resolveInfo = list.get(i);
+ if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public long insertStartSyncEvent(SyncOperation syncOperation) {
+ final int source = syncOperation.syncSource;
+ final long now = System.currentTimeMillis();
+
+ EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_START, source);
+
+ return mSyncStorageEngine.insertStartSyncEvent(
+ syncOperation.account, syncOperation.authority, now, source);
+ }
+
+ public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
+ int upstreamActivity, int downstreamActivity, long elapsedTime) {
+ EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_STOP, syncOperation.syncSource);
+
+ mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
+ downstreamActivity, upstreamActivity);
+ }
+ }
+
+ static class SyncQueue {
+ private SyncStorageEngine mSyncStorageEngine;
+ private final String[] COLUMNS = new String[]{
+ "_id",
+ "authority",
+ "account",
+ "extras",
+ "source"
+ };
+ private static final int COLUMN_ID = 0;
+ private static final int COLUMN_AUTHORITY = 1;
+ private static final int COLUMN_ACCOUNT = 2;
+ private static final int COLUMN_EXTRAS = 3;
+ private static final int COLUMN_SOURCE = 4;
+
+ private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
+
+ // A priority queue of scheduled SyncOperations that is designed to make it quick
+ // to find the next SyncOperation that should be considered for running.
+ private final PriorityQueue<SyncOperation> mOpsByWhen = new PriorityQueue<SyncOperation>();
+
+ // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+ // quick lookup of an enqueued SyncOperation.
+ private final HashMap<String, SyncOperation> mOpsByKey = Maps.newHashMap();
+
+ public SyncQueue(SyncStorageEngine syncStorageEngine) {
+ mSyncStorageEngine = syncStorageEngine;
+ Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS);
+ try {
+ while (cursor.moveToNext()) {
+ add(cursorToOperation(cursor),
+ true /* this is being added from the database */);
+ }
+ } finally {
+ cursor.close();
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ }
+ }
+
+ public boolean add(SyncOperation operation) {
+ return add(new SyncOperation(operation),
+ false /* this is not coming from the database */);
+ }
+
+ private boolean add(SyncOperation operation, boolean fromDatabase) {
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+
+ // If this operation is expedited then set its earliestRunTime to be immediately
+ // before the head of the list, or not if none are in the list.
+ if (operation.delay < 0) {
+ SyncOperation headOperation = head();
+ if (headOperation != null) {
+ operation.earliestRunTime = Math.min(SystemClock.elapsedRealtime(),
+ headOperation.earliestRunTime - 1);
+ } else {
+ operation.earliestRunTime = SystemClock.elapsedRealtime();
+ }
+ }
+
+ // - if an operation with the same key exists and this one should run earlier,
+ // delete the old one and add the new one
+ // - if an operation with the same key exists and if this one should run
+ // later, ignore it
+ // - if no operation exists then add the new one
+ final String operationKey = operation.key;
+ SyncOperation existingOperation = mOpsByKey.get(operationKey);
+
+ // if this operation matches an existing operation that is being retried (delay > 0)
+ // and this operation isn't forced, ignore this operation
+ if (existingOperation != null && existingOperation.delay > 0) {
+ if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)) {
+ return false;
+ }
+ }
+
+ if (existingOperation != null
+ && operation.earliestRunTime >= existingOperation.earliestRunTime) {
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+ return false;
+ }
+
+ if (existingOperation != null) {
+ removeByKey(operationKey);
+ }
+
+ if (operation.rowId == null) {
+ byte[] extrasData = null;
+ Parcel parcel = Parcel.obtain();
+ try {
+ operation.extras.writeToParcel(parcel, 0);
+ extrasData = parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+ ContentValues values = new ContentValues();
+ values.put("account", operation.account);
+ values.put("authority", operation.authority);
+ values.put("source", operation.syncSource);
+ values.put("extras", extrasData);
+ Uri pendingUri = mSyncStorageEngine.insertIntoPending(values);
+ operation.rowId = pendingUri == null ? null : ContentUris.parseId(pendingUri);
+ if (operation.rowId == null) {
+ throw new IllegalStateException("error adding pending sync operation "
+ + operation);
+ }
+ }
+
+ if (DEBUG_CHECK_DATA_CONSISTENCY) {
+ debugCheckDataStructures(
+ false /* don't compare with the DB, since we know
+ it is inconsistent right now */ );
+ }
+ mOpsByKey.put(operationKey, operation);
+ mOpsByWhen.add(operation);
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+ return true;
+ }
+
+ public void removeByKey(String operationKey) {
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ SyncOperation operationToRemove = mOpsByKey.remove(operationKey);
+ if (!mOpsByWhen.remove(operationToRemove)) {
+ throw new IllegalStateException(
+ "unable to find " + operationToRemove + " in mOpsByWhen");
+ }
+
+ if (mSyncStorageEngine.deleteFromPending(operationToRemove.rowId) != 1) {
+ throw new IllegalStateException("unable to find pending row for "
+ + operationToRemove);
+ }
+
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ }
+
+ public SyncOperation head() {
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ return mOpsByWhen.peek();
+ }
+
+ public void popHead() {
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ SyncOperation operation = mOpsByWhen.remove();
+ if (mOpsByKey.remove(operation.key) == null) {
+ throw new IllegalStateException("unable to find " + operation + " in mOpsByKey");
+ }
+
+ if (mSyncStorageEngine.deleteFromPending(operation.rowId) != 1) {
+ throw new IllegalStateException("unable to find pending row for " + operation);
+ }
+
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ }
+
+ public void clear(String account, String authority) {
+ Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry<String, SyncOperation> entry = entries.next();
+ SyncOperation syncOperation = entry.getValue();
+ if (account != null && !syncOperation.account.equals(account)) continue;
+ if (authority != null && !syncOperation.authority.equals(authority)) continue;
+
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ entries.remove();
+ if (!mOpsByWhen.remove(syncOperation)) {
+ throw new IllegalStateException(
+ "unable to find " + syncOperation + " in mOpsByWhen");
+ }
+
+ if (mSyncStorageEngine.deleteFromPending(syncOperation.rowId) != 1) {
+ throw new IllegalStateException("unable to find pending row for "
+ + syncOperation);
+ }
+
+ if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+ }
+ }
+
+ public void dump(StringBuilder sb) {
+ sb.append("SyncQueue: ").append(mOpsByWhen.size()).append(" operation(s)\n");
+ for (SyncOperation operation : mOpsByWhen) {
+ sb.append(operation).append("\n");
+ }
+ }
+
+ private void debugCheckDataStructures(boolean checkDatabase) {
+ if (mOpsByKey.size() != mOpsByWhen.size()) {
+ throw new IllegalStateException("size mismatch: "
+ + mOpsByKey .size() + " != " + mOpsByWhen.size());
+ }
+ for (SyncOperation operation : mOpsByWhen) {
+ if (!mOpsByKey.containsKey(operation.key)) {
+ throw new IllegalStateException(
+ "operation " + operation + " is in mOpsByWhen but not mOpsByKey");
+ }
+ }
+ for (Map.Entry<String, SyncOperation> entry : mOpsByKey.entrySet()) {
+ final SyncOperation operation = entry.getValue();
+ final String key = entry.getKey();
+ if (!key.equals(operation.key)) {
+ throw new IllegalStateException(
+ "operation " + operation + " in mOpsByKey doesn't match key " + key);
+ }
+ if (!mOpsByWhen.contains(operation)) {
+ throw new IllegalStateException(
+ "operation " + operation + " is in mOpsByKey but not mOpsByWhen");
+ }
+ }
+
+ if (checkDatabase) {
+ // check that the DB contains the same rows as the in-memory data structures
+ Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS);
+ try {
+ if (mOpsByKey.size() != cursor.getCount()) {
+ StringBuilder sb = new StringBuilder();
+ DatabaseUtils.dumpCursor(cursor, sb);
+ sb.append("\n");
+ dump(sb);
+ throw new IllegalStateException("DB size mismatch: "
+ + mOpsByKey .size() + " != " + cursor.getCount() + "\n"
+ + sb.toString());
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ private SyncOperation cursorToOperation(Cursor cursor) {
+ byte[] extrasData = cursor.getBlob(COLUMN_EXTRAS);
+ Bundle extras;
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.unmarshall(extrasData, 0, extrasData.length);
+ parcel.setDataPosition(0);
+ extras = parcel.readBundle();
+ } catch (RuntimeException e) {
+ // A RuntimeException is thrown if we were unable to parse the parcel.
+ // Create an empty parcel in this case.
+ extras = new Bundle();
+ } finally {
+ parcel.recycle();
+ }
+
+ SyncOperation syncOperation = new SyncOperation(
+ cursor.getString(COLUMN_ACCOUNT),
+ cursor.getInt(COLUMN_SOURCE),
+ cursor.getString(COLUMN_AUTHORITY),
+ extras,
+ 0 /* delay */);
+ syncOperation.rowId = cursor.getLong(COLUMN_ID);
+ return syncOperation;
+ }
+ }
+}
diff --git a/core/java/android/content/SyncProvider.java b/core/java/android/content/SyncProvider.java
new file mode 100644
index 0000000..6ddd046
--- /dev/null
+++ b/core/java/android/content/SyncProvider.java
@@ -0,0 +1,53 @@
+// Copyright 2007 The Android Open Source Project
+package android.content;
+
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * ContentProvider that tracks the sync data and overall sync
+ * history on the device.
+ *
+ * @hide
+ */
+public class SyncProvider extends ContentProvider {
+ public SyncProvider() {
+ }
+
+ private SyncStorageEngine mSyncStorageEngine;
+
+ @Override
+ public boolean onCreate() {
+ mSyncStorageEngine = SyncStorageEngine.getSingleton();
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn,
+ String selection, String[] selectionArgs, String sort) {
+ return mSyncStorageEngine.query(url, projectionIn, selection, selectionArgs, sort);
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ return mSyncStorageEngine.insert(true /* the caller is the provider */,
+ url, initialValues);
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ return mSyncStorageEngine.delete(true /* the caller is the provider */,
+ url, where, whereArgs);
+ }
+
+ @Override
+ public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
+ return mSyncStorageEngine.update(true /* the caller is the provider */,
+ url, initialValues, where, whereArgs);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return mSyncStorageEngine.getType(url);
+ }
+}
diff --git a/core/java/android/content/SyncResult.aidl b/core/java/android/content/SyncResult.aidl
new file mode 100644
index 0000000..061b81c
--- /dev/null
+++ b/core/java/android/content/SyncResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable SyncResult;
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
new file mode 100644
index 0000000..f3260f3
--- /dev/null
+++ b/core/java/android/content/SyncResult.java
@@ -0,0 +1,178 @@
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to store information about the result of a sync
+ *
+ * @hide
+ */
+public final class SyncResult implements Parcelable {
+ public final boolean syncAlreadyInProgress;
+ public boolean tooManyDeletions;
+ public boolean tooManyRetries;
+ public boolean databaseError;
+ public boolean fullSyncRequested;
+ public boolean partialSyncUnavailable;
+ public boolean moreRecordsToGet;
+ public final SyncStats stats;
+ public static final SyncResult ALREADY_IN_PROGRESS;
+
+ static {
+ ALREADY_IN_PROGRESS = new SyncResult(true);
+ }
+
+ public SyncResult() {
+ this(false);
+ }
+
+ private SyncResult(boolean syncAlreadyInProgress) {
+ this.syncAlreadyInProgress = syncAlreadyInProgress;
+ this.tooManyDeletions = false;
+ this.tooManyRetries = false;
+ this.fullSyncRequested = false;
+ this.partialSyncUnavailable = false;
+ this.moreRecordsToGet = false;
+ 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;
+ stats = new SyncStats(parcel);
+ }
+
+ public boolean hasHardError() {
+ return stats.numParseExceptions > 0
+ || stats.numConflictDetectedExceptions > 0
+ || stats.numAuthExceptions > 0
+ || tooManyDeletions
+ || tooManyRetries
+ || databaseError;
+ }
+
+ public boolean hasSoftError() {
+ return syncAlreadyInProgress || stats.numIoExceptions > 0;
+ }
+
+ public boolean hasError() {
+ return hasSoftError() || hasHardError();
+ }
+
+ public boolean madeSomeProgress() {
+ return ((stats.numDeletes > 0) && !tooManyDeletions)
+ || stats.numInserts > 0
+ || stats.numUpdates > 0;
+ }
+
+ 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;
+ 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);
+ stats.writeToParcel(parcel, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" syncAlreadyInProgress: ").append(syncAlreadyInProgress);
+ sb.append(" tooManyDeletions: ").append(tooManyDeletions);
+ sb.append(" tooManyRetries: ").append(tooManyRetries);
+ sb.append(" databaseError: ").append(databaseError);
+ sb.append(" fullSyncRequested: ").append(fullSyncRequested);
+ sb.append(" partialSyncUnavailable: ").append(partialSyncUnavailable);
+ sb.append(" moreRecordsToGet: ").append(moreRecordsToGet);
+ sb.append(" stats: ").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/core/java/android/content/SyncStateContentProviderHelper.java b/core/java/android/content/SyncStateContentProviderHelper.java
new file mode 100644
index 0000000..f503e6f
--- /dev/null
+++ b/core/java/android/content/SyncStateContentProviderHelper.java
@@ -0,0 +1,234 @@
+/*
+ * 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.util.ArrayUtils;
+
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+
+/**
+ * Extends the schema of a ContentProvider to include the _sync_state table
+ * and implements query/insert/update/delete to access that table using the
+ * authority "syncstate". This can be used to store the sync state for a
+ * set of accounts.
+ *
+ * @hide
+ */
+public class SyncStateContentProviderHelper {
+ final SQLiteOpenHelper mOpenHelper;
+
+ private static final String SYNC_STATE_AUTHORITY = "syncstate";
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final int STATE = 0;
+
+ private static final Uri CONTENT_URI =
+ Uri.parse("content://" + SYNC_STATE_AUTHORITY + "/state");
+
+ private static final String ACCOUNT_WHERE = "_sync_account = ?";
+
+ private final Provider mInternalProviderInterface;
+
+ private static final String SYNC_STATE_TABLE = "_sync_state";
+ private static long DB_VERSION = 2;
+
+ private static final String[] ACCOUNT_PROJECTION = new String[]{"_sync_account"};
+
+ static {
+ sURIMatcher.addURI(SYNC_STATE_AUTHORITY, "state", STATE);
+ }
+
+ public SyncStateContentProviderHelper(SQLiteOpenHelper openHelper) {
+ mOpenHelper = openHelper;
+ mInternalProviderInterface = new Provider();
+ }
+
+ public ContentProvider asContentProvider() {
+ return mInternalProviderInterface;
+ }
+
+ public void createDatabase(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS _sync_state");
+ db.execSQL("CREATE TABLE _sync_state (" +
+ "_id INTEGER PRIMARY KEY," +
+ "_sync_account TEXT," +
+ "data TEXT," +
+ "UNIQUE(_sync_account)" +
+ ");");
+
+ db.execSQL("DROP TABLE IF EXISTS _sync_state_metadata");
+ db.execSQL("CREATE TABLE _sync_state_metadata (" +
+ "version INTEGER" +
+ ");");
+ ContentValues values = new ContentValues();
+ values.put("version", DB_VERSION);
+ db.insert("_sync_state_metadata", "version", values);
+ }
+
+ protected void onDatabaseOpened(SQLiteDatabase db) {
+ long version = DatabaseUtils.longForQuery(db,
+ "select version from _sync_state_metadata", null);
+ if (version != DB_VERSION) {
+ createDatabase(db);
+ }
+ }
+
+ class Provider extends ContentProvider {
+ public boolean onCreate() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ int match = sURIMatcher.match(url);
+ switch (match) {
+ case STATE:
+ return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs,
+ null, null, sortOrder);
+ default:
+ throw new UnsupportedOperationException("Cannot query URL: " + url);
+ }
+ }
+
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ public Uri insert(Uri url, ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int match = sURIMatcher.match(url);
+ switch (match) {
+ case STATE: {
+ long id = db.insert(SYNC_STATE_TABLE, "feed", values);
+ return CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build();
+ }
+ default:
+ throw new UnsupportedOperationException("Cannot insert into URL: " + url);
+ }
+ }
+
+ public int delete(Uri url, String userWhere, String[] whereArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (sURIMatcher.match(url)) {
+ case STATE:
+ return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs);
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+
+ }
+
+ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (sURIMatcher.match(url)) {
+ case STATE:
+ return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs);
+ default:
+ throw new UnsupportedOperationException("Cannot update URL: " + url);
+ }
+
+ }
+ }
+
+ /**
+ * Check if the url matches content that this ContentProvider manages.
+ * @param url the Uri to check
+ * @return true if this ContentProvider can handle that Uri.
+ */
+ public boolean matches(Uri url) {
+ return (SYNC_STATE_AUTHORITY.equals(url.getAuthority()));
+ }
+
+ /**
+ * Replaces the contents of the _sync_state table in the destination ContentProvider
+ * with the row that matches account, if any, in the source ContentProvider.
+ * <p>
+ * The ContentProviders must expose the _sync_state table as URI content://syncstate/state.
+ * @param dbSrc the database to read from
+ * @param dbDest the database to write to
+ * @param account the account of the row that should be copied over.
+ */
+ public void copySyncState(SQLiteDatabase dbSrc, SQLiteDatabase dbDest,
+ String account) {
+ final String[] whereArgs = new String[]{account};
+ Cursor c = dbSrc.query(SYNC_STATE_TABLE, new String[]{"_sync_account", "data"},
+ ACCOUNT_WHERE, whereArgs, null, null, null);
+ try {
+ if (c.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put("_sync_account", c.getString(0));
+ values.put("data", c.getBlob(1));
+ dbDest.replace(SYNC_STATE_TABLE, "_sync_account", values);
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ public void onAccountsChanged(String[] accounts) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null);
+ try {
+ while (c.moveToNext()) {
+ final String account = c.getString(0);
+ if (!ArrayUtils.contains(accounts, account)) {
+ db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account});
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ public void discardSyncData(SQLiteDatabase db, String account) {
+ if (account != null) {
+ db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account});
+ } else {
+ db.delete(SYNC_STATE_TABLE, null, null);
+ }
+ }
+
+ /**
+ * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+ */
+ public byte[] readSyncDataBytes(SQLiteDatabase db, String account) {
+ Cursor c = db.query(SYNC_STATE_TABLE, null, ACCOUNT_WHERE,
+ new String[]{account}, null, null, null);
+ try {
+ if (c.moveToFirst()) {
+ return c.getBlob(c.getColumnIndexOrThrow("data"));
+ }
+ } finally {
+ c.close();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the SyncData bytes for the given account. The bytes array may be null.
+ */
+ public void writeSyncDataBytes(SQLiteDatabase db, String account, byte[] data) {
+ ContentValues values = new ContentValues();
+ values.put("data", data);
+ db.update(SYNC_STATE_TABLE, values, ACCOUNT_WHERE, new String[]{account});
+ }
+}
diff --git a/core/java/android/content/SyncStats.aidl b/core/java/android/content/SyncStats.aidl
new file mode 100644
index 0000000..dff0ebf
--- /dev/null
+++ b/core/java/android/content/SyncStats.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable SyncStats;
diff --git a/core/java/android/content/SyncStats.java b/core/java/android/content/SyncStats.java
new file mode 100644
index 0000000..b561b05
--- /dev/null
+++ b/core/java/android/content/SyncStats.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.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * @hide
+ */
+public class SyncStats implements Parcelable {
+ public long numAuthExceptions;
+ public long numIoExceptions;
+ public long numParseExceptions;
+ public long numConflictDetectedExceptions;
+ public long numInserts;
+ public long numUpdates;
+ public long numDeletes;
+ public long numEntries;
+ 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("numAuthExceptions: ").append(numAuthExceptions);
+ sb.append(" numIoExceptions: ").append(numIoExceptions);
+ sb.append(" numParseExceptions: ").append(numParseExceptions);
+ sb.append(" numConflictDetectedExceptions: ").append(numConflictDetectedExceptions);
+ sb.append(" numInserts: ").append(numInserts);
+ sb.append(" numUpdates: ").append(numUpdates);
+ sb.append(" numDeletes: ").append(numDeletes);
+ sb.append(" numEntries: ").append(numEntries);
+ sb.append(" numSkippedEntries: ").append(numSkippedEntries);
+ return sb.toString();
+ }
+
+ 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/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
new file mode 100644
index 0000000..282f6e7
--- /dev/null
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -0,0 +1,758 @@
+package android.content;
+
+import android.Manifest;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.Sync;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * ContentProvider that tracks the sync data and overall sync
+ * history on the device.
+ *
+ * @hide
+ */
+public class SyncStorageEngine {
+ private static final String TAG = "SyncManager";
+
+ private static final String DATABASE_NAME = "syncmanager.db";
+ private static final int DATABASE_VERSION = 10;
+
+ private static final int STATS = 1;
+ private static final int STATS_ID = 2;
+ private static final int HISTORY = 3;
+ private static final int HISTORY_ID = 4;
+ private static final int SETTINGS = 5;
+ private static final int PENDING = 7;
+ private static final int ACTIVE = 8;
+ private static final int STATUS = 9;
+
+ private static final UriMatcher sURLMatcher =
+ new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final HashMap<String,String> HISTORY_PROJECTION_MAP;
+ private static final HashMap<String,String> PENDING_PROJECTION_MAP;
+ private static final HashMap<String,String> ACTIVE_PROJECTION_MAP;
+ private static final HashMap<String,String> STATUS_PROJECTION_MAP;
+
+ private final Context mContext;
+ private final SQLiteOpenHelper mOpenHelper;
+ private static SyncStorageEngine sSyncStorageEngine = null;
+
+ static {
+ sURLMatcher.addURI("sync", "stats", STATS);
+ sURLMatcher.addURI("sync", "stats/#", STATS_ID);
+ sURLMatcher.addURI("sync", "history", HISTORY);
+ sURLMatcher.addURI("sync", "history/#", HISTORY_ID);
+ sURLMatcher.addURI("sync", "settings", SETTINGS);
+ sURLMatcher.addURI("sync", "status", STATUS);
+ sURLMatcher.addURI("sync", "active", ACTIVE);
+ sURLMatcher.addURI("sync", "pending", PENDING);
+
+ HashMap<String,String> map;
+ PENDING_PROJECTION_MAP = map = new HashMap<String,String>();
+ map.put(Sync.History._ID, Sync.History._ID);
+ map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
+ map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
+
+ ACTIVE_PROJECTION_MAP = map = new HashMap<String,String>();
+ map.put(Sync.History._ID, Sync.History._ID);
+ map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
+ map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
+ map.put("startTime", "startTime");
+
+ HISTORY_PROJECTION_MAP = map = new HashMap<String,String>();
+ map.put(Sync.History._ID, "history._id as _id");
+ map.put(Sync.History.ACCOUNT, "stats.account as account");
+ map.put(Sync.History.AUTHORITY, "stats.authority as authority");
+ map.put(Sync.History.EVENT, Sync.History.EVENT);
+ map.put(Sync.History.EVENT_TIME, Sync.History.EVENT_TIME);
+ map.put(Sync.History.ELAPSED_TIME, Sync.History.ELAPSED_TIME);
+ map.put(Sync.History.SOURCE, Sync.History.SOURCE);
+ map.put(Sync.History.UPSTREAM_ACTIVITY, Sync.History.UPSTREAM_ACTIVITY);
+ map.put(Sync.History.DOWNSTREAM_ACTIVITY, Sync.History.DOWNSTREAM_ACTIVITY);
+ map.put(Sync.History.MESG, Sync.History.MESG);
+
+ STATUS_PROJECTION_MAP = map = new HashMap<String,String>();
+ map.put(Sync.Status._ID, "status._id as _id");
+ map.put(Sync.Status.ACCOUNT, "stats.account as account");
+ map.put(Sync.Status.AUTHORITY, "stats.authority as authority");
+ map.put(Sync.Status.TOTAL_ELAPSED_TIME, Sync.Status.TOTAL_ELAPSED_TIME);
+ map.put(Sync.Status.NUM_SYNCS, Sync.Status.NUM_SYNCS);
+ map.put(Sync.Status.NUM_SOURCE_LOCAL, Sync.Status.NUM_SOURCE_LOCAL);
+ map.put(Sync.Status.NUM_SOURCE_POLL, Sync.Status.NUM_SOURCE_POLL);
+ map.put(Sync.Status.NUM_SOURCE_SERVER, Sync.Status.NUM_SOURCE_SERVER);
+ map.put(Sync.Status.NUM_SOURCE_USER, Sync.Status.NUM_SOURCE_USER);
+ map.put(Sync.Status.LAST_SUCCESS_SOURCE, Sync.Status.LAST_SUCCESS_SOURCE);
+ map.put(Sync.Status.LAST_SUCCESS_TIME, Sync.Status.LAST_SUCCESS_TIME);
+ map.put(Sync.Status.LAST_FAILURE_SOURCE, Sync.Status.LAST_FAILURE_SOURCE);
+ map.put(Sync.Status.LAST_FAILURE_TIME, Sync.Status.LAST_FAILURE_TIME);
+ map.put(Sync.Status.LAST_FAILURE_MESG, Sync.Status.LAST_FAILURE_MESG);
+ map.put(Sync.Status.PENDING, Sync.Status.PENDING);
+ }
+
+ private static final String[] STATS_ACCOUNT_PROJECTION =
+ new String[] { Sync.Stats.ACCOUNT };
+
+ private static final int MAX_HISTORY_EVENTS_TO_KEEP = 5000;
+
+ private static final String SELECT_INITIAL_FAILURE_TIME_QUERY_STRING = ""
+ + "SELECT min(a) "
+ + "FROM ("
+ + " SELECT initialFailureTime AS a "
+ + " FROM status "
+ + " WHERE stats_id=? AND a IS NOT NULL "
+ + " UNION "
+ + " SELECT ? AS a"
+ + " )";
+
+ private SyncStorageEngine(Context context) {
+ mContext = context;
+ mOpenHelper = new SyncStorageEngine.DatabaseHelper(context);
+ sSyncStorageEngine = this;
+ }
+
+ public static SyncStorageEngine newTestInstance(Context context) {
+ return new SyncStorageEngine(context);
+ }
+
+ public static void init(Context context) {
+ if (sSyncStorageEngine != null) {
+ throw new IllegalStateException("already initialized");
+ }
+ sSyncStorageEngine = new SyncStorageEngine(context);
+ }
+
+ public static SyncStorageEngine getSingleton() {
+ if (sSyncStorageEngine == null) {
+ throw new IllegalStateException("not initialized");
+ }
+ return sSyncStorageEngine;
+ }
+
+ private class DatabaseHelper extends SQLiteOpenHelper {
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE pending ("
+ + "_id INTEGER PRIMARY KEY,"
+ + "authority TEXT NOT NULL,"
+ + "account TEXT NOT NULL,"
+ + "extras BLOB NOT NULL,"
+ + "source INTEGER NOT NULL"
+ + ");");
+
+ db.execSQL("CREATE TABLE stats (" +
+ "_id INTEGER PRIMARY KEY," +
+ "account TEXT, " +
+ "authority TEXT, " +
+ "syncdata TEXT, " +
+ "UNIQUE (account, authority)" +
+ ");");
+
+ db.execSQL("CREATE TABLE history (" +
+ "_id INTEGER PRIMARY KEY," +
+ "stats_id INTEGER," +
+ "eventTime INTEGER," +
+ "elapsedTime INTEGER," +
+ "source INTEGER," +
+ "event INTEGER," +
+ "upstreamActivity INTEGER," +
+ "downstreamActivity INTEGER," +
+ "mesg TEXT);");
+
+ db.execSQL("CREATE TABLE status ("
+ + "_id INTEGER PRIMARY KEY,"
+ + "stats_id INTEGER NOT NULL,"
+ + "totalElapsedTime INTEGER NOT NULL DEFAULT 0,"
+ + "numSyncs INTEGER NOT NULL DEFAULT 0,"
+ + "numSourcePoll INTEGER NOT NULL DEFAULT 0,"
+ + "numSourceServer INTEGER NOT NULL DEFAULT 0,"
+ + "numSourceLocal INTEGER NOT NULL DEFAULT 0,"
+ + "numSourceUser INTEGER NOT NULL DEFAULT 0,"
+ + "lastSuccessTime INTEGER,"
+ + "lastSuccessSource INTEGER,"
+ + "lastFailureTime INTEGER,"
+ + "lastFailureSource INTEGER,"
+ + "lastFailureMesg STRING,"
+ + "initialFailureTime INTEGER,"
+ + "pending INTEGER NOT NULL DEFAULT 0);");
+
+ db.execSQL("CREATE TABLE active ("
+ + "_id INTEGER PRIMARY KEY,"
+ + "authority TEXT,"
+ + "account TEXT,"
+ + "startTime INTEGER);");
+
+ db.execSQL("CREATE INDEX historyEventTime ON history (eventTime)");
+
+ db.execSQL("CREATE TABLE settings (" +
+ "name TEXT PRIMARY KEY," +
+ "value TEXT);");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion == 9 && newVersion == 10) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will preserve old data");
+ db.execSQL("ALTER TABLE status ADD COLUMN initialFailureTime INTEGER");
+ return;
+ }
+
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS pending");
+ db.execSQL("DROP TABLE IF EXISTS stats");
+ db.execSQL("DROP TABLE IF EXISTS history");
+ db.execSQL("DROP TABLE IF EXISTS settings");
+ db.execSQL("DROP TABLE IF EXISTS active");
+ db.execSQL("DROP TABLE IF EXISTS status");
+ onCreate(db);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ if (!db.isReadOnly()) {
+ db.delete("active", null, null);
+ db.insert("active", "account", null);
+ }
+ }
+ }
+
+ protected void doDatabaseCleanup(String[] accounts) {
+ HashSet<String> currentAccounts = new HashSet<String>();
+ for (String account : accounts) currentAccounts.add(account);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = db.query("stats", STATS_ACCOUNT_PROJECTION,
+ null /* where */, null /* where args */, Sync.Stats.ACCOUNT,
+ null /* having */, null /* order by */);
+ try {
+ while (cursor.moveToNext()) {
+ String account = cursor.getString(0);
+ if (TextUtils.isEmpty(account)) {
+ continue;
+ }
+ if (!currentAccounts.contains(account)) {
+ String where = Sync.Stats.ACCOUNT + "=?";
+ int numDeleted;
+ numDeleted = db.delete("stats", where, new String[]{account});
+ if (Config.LOGD) {
+ Log.d(TAG, "deleted " + numDeleted
+ + " records from stats table"
+ + " for account " + account);
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ protected void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+ if (activeSyncContext != null) {
+ updateActiveSync(activeSyncContext.mSyncOperation.account,
+ activeSyncContext.mSyncOperation.authority, activeSyncContext.mStartTime);
+ } else {
+ // we indicate that the sync is not active by passing null for all the parameters
+ updateActiveSync(null, null, null);
+ }
+ }
+
+ private int updateActiveSync(String account, String authority, Long startTime) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put("account", account);
+ values.put("authority", authority);
+ values.put("startTime", startTime);
+ int numChanges = db.update("active", values, null, null);
+ if (numChanges > 0) {
+ mContext.getContentResolver().notifyChange(Sync.Active.CONTENT_URI,
+ null /* this change wasn't made through an observer */);
+ }
+ return numChanges;
+ }
+
+ /**
+ * Implements the {@link ContentProvider#query} method
+ */
+ public Cursor query(Uri url, String[] projectionIn,
+ String selection, String[] selectionArgs, String sort) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ // Generate the body of the query
+ int match = sURLMatcher.match(url);
+ String groupBy = null;
+ switch (match) {
+ case STATS:
+ qb.setTables("stats");
+ break;
+ case STATS_ID:
+ qb.setTables("stats");
+ qb.appendWhere("_id=");
+ qb.appendWhere(url.getPathSegments().get(1));
+ break;
+ case HISTORY:
+ // join the stats and history tables, so the caller can get
+ // the account and authority information as part of this query.
+ qb.setTables("stats, history");
+ qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+ qb.appendWhere("stats._id = history.stats_id");
+ break;
+ case ACTIVE:
+ qb.setTables("active");
+ qb.setProjectionMap(ACTIVE_PROJECTION_MAP);
+ qb.appendWhere("account is not null");
+ break;
+ case PENDING:
+ qb.setTables("pending");
+ qb.setProjectionMap(PENDING_PROJECTION_MAP);
+ groupBy = "account, authority";
+ break;
+ case STATUS:
+ // join the stats and status tables, so the caller can get
+ // the account and authority information as part of this query.
+ qb.setTables("stats, status");
+ qb.setProjectionMap(STATUS_PROJECTION_MAP);
+ qb.appendWhere("stats._id = status.stats_id");
+ break;
+ case HISTORY_ID:
+ // join the stats and history tables, so the caller can get
+ // the account and authority information as part of this query.
+ qb.setTables("stats, history");
+ qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+ qb.appendWhere("stats._id = history.stats_id");
+ qb.appendWhere("AND history._id=");
+ qb.appendWhere(url.getPathSegments().get(1));
+ break;
+ case SETTINGS:
+ qb.setTables("settings");
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+
+ if (match == SETTINGS) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ } else {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+ "no permission to read the sync stats");
+ }
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor c = qb.query(db, projectionIn, selection, selectionArgs, groupBy, null, sort);
+ c.setNotificationUri(mContext.getContentResolver(), url);
+ return c;
+ }
+
+ /**
+ * Implements the {@link ContentProvider#insert} method
+ * @param callerIsTheProvider true if this is being called via the
+ * {@link ContentProvider#insert} in method rather than directly.
+ * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
+ * for the Settings table.
+ */
+ public Uri insert(boolean callerIsTheProvider, Uri url, ContentValues values) {
+ String table;
+ long rowID;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sURLMatcher.match(url);
+ checkCaller(callerIsTheProvider, match);
+ switch (match) {
+ case SETTINGS:
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ table = "settings";
+ rowID = db.replace(table, null, values);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+
+
+ if (rowID > 0) {
+ mContext.getContentResolver().notifyChange(url, null /* observer */);
+ return Uri.parse("content://sync/" + table + "/" + rowID);
+ }
+
+ return null;
+ }
+
+ private static void checkCaller(boolean callerIsTheProvider, int match) {
+ if (callerIsTheProvider && match != SETTINGS) {
+ throw new UnsupportedOperationException(
+ "only the settings are modifiable via the ContentProvider interface");
+ }
+ }
+
+ /**
+ * Implements the {@link ContentProvider#delete} method
+ * @param callerIsTheProvider true if this is being called via the
+ * {@link ContentProvider#delete} in method rather than directly.
+ * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
+ * for the Settings table.
+ */
+ public int delete(boolean callerIsTheProvider, Uri url, String where, String[] whereArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int match = sURLMatcher.match(url);
+
+ int numRows;
+ switch (match) {
+ case SETTINGS:
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ numRows = db.delete("settings", where, whereArgs);
+ break;
+ default:
+ throw new UnsupportedOperationException("Cannot delete URL: " + url);
+ }
+
+ if (numRows > 0) {
+ mContext.getContentResolver().notifyChange(url, null /* observer */);
+ }
+ return numRows;
+ }
+
+ /**
+ * Implements the {@link ContentProvider#update} method
+ * @param callerIsTheProvider true if this is being called via the
+ * {@link ContentProvider#update} in method rather than directly.
+ * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
+ * for the Settings table.
+ */
+ public int update(boolean callerIsTheProvider, Uri url, ContentValues initialValues,
+ String where, String[] whereArgs) {
+ switch (sURLMatcher.match(url)) {
+ case SETTINGS:
+ throw new UnsupportedOperationException("updating url " + url
+ + " is not allowed, use insert instead");
+ default:
+ throw new UnsupportedOperationException("Cannot update URL: " + url);
+ }
+ }
+
+ /**
+ * Implements the {@link ContentProvider#getType} method
+ */
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case SETTINGS:
+ return "vnd.android.cursor.dir/sync-settings";
+ default:
+ throw new IllegalArgumentException("Unknown URL");
+ }
+ }
+
+ protected Uri insertIntoPending(ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ try {
+ db.beginTransaction();
+ long rowId = db.insert("pending", Sync.Pending.ACCOUNT, values);
+ if (rowId < 0) return null;
+ String account = values.getAsString(Sync.Pending.ACCOUNT);
+ String authority = values.getAsString(Sync.Pending.AUTHORITY);
+
+ long statsId = createStatsRowIfNecessary(account, authority);
+ createStatusRowIfNecessary(statsId);
+
+ values.clear();
+ values.put(Sync.Status.PENDING, 1);
+ int numUpdatesStatus = db.update("status", values, "stats_id=" + statsId, null);
+
+ db.setTransactionSuccessful();
+
+ mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
+ null /* no observer initiated this change */);
+ if (numUpdatesStatus > 0) {
+ mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+ null /* no observer initiated this change */);
+ }
+ return ContentUris.withAppendedId(Sync.Pending.CONTENT_URI, rowId);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ int deleteFromPending(long rowId) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ String account;
+ String authority;
+ Cursor c = db.query("pending",
+ new String[]{Sync.Pending.ACCOUNT, Sync.Pending.AUTHORITY},
+ "_id=" + rowId, null, null, null, null);
+ try {
+ if (c.getCount() != 1) {
+ return 0;
+ }
+ c.moveToNext();
+ account = c.getString(0);
+ authority = c.getString(1);
+ } finally {
+ c.close();
+ }
+ db.delete("pending", "_id=" + rowId, null /* no where args */);
+ final String[] accountAuthorityWhereArgs = new String[]{account, authority};
+ boolean isPending = 0 < DatabaseUtils.longForQuery(db,
+ "SELECT COUNT(*) FROM PENDING WHERE account=? AND authority=?",
+ accountAuthorityWhereArgs);
+ if (!isPending) {
+ long statsId = createStatsRowIfNecessary(account, authority);
+ db.execSQL("UPDATE status SET pending=0 WHERE stats_id=" + statsId);
+ }
+ db.setTransactionSuccessful();
+
+ mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
+ null /* no observer initiated this change */);
+ if (!isPending) {
+ mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+ null /* no observer initiated this change */);
+ }
+ return 1;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ int clearPending() {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ int numChanges = db.delete("pending", null, null /* no where args */);
+ if (numChanges > 0) {
+ db.execSQL("UPDATE status SET pending=0");
+ mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
+ null /* no observer initiated this change */);
+ mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+ null /* no observer initiated this change */);
+ }
+ db.setTransactionSuccessful();
+ return numChanges;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Returns a cursor over all the pending syncs in no particular order. This cursor is not
+ * "live", in that if changes are made to the pending table any observers on this cursor
+ * will not be notified.
+ * @param projection Return only these columns. If null then all columns are returned.
+ * @return the cursor of pending syncs
+ */
+ public Cursor getPendingSyncsCursor(String[] projection) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ return db.query("pending", projection, null, null, null, null, null);
+ }
+
+ // @VisibleForTesting
+ static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
+
+ private boolean purgeOldHistoryEvents(long now) {
+ // remove events that are older than MILLIS_IN_4WEEKS
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int numDeletes = db.delete("history", "eventTime<" + (now - MILLIS_IN_4WEEKS), null);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (numDeletes > 0) {
+ Log.v(TAG, "deleted " + numDeletes + " old event(s) from the sync history");
+ }
+ }
+
+ // keep only the last MAX_HISTORY_EVENTS_TO_KEEP history events
+ numDeletes += db.delete("history", "eventTime < (select min(eventTime) from "
+ + "(select eventTime from history order by eventTime desc limit ?))",
+ new String[]{String.valueOf(MAX_HISTORY_EVENTS_TO_KEEP)});
+
+ return numDeletes > 0;
+ }
+
+ public long insertStartSyncEvent(String account, String authority, long now, int source) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long statsId = createStatsRowIfNecessary(account, authority);
+
+ purgeOldHistoryEvents(now);
+ ContentValues values = new ContentValues();
+ values.put(Sync.History.STATS_ID, statsId);
+ values.put(Sync.History.EVENT_TIME, now);
+ values.put(Sync.History.SOURCE, source);
+ values.put(Sync.History.EVENT, Sync.History.EVENT_START);
+ long rowId = db.insert("history", null, values);
+ mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI, null /* observer */);
+ mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, null /* observer */);
+ return rowId;
+ }
+
+ public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+ long downstreamActivity, long upstreamActivity) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ ContentValues values = new ContentValues();
+ values.put(Sync.History.ELAPSED_TIME, elapsedTime);
+ values.put(Sync.History.EVENT, Sync.History.EVENT_STOP);
+ values.put(Sync.History.MESG, resultMessage);
+ values.put(Sync.History.DOWNSTREAM_ACTIVITY, downstreamActivity);
+ values.put(Sync.History.UPSTREAM_ACTIVITY, upstreamActivity);
+
+ int count = db.update("history", values, "_id=?",
+ new String[]{Long.toString(historyId)});
+ // We think that count should always be 1 but don't want to change this until after
+ // launch.
+ if (count > 0) {
+ int source = (int) DatabaseUtils.longForQuery(db,
+ "SELECT source FROM history WHERE _id=" + historyId, null);
+ long eventTime = DatabaseUtils.longForQuery(db,
+ "SELECT eventTime FROM history WHERE _id=" + historyId, null);
+ long statsId = DatabaseUtils.longForQuery(db,
+ "SELECT stats_id FROM history WHERE _id=" + historyId, null);
+
+ createStatusRowIfNecessary(statsId);
+
+ // update the status table to reflect this sync
+ StringBuilder sb = new StringBuilder();
+ ArrayList<String> bindArgs = new ArrayList<String>();
+ sb.append("UPDATE status SET");
+ sb.append(" numSyncs=numSyncs+1");
+ sb.append(", totalElapsedTime=totalElapsedTime+" + elapsedTime);
+ switch (source) {
+ case Sync.History.SOURCE_LOCAL:
+ sb.append(", numSourceLocal=numSourceLocal+1");
+ break;
+ case Sync.History.SOURCE_POLL:
+ sb.append(", numSourcePoll=numSourcePoll+1");
+ break;
+ case Sync.History.SOURCE_USER:
+ sb.append(", numSourceUser=numSourceUser+1");
+ break;
+ case Sync.History.SOURCE_SERVER:
+ sb.append(", numSourceServer=numSourceServer+1");
+ break;
+ }
+
+ final String statsIdString = String.valueOf(statsId);
+ final long lastSyncTime = (eventTime + elapsedTime);
+ if (Sync.History.MESG_SUCCESS.equals(resultMessage)) {
+ // - if successful, update the successful columns
+ sb.append(", lastSuccessTime=" + lastSyncTime);
+ sb.append(", lastSuccessSource=" + source);
+ sb.append(", lastFailureTime=null");
+ sb.append(", lastFailureSource=null");
+ sb.append(", lastFailureMesg=null");
+ sb.append(", initialFailureTime=null");
+ } else if (!Sync.History.MESG_CANCELED.equals(resultMessage)) {
+ sb.append(", lastFailureTime=" + lastSyncTime);
+ sb.append(", lastFailureSource=" + source);
+ sb.append(", lastFailureMesg=?");
+ bindArgs.add(resultMessage);
+ long initialFailureTime = DatabaseUtils.longForQuery(db,
+ SELECT_INITIAL_FAILURE_TIME_QUERY_STRING,
+ new String[]{statsIdString, String.valueOf(lastSyncTime)});
+ sb.append(", initialFailureTime=" + initialFailureTime);
+ }
+ sb.append(" WHERE stats_id=?");
+ bindArgs.add(statsIdString);
+ db.execSQL(sb.toString(), bindArgs.toArray());
+ db.setTransactionSuccessful();
+ mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI,
+ null /* observer */);
+ mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
+ null /* observer */);
+ }
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * If sync is failing for any of the provider/accounts then determine the time at which it
+ * started failing and return the earliest time over all the provider/accounts. If none are
+ * failing then return 0.
+ */
+ public long getInitialSyncFailureTime() {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ // Join the settings for a provider with the status so that we can easily
+ // check if each provider is enabled for syncing. We also join in the overall
+ // enabled flag ("listen_for_tickles") to each row so that we don't need to
+ // make a separate DB lookup to access it.
+ Cursor c = db.rawQuery(""
+ + "SELECT initialFailureTime, s1.value, s2.value "
+ + "FROM status "
+ + "LEFT JOIN stats ON status.stats_id=stats._id "
+ + "LEFT JOIN settings as s1 ON 'sync_provider_' || authority=s1.name "
+ + "LEFT JOIN settings as s2 ON s2.name='listen_for_tickles' "
+ + "where initialFailureTime is not null "
+ + " AND lastFailureMesg!=" + Sync.History.ERROR_TOO_MANY_DELETIONS
+ + " AND lastFailureMesg!=" + Sync.History.ERROR_AUTHENTICATION
+ + " AND lastFailureMesg!=" + Sync.History.ERROR_SYNC_ALREADY_IN_PROGRESS
+ + " AND authority!='subscribedfeeds' "
+ + " ORDER BY initialFailureTime", null);
+ try {
+ while (c.moveToNext()) {
+ // these settings default to true, so if they are null treat them as enabled
+ final String providerEnabledString = c.getString(1);
+ if (providerEnabledString != null && !Boolean.parseBoolean(providerEnabledString)) {
+ continue;
+ }
+ final String allEnabledString = c.getString(2);
+ if (allEnabledString != null && !Boolean.parseBoolean(allEnabledString)) {
+ continue;
+ }
+ return c.getLong(0);
+ }
+ } finally {
+ c.close();
+ }
+ return 0;
+ }
+
+ private void createStatusRowIfNecessary(long statsId) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ boolean statusExists = 0 != DatabaseUtils.longForQuery(db,
+ "SELECT count(*) FROM status WHERE stats_id=" + statsId, null);
+ if (!statusExists) {
+ ContentValues values = new ContentValues();
+ values.put("stats_id", statsId);
+ db.insert("status", null, values);
+ }
+ }
+
+ private long createStatsRowIfNecessary(String account, String authority) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ StringBuilder where = new StringBuilder();
+ where.append(Sync.Stats.ACCOUNT + "= ?");
+ where.append(" and " + Sync.Stats.AUTHORITY + "= ?");
+ Cursor cursor = query(Sync.Stats.CONTENT_URI,
+ Sync.Stats.SYNC_STATS_PROJECTION,
+ where.toString(), new String[] { account, authority },
+ null /* order */);
+ try {
+ long id;
+ if (cursor.moveToFirst()) {
+ id = cursor.getLong(cursor.getColumnIndexOrThrow(Sync.Stats._ID));
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(Sync.Stats.ACCOUNT, account);
+ values.put(Sync.Stats.AUTHORITY, authority);
+ id = db.insert("stats", null, values);
+ }
+ return id;
+ } finally {
+ cursor.close();
+ }
+ }
+}
diff --git a/core/java/android/content/SyncUIContext.java b/core/java/android/content/SyncUIContext.java
new file mode 100644
index 0000000..6dde004
--- /dev/null
+++ b/core/java/android/content/SyncUIContext.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+/**
+ * Class with callback methods for SyncAdapters and ContentProviders
+ * that are called in response to the calls on SyncContext. This class
+ * is really only meant to be used by the Sync UI activities.
+ *
+ * <p>All of the onXXX callback methods here are called from a handler
+ * on the thread this object was created in.
+ *
+ * <p>This interface is unused. It should be removed.
+ *
+ * @hide
+ */
+@Deprecated
+public interface SyncUIContext {
+
+ void setStatusText(String text);
+
+ Context context();
+}
diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java
new file mode 100644
index 0000000..e0cd786
--- /dev/null
+++ b/core/java/android/content/SyncableContentProvider.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+
+import java.util.Map;
+
+/**
+ * A specialization of the ContentProvider that centralizes functionality
+ * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
+ * inside of database transactions.
+ *
+ * @hide
+ */
+public abstract class SyncableContentProvider extends ContentProvider {
+ protected abstract boolean isTemporary();
+
+ /**
+ * Close resources that must be closed. You must call this to properly release
+ * the resources used by the SyncableContentProvider.
+ */
+ public abstract void close();
+
+ /**
+ * Override to create your schema and do anything else you need to do with a new database.
+ * This is run inside a transaction (so you don't need to use one).
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ */
+ protected abstract void bootstrapDatabase(SQLiteDatabase db);
+
+ /**
+ * Override to upgrade your database from an old version to the version you specified.
+ * Don't set the DB version, this will automatically be done after the method returns.
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ *
+ * @param oldVersion version of the existing database
+ * @param newVersion current version to upgrade to
+ * @return true if the upgrade was lossless, false if it was lossy
+ */
+ protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
+
+ /**
+ * Override to do anything (like cleanups or checks) you need to do after opening a database.
+ * Does nothing by default. This is run inside a transaction (so you don't need to use one).
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ */
+ protected abstract void onDatabaseOpened(SQLiteDatabase db);
+
+ /**
+ * Get a non-persistent instance of this content provider.
+ * You must call {@link #close} on the returned
+ * SyncableContentProvider when you are done with it.
+ *
+ * @return a non-persistent content provider with the same layout as this
+ * provider.
+ */
+ public abstract SyncableContentProvider getTemporaryInstance();
+
+ public abstract SQLiteDatabase getDatabase();
+
+ public abstract boolean getContainsDiffs();
+
+ public abstract void setContainsDiffs(boolean containsDiffs);
+
+ /**
+ * Each subclass of this class should define a subclass of {@link
+ * AbstractTableMerger} for each table they wish to merge. It
+ * should then override this method and return one instance of
+ * each merger, in sequence. Their {@link
+ * AbstractTableMerger#merge merge} methods will be called, one at a
+ * time, in the order supplied.
+ *
+ * <p>The default implementation returns an empty list, so that no
+ * merging will occur.
+ * @return A sequence of subclasses of {@link
+ * AbstractTableMerger}, one for each table that should be merged.
+ */
+ protected abstract Iterable<? extends AbstractTableMerger> getMergers();
+
+ /**
+ * Check if changes to this URI can be syncable changes.
+ * @param uri the URI of the resource that was changed
+ * @return true if changes to this URI can be syncable changes, false otherwise
+ */
+ public abstract boolean changeRequiresLocalSync(Uri uri);
+
+ /**
+ * Called right before a sync is started.
+ *
+ * @param context the sync context for the operation
+ * @param account
+ */
+ public abstract void onSyncStart(SyncContext context, String account);
+
+ /**
+ * Called right after a sync is completed
+ *
+ * @param context the sync context for the operation
+ * @param success true if the sync succeeded, false if an error occurred
+ */
+ public abstract void onSyncStop(SyncContext context, boolean success);
+
+ /**
+ * The account of the most recent call to onSyncStart()
+ * @return the account
+ */
+ public abstract String getSyncingAccount();
+
+ /**
+ * Merge diffs from a sync source with this content provider.
+ *
+ * @param context the SyncContext within which this merge is taking place
+ * @param diffs A temporary content provider containing diffs from a sync
+ * source.
+ * @param result a MergeResult that contains information about the merge, including
+ * a temporary content provider with the same layout as this provider containing
+ * @param syncResult
+ */
+ public abstract void merge(SyncContext context, SyncableContentProvider diffs,
+ TempProviderSyncResult result, SyncResult syncResult);
+
+
+ /**
+ * Invoked when the active sync has been canceled. The default
+ * implementation doesn't do anything (except ensure that this
+ * provider is syncable). Subclasses of ContentProvider
+ * that support canceling of sync should override this.
+ */
+ public abstract void onSyncCanceled();
+
+
+ public abstract boolean isMergeCancelled();
+
+ /**
+ * Subclasses should override this instead of update(). See update()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract int updateInternal(Uri url, ContentValues values,
+ String selection, String[] selectionArgs);
+
+ /**
+ * Subclasses should override this instead of delete(). See delete()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
+
+ /**
+ * Subclasses should override this instead of insert(). See insert()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract Uri insertInternal(Uri url, ContentValues values);
+
+ /**
+ * Subclasses should override this instead of query(). See query()
+ * for details.
+ *
+ * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
+ * block for performance reasons. If an implementation needs atomic access
+ * to the database the lock can be acquired then.
+ */
+ protected abstract Cursor queryInternal(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder);
+
+ /**
+ * Make sure that there are no entries for accounts that no longer exist
+ * @param accountsArray the array of currently-existing accounts
+ */
+ protected abstract void onAccountsChanged(String[] accountsArray);
+
+ /**
+ * A helper method to delete all rows whose account is not in the accounts
+ * map. The accountColumnName is the name of the column that is expected
+ * to hold the account. If a row has an empty account it is never deleted.
+ *
+ * @param accounts a map of existing accounts
+ * @param table the table to delete from
+ * @param accountColumnName the name of the column that is expected
+ * to hold the account.
+ */
+ protected abstract void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
+ String table, String accountColumnName);
+
+ /**
+ * Called when the sync system determines that this provider should no longer
+ * contain records for the specified account.
+ */
+ public abstract void wipeAccount(String account);
+
+ /**
+ * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+ */
+ public abstract byte[] readSyncDataBytes(String account);
+
+ /**
+ * Sets the SyncData bytes for the given account. The bytes array may be null.
+ */
+ public abstract void writeSyncDataBytes(String account, byte[] data);
+}
+
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
new file mode 100644
index 0000000..eb3a5da
--- /dev/null
+++ b/core/java/android/content/TempProviderSyncAdapter.java
@@ -0,0 +1,550 @@
+package android.content;
+
+import android.database.SQLException;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.NetStat;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.TimingLogger;
+
+/**
+ * @hide
+ */
+public abstract class TempProviderSyncAdapter extends SyncAdapter {
+ private static final String TAG = "Sync";
+
+ private static final int MAX_GET_SERVER_DIFFS_LOOP_COUNT = 20;
+ private static final int MAX_UPLOAD_CHANGES_LOOP_COUNT = 10;
+ private static final int NUM_ALLOWED_SIMULTANEOUS_DELETIONS = 5;
+ private static final long PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS = 20;
+
+ private volatile SyncableContentProvider mProvider;
+ private volatile SyncThread mSyncThread = null;
+ private volatile boolean mProviderSyncStarted;
+ private volatile boolean mAdapterSyncStarted;
+
+ public TempProviderSyncAdapter(SyncableContentProvider provider) {
+ super();
+ mProvider = provider;
+ }
+
+ /**
+ * Used by getServerDiffs() to track the sync progress for a given
+ * sync adapter. Implementations of SyncAdapter generally specialize
+ * this class in order to track specific data about that SyncAdapter's
+ * sync. If an implementation of SyncAdapter doesn't need to store
+ * any data for a sync it may use TrivialSyncData.
+ */
+ public static abstract class SyncData implements Parcelable {
+
+ }
+
+ public final void setContext(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Retrieve the Context this adapter is running in. Only available
+ * once onSyncStarting() is called (not available from constructor).
+ */
+ final public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Called right before a sync is started.
+ *
+ * @param context allows you to publish status and interact with the
+ * @param account the account to sync
+ * @param forced if true then the sync was forced
+ * @param result information to track what happened during this sync attempt
+ * @return true, if the sync was successfully started. One reason it can
+ * fail to start is if there is no user configured on the device.
+ */
+ public abstract void onSyncStarting(SyncContext context, String account, boolean forced,
+ SyncResult result);
+
+ /**
+ * Called right after a sync is completed
+ *
+ * @param context allows you to publish status and interact with the
+ * user during interactive syncs.
+ * @param success true if the sync suceeded, false if an error occured
+ */
+ public abstract void onSyncEnding(SyncContext context, boolean success);
+
+ /**
+ * Implement this to return true if the data in your content provider
+ * is read only.
+ */
+ public abstract boolean isReadOnly();
+
+ /**
+ * Get diffs from the server since the last completed sync and put them
+ * into a temporary provider.
+ *
+ * @param context allows you to publish status and interact with the
+ * user during interactive syncs.
+ * @param syncData used to track the progress this client has made in syncing data
+ * from the server
+ * @param tempProvider this is where the diffs should be stored
+ * @param extras any extra data describing the sync that is desired
+ * @param syncInfo sync adapter-specific data that is used during a single sync operation
+ * @param syncResult information to track what happened during this sync attempt
+ */
+ public abstract void getServerDiffs(SyncContext context,
+ SyncData syncData, SyncableContentProvider tempProvider,
+ Bundle extras, Object syncInfo, SyncResult syncResult);
+
+ /**
+ * Send client diffs to the server, optionally receiving more diffs from the server
+ *
+ * @param context allows you to publish status and interact with the
+ * user during interactive syncs.
+ * @param clientDiffs the diffs from the client
+ * @param serverDiffs the SyncableContentProvider that should be populated with
+* the entries that were returned in response to an insert/update/delete request
+* to the server
+ * @param syncResult information to track what happened during this sync attempt
+ * @param dontActuallySendDeletes
+ */
+ public abstract void sendClientDiffs(SyncContext context,
+ SyncableContentProvider clientDiffs,
+ SyncableContentProvider serverDiffs, SyncResult syncResult,
+ boolean dontActuallySendDeletes);
+
+ /**
+ * Reads the sync data from the ContentProvider
+ * @param contentProvider the ContentProvider to read from
+ * @return the SyncData for the provider. This may be null.
+ */
+ public SyncData readSyncData(SyncableContentProvider contentProvider) {
+ return null;
+ }
+
+ /**
+ * Create and return a new, empty SyncData object
+ */
+ public SyncData newSyncData() {
+ return null;
+ }
+
+ /**
+ * Stores the sync data in the Sync Stats database, keying it by
+ * the account that was set in the last call to onSyncStarting()
+ */
+ public void writeSyncData(SyncData syncData, SyncableContentProvider contentProvider) {}
+
+ /**
+ * Indicate to the SyncAdapter that the last sync that was started has
+ * been cancelled.
+ */
+ public abstract void onSyncCanceled();
+
+ /**
+ * Initializes the temporary content providers used during
+ * {@link TempProviderSyncAdapter#sendClientDiffs}.
+ * May copy relevant data from the underlying db into this provider so
+ * joins, etc., can work.
+ *
+ * @param cp The ContentProvider to initialize.
+ */
+ protected void initTempProvider(SyncableContentProvider cp) {}
+
+ protected Object createSyncInfo() {
+ return null;
+ }
+
+ /**
+ * Called when the accounts list possibly changed, to give the
+ * SyncAdapter a chance to do any necessary bookkeeping, e.g.
+ * to make sure that any required SubscribedFeeds subscriptions
+ * exist.
+ * @param accounts the list of accounts
+ */
+ public abstract void onAccountsChanged(String[] accounts);
+
+ private Context mContext;
+
+ private class SyncThread extends Thread {
+ private final String mAccount;
+ private final Bundle mExtras;
+ private final SyncContext mSyncContext;
+ private volatile boolean mIsCanceled = false;
+ private long mInitialTxBytes;
+ private long mInitialRxBytes;
+ private final SyncResult mResult;
+
+ SyncThread(SyncContext syncContext, String account, Bundle extras) {
+ super("SyncThread");
+ mAccount = account;
+ mExtras = extras;
+ mSyncContext = syncContext;
+ mResult = new SyncResult();
+ }
+
+ void cancelSync() {
+ mIsCanceled = true;
+ if (mAdapterSyncStarted) onSyncCanceled();
+ if (mProviderSyncStarted) mProvider.onSyncCanceled();
+ // We may lose the last few sync events when canceling. Oh well.
+ int uid = Process.myUid();
+ logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
+ NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.myTid(),
+ Process.THREAD_PRIORITY_BACKGROUND);
+ int uid = Process.myUid();
+ mInitialTxBytes = NetStat.getUidTxBytes(uid);
+ mInitialRxBytes = NetStat.getUidRxBytes(uid);
+ try {
+ sync(mSyncContext, mAccount, mExtras);
+ } catch (SQLException e) {
+ Log.e(TAG, "Sync failed", e);
+ mResult.databaseError = true;
+ } finally {
+ mSyncThread = null;
+ if (!mIsCanceled) {
+ logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
+ NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+ mSyncContext.onFinished(mResult);
+ }
+ }
+ }
+
+ private void sync(SyncContext syncContext, String account, Bundle extras) {
+ mIsCanceled = false;
+
+ mProviderSyncStarted = false;
+ mAdapterSyncStarted = false;
+ String message = null;
+
+ boolean syncForced = extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
+
+ try {
+ mProvider.onSyncStart(syncContext, account);
+ mProviderSyncStarted = true;
+ onSyncStarting(syncContext, account, syncForced, mResult);
+ if (mResult.hasError()) {
+ message = "SyncAdapter failed while trying to start sync";
+ return;
+ }
+ mAdapterSyncStarted = true;
+ if (mIsCanceled) {
+ return;
+ }
+ final String syncTracingEnabledValue = SystemProperties.get(TAG + "Tracing");
+ final boolean syncTracingEnabled = !TextUtils.isEmpty(syncTracingEnabledValue);
+ try {
+ if (syncTracingEnabled) {
+ System.gc();
+ System.gc();
+ Debug.startMethodTracing("synctrace." + System.currentTimeMillis());
+ }
+ runSyncLoop(syncContext, account, extras);
+ } finally {
+ if (syncTracingEnabled) Debug.stopMethodTracing();
+ }
+ onSyncEnding(syncContext, !mResult.hasError());
+ mAdapterSyncStarted = false;
+ mProvider.onSyncStop(syncContext, true);
+ mProviderSyncStarted = false;
+ } finally {
+ if (mAdapterSyncStarted) {
+ mAdapterSyncStarted = false;
+ onSyncEnding(syncContext, false);
+ }
+ if (mProviderSyncStarted) {
+ mProviderSyncStarted = false;
+ mProvider.onSyncStop(syncContext, false);
+ }
+ if (!mIsCanceled) {
+ if (message != null) syncContext.setStatusText(message);
+ }
+ }
+ }
+
+ private void runSyncLoop(SyncContext syncContext, String account, Bundle extras) {
+ TimingLogger syncTimer = new TimingLogger(TAG + "Profiling", "sync");
+ syncTimer.addSplit("start");
+ int loopCount = 0;
+ boolean tooManyGetServerDiffsAttempts = false;
+
+ final boolean overrideTooManyDeletions =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS,
+ false);
+ final boolean discardLocalDeletions =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, false);
+ boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD,
+ false /* default this flag to false */);
+ SyncableContentProvider serverDiffs = null;
+ TempProviderSyncResult result = new TempProviderSyncResult();
+ try {
+ if (!uploadOnly) {
+ /**
+ * This loop repeatedly calls SyncAdapter.getServerDiffs()
+ * (to get changes from the feed) followed by
+ * ContentProvider.merge() (to incorporate these changes
+ * into the provider), stopping when the SyncData returned
+ * from getServerDiffs() indicates that all the data was
+ * fetched.
+ */
+ while (!mIsCanceled) {
+ // Don't let a bad sync go forever
+ if (loopCount++ == MAX_GET_SERVER_DIFFS_LOOP_COUNT) {
+ Log.e(TAG, "runSyncLoop: Hit max loop count while getting server diffs "
+ + getClass().getName());
+ // TODO: change the structure here to schedule a new sync
+ // with a backoff time, keeping track to be sure
+ // we don't keep doing this forever (due to some bug or
+ // mismatch between the client and the server)
+ tooManyGetServerDiffsAttempts = true;
+ break;
+ }
+
+ // Get an empty content provider to put the diffs into
+ if (serverDiffs != null) serverDiffs.close();
+ serverDiffs = mProvider.getTemporaryInstance();
+
+ // Get records from the server which will be put into the serverDiffs
+ initTempProvider(serverDiffs);
+ Object syncInfo = createSyncInfo();
+ SyncData syncData = readSyncData(serverDiffs);
+ // syncData will only be null if there was a demarshalling error
+ // while reading the sync data.
+ if (syncData == null) {
+ mProvider.wipeAccount(account);
+ syncData = newSyncData();
+ }
+ mResult.clear();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: running getServerDiffs using syncData "
+ + syncData.toString());
+ }
+ getServerDiffs(syncContext, syncData, serverDiffs, extras, syncInfo,
+ mResult);
+
+ if (mIsCanceled) return;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: result: " + mResult);
+ }
+ if (mResult.hasError()) return;
+ if (mResult.partialSyncUnavailable) {
+ if (Config.LOGD) {
+ Log.d(TAG, "partialSyncUnavailable is set, setting "
+ + "ignoreSyncData and retrying");
+ }
+ mProvider.wipeAccount(account);
+ continue;
+ }
+
+ // write the updated syncData back into the temp provider
+ writeSyncData(syncData, serverDiffs);
+
+ // apply the downloaded changes to the provider
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: running merge");
+ }
+ mProvider.merge(syncContext, serverDiffs,
+ null /* don't return client diffs */, mResult);
+ if (mIsCanceled) return;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: result: " + mResult);
+ }
+
+ // if the server has no more changes then break out of the loop
+ if (!mResult.moreRecordsToGet) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: fetched all data, moving on");
+ }
+ break;
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: more data to fetch, looping");
+ }
+ }
+ }
+
+ /**
+ * This loop repeatedly calls ContentProvider.merge() followed
+ * by SyncAdapter.merge() until either indicate that there is
+ * no more work to do by returning null.
+ * <p>
+ * The initial ContentProvider.merge() returns a temporary
+ * ContentProvider that contains any local changes that need
+ * to be committed to the server.
+ * <p>
+ * The SyncAdapter.merge() calls upload the changes to the server
+ * and populates temporary provider (the serverDiffs) with the
+ * result.
+ * <p>
+ * Subsequent calls to ContentProvider.merge() incoporate the
+ * result of previous SyncAdapter.merge() calls into the
+ * real ContentProvider and again return a temporary
+ * ContentProvider that contains any local changes that need
+ * to be committed to the server.
+ */
+ loopCount = 0;
+ boolean readOnly = isReadOnly();
+ long previousNumModifications = 0;
+ if (serverDiffs != null) {
+ serverDiffs.close();
+ serverDiffs = null;
+ }
+
+ // If we are discarding local deletions then we need to redownload all the items
+ // again (since some of them might have been deleted). We do this by deleting the
+ // sync data for the current account by writing in a null one.
+ if (discardLocalDeletions) {
+ serverDiffs = mProvider.getTemporaryInstance();
+ initTempProvider(serverDiffs);
+ writeSyncData(null, serverDiffs);
+ }
+
+ while (!mIsCanceled) {
+ if (Config.LOGV) {
+ Log.v(TAG, "runSyncLoop: Merging diffs from server to client");
+ }
+ if (result.tempContentProvider != null) {
+ result.tempContentProvider.close();
+ result.tempContentProvider = null;
+ }
+ mResult.clear();
+ mProvider.merge(syncContext, serverDiffs, readOnly ? null : result,
+ mResult);
+ if (mIsCanceled) return;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: result: " + mResult);
+ }
+
+ SyncableContentProvider clientDiffs =
+ readOnly ? null : result.tempContentProvider;
+ if (clientDiffs == null) {
+ // Nothing to commit back to the server
+ if (Config.LOGV) Log.v(TAG, "runSyncLoop: No client diffs");
+ break;
+ }
+
+ long numModifications = mResult.stats.numUpdates
+ + mResult.stats.numDeletes
+ + mResult.stats.numInserts;
+
+ // as long as we are making progress keep resetting the loop count
+ if (numModifications < previousNumModifications) {
+ loopCount = 0;
+ }
+ previousNumModifications = numModifications;
+
+ // Don't let a bad sync go forever
+ if (loopCount++ >= MAX_UPLOAD_CHANGES_LOOP_COUNT) {
+ Log.e(TAG, "runSyncLoop: Hit max loop count while syncing "
+ + getClass().getName());
+ mResult.tooManyRetries = true;
+ break;
+ }
+
+ if (!overrideTooManyDeletions && !discardLocalDeletions
+ && hasTooManyDeletions(mResult.stats)) {
+ if (Config.LOGD) {
+ Log.d(TAG, "runSyncLoop: Too many deletions were found in provider "
+ + getClass().getName() + ", not doing any more updates");
+ }
+ long numDeletes = mResult.stats.numDeletes;
+ mResult.stats.clear();
+ mResult.tooManyDeletions = true;
+ mResult.stats.numDeletes = numDeletes;
+ break;
+ }
+
+ if (Config.LOGV) Log.v(TAG, "runSyncLoop: Merging diffs from client to server");
+ if (serverDiffs != null) serverDiffs.close();
+ serverDiffs = clientDiffs.getTemporaryInstance();
+ initTempProvider(serverDiffs);
+ mResult.clear();
+ sendClientDiffs(syncContext, clientDiffs, serverDiffs, mResult,
+ discardLocalDeletions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: result: " + mResult);
+ }
+
+ if (!mResult.madeSomeProgress()) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: No data from client diffs merge");
+ }
+ break;
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: made some progress, looping");
+ }
+ }
+
+ // add in any status codes that we saved from earlier
+ mResult.tooManyRetries |= tooManyGetServerDiffsAttempts;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runSyncLoop: final result: " + mResult);
+ }
+ } finally {
+ // do this in the finally block to guarantee that is is set and not overwritten
+ if (discardLocalDeletions) {
+ mResult.fullSyncRequested = true;
+ }
+ if (serverDiffs != null) serverDiffs.close();
+ if (result.tempContentProvider != null) result.tempContentProvider.close();
+ syncTimer.addSplit("stop");
+ syncTimer.dumpToLog();
+ }
+ }
+ }
+
+ /**
+ * Logs details on the sync.
+ * Normally this will be overridden by a subclass that will provide
+ * provider-specific details.
+ *
+ * @param bytesSent number of bytes the sync sent over the network
+ * @param bytesReceived number of bytes the sync received over the network
+ * @param result The SyncResult object holding info on the sync
+ */
+ protected void logSyncDetails(long bytesSent, long bytesReceived, SyncResult result) {
+ EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
+ }
+
+ public void startSync(SyncContext syncContext, String account, Bundle extras) {
+ if (mSyncThread != null) {
+ syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
+ return;
+ }
+
+ mSyncThread = new SyncThread(syncContext, account, extras);
+ mSyncThread.start();
+ }
+
+ public void cancelSync() {
+ if (mSyncThread != null) {
+ mSyncThread.cancelSync();
+ }
+ }
+
+ protected boolean hasTooManyDeletions(SyncStats stats) {
+ long numEntries = stats.numEntries;
+ long numDeletedEntries = stats.numDeletes;
+
+ long percentDeleted = (numDeletedEntries == 0)
+ ? 0
+ : (100 * numDeletedEntries /
+ (numEntries + numDeletedEntries));
+ boolean tooManyDeletions =
+ (numDeletedEntries > NUM_ALLOWED_SIMULTANEOUS_DELETIONS)
+ && (percentDeleted > PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS);
+ return tooManyDeletions;
+ }
+}
diff --git a/core/java/android/content/TempProviderSyncResult.java b/core/java/android/content/TempProviderSyncResult.java
new file mode 100644
index 0000000..81f6f79
--- /dev/null
+++ b/core/java/android/content/TempProviderSyncResult.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Used to hold data returned from a given phase of a TempProviderSync.
+ * @hide
+ */
+public class TempProviderSyncResult {
+ /**
+ * An interface to a temporary content provider that contains
+ * the result of updates that were sent to the server. This
+ * provider must be merged into the permanent content provider.
+ * This may be null, which indicates that there is nothing to
+ * merge back into the content provider.
+ */
+ public SyncableContentProvider tempContentProvider;
+
+ public TempProviderSyncResult() {
+ tempContentProvider = null;
+ }
+}
diff --git a/core/java/android/content/UriMatcher.java b/core/java/android/content/UriMatcher.java
new file mode 100644
index 0000000..a98e6d5
--- /dev/null
+++ b/core/java/android/content/UriMatcher.java
@@ -0,0 +1,262 @@
+/*
+ * 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.regex.Pattern;
+
+/**
+Utility class to aid in matching URIs in content providers.
+
+<p>To use this class, build up a tree of UriMatcher objects.
+Typically, it looks something like this:
+<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();
+
+ 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>Then when you need to match match against a URI, call {@link #match}, providing
+the tokenized url you've been given, and the value you want if there isn't
+a match. 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 you'd otherwise need. Like this:
+<pre>
+ public String getType(String[] url)
+ {
+ int match = sURIMatcher.match(url, NO_MATCH);
+ 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(String[] url)
+ {
+ if (url.length >= 2) {
+ if (url[1].equals("people")) {
+ if (url.length == 2) {
+ return "vnd.android.cursor.dir/person";
+ } else if (url.length == 3) {
+ return "vnd.android.cursor.item/person";
+... snip ...
+ return "vnd.android.cursor.dir/snail-mail";
+ } else if (url.length == 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.
+ *
+ * @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 = path != null ? PATH_SPLIT_PATTERN.split(path) : null;
+ 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;
+ }
+
+ static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
+
+ /**
+ * 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 int li = uri.getPathSegments().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() : uri.getPathSegments().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/core/java/android/content/package.html b/core/java/android/content/package.html
new file mode 100644
index 0000000..dd5360f
--- /dev/null
+++ b/core/java/android/content/package.html
@@ -0,0 +1,650 @@
+<html>
+<head>
+<script type="text/javascript" src="http://www.corp.google.com/style/prettify.js"></script>
+<script src="http://www.corp.google.com/eng/techpubs/include/navbar.js" type="text/javascript"></script>
+</head>
+
+<body>
+
+<p>Contains classes for accessing and publishing data
+on the device. It includes three main categories of APIs:
+the {@link android.content.res.Resources Resources} for
+retrieving resource data associated with an application;
+{@link android.content.ContentProvider Content Providers} and
+{@link android.content.ContentResolver ContentResolver} for managing and
+publishing persistent data associated with an application; and
+the {@link android.content.pm.PackageManager Package Manager}
+for finding out information about the application packages installed
+on the device.</p>
+
+<p>In addition, the {@link android.content.Context Context} abstract class
+is a base API for pulling these pieces together, allowing you to access
+an application's resources and transfer data between applications.</p>
+
+<p>This package builds on top of the lower-level Android packages
+{@link android.database}, {@link android.text},
+{@link android.graphics.drawable}, {@link android.graphics},
+{@link android.os}, and {@link android.util}.</p>
+
+<ol>
+ <li> <a href="#Resources">Resources</a>
+ <ol>
+ <li> <a href="#ResourcesTerminology">Terminology</a>
+ <li> <a href="#ResourcesQuickStart">Examples</a>
+ <ol>
+ <li> <a href="#UsingSystemResources">Using System Resources</a>
+ <li> <a href="#StringResources">String Resources</a>
+ <li> <a href="#ColorResources">Color Resources</a>
+ <li> <a href="#DrawableResources">Drawable Resources</a>
+ <li> <a href="#LayoutResources">Layout Resources</a>
+ <li> <a href="#ReferencesToResources">References to Resources</a>
+ <li> <a href="#ReferencesToThemeAttributes">References to Theme Attributes</a>
+ <li> <a href="#StyleResources">Style Resources</a>
+ <li> <a href="#StylesInLayoutResources">Styles in Layout Resources</a>
+ </ol>
+ </ol>
+</ol>
+
+<a name="Resources"></a>
+<h2>Resources</h2>
+
+<p>This topic includes a terminology list associated with resources, and a series
+ of examples of using resources in code. For a complete guide on creating and
+ using resources, see the document on <a href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources
+ and Internationalization</a>. For a reference on the supported Android resource types,
+ see <a href="{@docRoot}guide/topics/resources/available-resources.html">Available Resource Types</a>.</p>
+<p>The Android resource system keeps track of all non-code
+ assets associated with an application. You use the
+ {@link android.content.res.Resources Resources} class to access your
+ application's resources; the Resources instance associated with your
+ application can generally be found through
+ {@link android.content.Context#getResources Context.getResources()}.</p>
+<p>An application's resources are compiled into the application
+binary at build time for you by the build system. To use a resource,
+you must install it correctly in the source tree and build your
+application. As part of the build process, Java symbols for each
+of the resources are generated that you can use in your source
+code -- this allows the compiler to verify that your application code matches
+up with the resources you defined.</p>
+
+<p>The rest of this section is organized as a tutorial on how to
+use resources in an application.</p>
+
+<a name="ResourcesTerminology"></a>
+<h3>Terminology</h3>
+
+<p>The resource system brings a number of different pieces together to
+form the final complete resource functionality. To help understand the
+overall system, here are some brief definitions of the core concepts and
+components you will encounter in using it:</p>
+
+<p><b>Asset</b>: A single blob of data associated with an application. This
+includes Java object files, graphics (such as PNG images), XML files, etc.
+These files are organized in a directory hierarchy that, during final packaging
+of the application, is bundled together into a single ZIP file.</p>
+
+<p><b>aapt</b>: The tool that generates the final ZIP file of application
+assets. In addition to collecting raw assets together, it also parses
+resource definitions into binary asset data.</p>
+
+<p><b>Resource Table</b>: A special asset that aapt generates for you,
+describing all of the resources contained in an application/package.
+This file is accessed for you by the Resources class; it is not touched
+directly by applications.</p>
+
+<p><b>Resource</b>: An entry in the Resource Table describing a single
+named value. Broadly, there are two types of resources: primitives and
+bags.</p>
+
+<p><b>Resource Identifier</b>: In the Resource Table all resources are
+identified by a unique integer number. In source code (resource descriptions,
+XML files, Java code) you can use symbolic names that stand as constants for
+the actual resource identifier integer.</p>
+
+<p><b>Primitive Resource</b>: All primitive resources can be written as a
+simple string, using formatting to describe a variety of primitive types
+included in the resource system: integers, colors, strings, references to
+other resources, etc. Complex resources, such as bitmaps and XML
+describes, are stored as a primitive string resource whose value is the path
+of the underlying Asset holding its actual data.</p>
+
+<p><b>Bag Resource</b>: A special kind of resource entry that, instead of a
+simple string, holds an arbitrary list of name/value pairs. Each name is
+itself a resource identifier, and each value can hold
+the same kinds of string formatted data as a normal resource. Bags also
+support inheritance: a bag can inherit the values from another bag, selectively
+replacing or extending them to generate its own contents.</p>
+
+<p><b>Kind</b>: The resource kind is a way to organize resource identifiers
+for various purposes. For example, drawable resources are used to
+instantiate Drawable objects, so their data is a primitive resource containing
+either a color constant or string path to a bitmap or XML asset. Other
+common resource kinds are string (localized string primitives), color
+(color primitives), layout (a string path to an XML asset describing a view
+layout), and style (a bag resource describing user interface attributes).
+There is also a standard "attr" resource kind, which defines the resource
+identifiers to be used for naming bag items and XML attributes</p>
+
+<p><b>Style</b>: The name of the resource kind containing bags that are used
+to supply a set of user interface attributes. For example, a TextView class may
+be given a style resource that defines its text size, color, and alignment.
+In a layout XML file, you associate a style with a bag using the "style"
+attribute, whose value is the name of the style resource.</p>
+
+<p><b>Style Class</b>: Specifies a related set of attribute resources.
+This data is not placed in the resource table itself, but used to generate
+Java constants that make it easier for you to retrieve values out of
+a style resource and/or XML tag's attributes. For example, the
+Android platform defines a "View" style class that
+contains all of the standard view attributes: padding, visibility,
+background, etc.; when View is inflated it uses this style class to
+retrieve those values from the XML file (at which point style and theme
+information is applied as approriate) and load them into its instance.</p>
+
+<p><b>Configuration</b>: For any particular resource identifier, there may be
+multiple different available values depending on the current configuration.
+The configuration includes the locale (language and country), screen
+orientation, screen density, etc. The current configuration is used to
+select which resource values are in effect when the resource table is
+loaded.</p>
+
+<p><b>Theme</b>: A standard style resource that supplies global
+attribute values for a particular context. For example, when writing a
+Activity the application developer can select a standard theme to use, such
+as the Theme.White or Theme.Black styles; this style supplies information
+such as the screen background image/color, default text color, button style,
+text editor style, text size, etc. When inflating a layout resource, most
+values for widgets (the text color, selector, background) if not explicitly
+set will come from the current theme; style and attribute
+values supplied in the layout can also assign their value from explicitly
+named values in the theme attributes if desired.</p>
+
+<p><b>Overlay</b>: A resource table that does not define a new set of resources,
+but instead replaces the values of resources that are in another resource table.
+Like a configuration, this is applied at load time
+to the resource data; it can add new configuration values (for example
+strings in a new locale), replace existing values (for example change
+the standard white background image to a "Hello Kitty" background image),
+and modify resource bags (for example change the font size of the Theme.White
+style to have an 18 pt font size). This is the facility that allows the
+user to select between different global appearances of their device, or
+download files with new appearances.</p>
+
+<a name="ResourcesQuickStart"></a>
+<h3>Examples</h3>
+
+<p>This section gives a few quick examples you can use to make your own resources.
+ For more details on how to define and use resources, see <a
+ href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources and
+ Internationalization</a>. </p>
+
+<a name="UsingSystemResources"></a>
+<h4>Using System Resources</h4>
+
+<p>Many resources included with the system are available to applications.
+All such resources are defined under the class "android.R". For example,
+you can display the standard application icon in a screen with the following
+code:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ requestScreenFeatures(FEATURE_BADGE_IMAGE);
+
+ super.onStart();
+
+ setBadgeResource(android.R.drawable.sym_def_app_icon);
+ }
+}
+</pre>
+
+<p>In a similar way, this code will apply to your screen the standard
+"green background" visual treatment defined by the system:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ super.onStart();
+
+ setTheme(android.R.style.Theme_Black);
+ }
+}
+</pre>
+
+<a name="StringResources"></a>
+<h4>String Resources</h4>
+
+<p>String resources are defined using an XML resource description syntax.
+The file or multiple files containing these resources can be given any name
+(as long as it has a .xml suffix) and placed at an appropriate location in
+the source tree for the desired configuration (locale/orientation/density).
+
+<p>Here is a simple resource file describing a few strings:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string id="mainLabel">Hello <u>th<ignore>e</ignore>re</u>, <i>you</i> <b>Activity</b>!</string>
+ <string id="back">Back</string>
+ <string id="clear">Clear</string>
+</resources>
+</pre>
+
+<p>Typically this file will be called "strings.xml", and must be placed
+in the <code>values</code> directory:</p>
+
+<pre>
+MyApp/res/values/strings.xml
+</pre>
+
+<p>The strings can now be retrieved by your application through the
+symbol specified in the "id" attribute:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ super.onStart();
+
+ String back = getResources().getString(R.string.back).toString();
+ back = getString(R.string.back).toString(); // synonym
+ }
+}
+</pre>
+
+<p>Unlike system resources, the resource symbol (the R class) we are using
+here comes from our own application's package, not android.R.</p>
+
+<p>Note that the "mainLabel" string is complex, including style information.
+To support this, the <code>getString()</code> method returns a
+<code>CharSequence</code> object that you can pass to a
+<code>TextView</code> to retain those style. This is why code
+must call <code>toString()</code> on the returned resource if it wants
+a raw string.</p>
+
+<a name="ColorResources"></a>
+<h4>Color Resources</h4>
+
+<p>Color resources are created in a way very similar to string resources,
+but with the <color> resource tag. The data for these resources
+must be a hex color constant of the form "#rgb", "#argb", "#rrggbb", or
+"#aarrggbb". The alpha channel is 0xff (or 0xf) for opaque and 0
+for transparent.</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color id="opaque_red">#ffff0000</color>
+ <color id="transparent_red">#80ff0000</color>
+ <color id="opaque_blue">#0000ff</color>
+ <color id="opaque_green">#0f0</color>
+</resources>
+</pre>
+
+<p>While color definitions could be placed in the same resource file
+as the previously shown string data, usually you will place the colors in
+their own file:</p>
+
+<pre>
+MyApp/res/values/colors.xml
+</pre>
+
+<p>The colors can now be retrieved by your application through the
+symbol specified in the "id" attribute:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ super.onStart();
+
+ int red = getResources().getColor(R.color.opaque_red);
+ }
+}
+</pre>
+
+<a name="DrawableResources"></a>
+<h4>Drawable Resources</h4>
+
+<p>For simple drawable resources, all you need to do is place your
+image in a special resource sub-directory called "drawable". Files here
+are things that can be handled by an implementation of the
+{@link android.graphics.drawable.Drawable Drawable} class, often bitmaps
+(such as PNG images) but also various kinds of XML descriptions
+for selectors, gradients, etc.</p>
+
+<p>The drawable files will be scanned by the
+resource tool, automatically generating a resource entry for each found.
+For example the file <code>res/drawable/<myimage>.<ext></code>
+will result in a resource symbol named "myimage" (without the extension). Note
+that these file names <em>must</em> be valid Java identifiers, and should
+have only lower-case letters.</p>
+
+<p>For example, to use your own custom image as a badge in a screen,
+you can place the image here:</p>
+
+<pre>
+MyApp/res/drawable/my_badge.png
+</pre>
+
+<p>The image can then be used in your code like this:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ requestScreenFeatures(FEATURE_BADGE_IMAGE);
+
+ super.onStart();
+
+ setBadgeResource(R.drawable.my_badge);
+ }
+}
+</pre>
+
+<p>For drawables that are a single solid color, you can also define them
+in a resource file very much like colors shown previously. The only
+difference is that here we use the <drawable> tag to create a
+drawable resource.</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <drawable id="opaque_red">#ffff0000</drawable>
+ <drawable id="transparent_red">#80ff0000</drawable>
+ <drawable id="opaque_blue">#0000ff</drawable>
+ <drawable id="opaque_green">#0f0</drawable>
+</resources>
+</pre>
+
+<p>These resource entries are often placed in the same resource file
+as color definitions:</p>
+
+<pre>
+MyApp/res/values/colors.xml
+</pre>
+
+<a name="LayoutResources"></a>
+<h4>Layout Resources</h4>
+
+<p>Layout resources describe a view hierarchy configuration that is
+generated at runtime. These resources are XML files placed in the
+resource directory "layout", and are how you should create the content
+views inside of your screen (instead of creating them by hand) so that
+they can be themed, styled, configured, and overlayed.</p>
+
+<p>Here is a simple layout resource consisting of a single view, a text
+editor:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text"
+ android:layout_width="fill-parent" android:layout_height="fill-parent"
+ android:text="Hello, World!" />
+</root>
+</pre>
+
+<p>To use this layout, it can be placed in a file like this:</p>
+
+<pre>
+MyApp/res/layout/my_layout.xml
+</pre>
+
+<p>The layout can then be instantiated in your screen like this:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ super.onStart();
+ setContentView(R.layout.my_layout);
+ }
+}
+</pre>
+
+<p>Note that there are a number of visual attributes that can be supplied
+to TextView (including textSize, textColor, and textStyle) that we did
+not define in the previous example; in such a sitation, the default values for
+those attributes come from the theme. If we want to customize them, we
+can supply them explicitly in the XML file:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text"
+ android:layout_width="fill_parent" android:layout_height="fill_parent"
+ <b>android:textSize="18" android:textColor="#008"</b>
+ android:text="Hello, World!" />
+</root>
+</pre>
+
+<p>However, usually these kinds of attributes (those being attributes that
+usually make sense to vary with theme or overlay) should be defined through
+the theme or separate style resource. Later we will see how this is done.</p>
+
+<a name="ReferencesToResources"></a>
+<h4>References to Resources</h4>
+
+<p>A value supplied in an attribute (or resource) can also be a reference to
+a resource. This is often used in layout files to supply strings (so they
+can be localized) and images (which exist in another file), though a reference
+can be do any resource type including colors and integers.</p>
+
+<p>For example, if we have the previously defined color resources, we can
+write a layout file that sets the text color size to be the value contained in
+one of those resources:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text"
+ android:layout_width="fill_parent" android:layout_height="fill_parent"
+ <b>android:textColor="@color/opaque_red"</b>
+ android:text="Hello, World!" />
+</root>
+</pre>
+
+<p>Note here the use of the '@' prefix to introduce a resource reference -- the
+text following that is the name of a resource in the form
+of <code>@[package:]type/name</code>. In this case we didn't need to specify
+the package because we are referencing a resource in our own package. To
+reference a system resource, you would need to write:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text"
+ android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:textColor="@<b>android:</b>color/opaque_red"
+ android:text="Hello, World!" />
+</root>
+</pre>
+
+<p>As another example, you should always use resource references when supplying
+strings in a layout file so that they can be localized:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text"
+ android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:textColor="@android:color/opaque_red"
+ android:text="@string/hello_world" />
+</root>
+</pre>
+
+<p>This facility can also be used to create references between resources.
+For example, we can create new drawable resources that are aliases for
+existing images:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <drawable id="my_background">@android:drawable/theme2_background</drawable>
+</resources>
+</pre>
+
+<a name="ReferencesToThemeAttributes"></a>
+<h4>References to Theme Attributes</h4>
+
+<p>Another kind of resource value allows you to reference the value of an
+attribute in the current theme. This attribute reference can <em>only</em>
+be used in style resources and XML attributes; it allows you to customize the
+look of UI elements by changing them to standard variations supplied by the
+current theme, instead of supplying more concrete values.</p>
+
+<p>As an example, we can use this in our layout to set the text color to
+one of the standard colors defined in the base system theme:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text"
+ android:layout_width="fill_parent" android:layout_height="fill_parent"
+ <b>android:textColor="?android:textDisabledColor"</b>
+ android:text="@string/hello_world" />
+</root>
+</pre>
+
+<p>Note that this is very similar to a resource reference, except we are using
+an '?' prefix instead of '@'. When you use this markup, you are supplying
+the name of an attribute resource that will be looked up in the theme --
+because the resource tool knows that an attribute resource is expected,
+you do not need to explicitly state the type (which would be
+<code>?android:attr/android:textDisabledColor</code>).</p>
+
+<p>Other than using this resource identifier to find the value in the
+theme instead of raw resources, the name syntax is identical to the '@' format:
+<code>?[package:]type/name</code> with the type here being optional.</p>
+
+<a name="StyleResources"></a>
+<h4>Style Resources</h4>
+
+<p>A style resource is a set of name/value pairs describing a group
+of related attributes. There are two main uses for these resources:
+defining overall visual themes, and describing a set of visual attributes
+to apply to a class in a layout resource. In this section we will look
+at their use to describe themes; later we will look at using them in
+conjunction with layouts.</p>
+
+<p>Like strings, styles are defined through a resource XML file. In the
+situation where we want to define a new theme, we can create a custom theme
+style that inherits from one of the standard system themes:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style id="Theme" parent="android:Theme.White">
+ <item id="android:foregroundColor">#FFF8D96F</item>
+ <item id="android:textColor">@color/opaque_blue</item>
+ <item id="android:textSelectedColor">?android:textColor</item>
+ </style>
+</resources>
+</pre>
+
+<p>Typically these resource definitions will be placed in a file
+called "styles.xml" , and must be placed in the <code>values</code>
+directory:</p>
+
+<pre>
+MyApp/res/values/styles.xml
+</pre>
+
+<p>Similar to how we previously used a system style for an Activity theme,
+you can apply this style to your Activity:</p>
+
+<pre class="prettyprint">
+public class MyActivity extends Activity
+{
+ public void onStart()
+ {
+ super.onStart();
+
+ setTheme(R.style.Theme);
+ }
+}
+</pre>
+
+<p>In the style resource shown here, we used the <code>parent</code>
+attribute to specify another style resource from which it inherits
+its values -- in this case the <code>Theme.White</code> system resource:</p>
+
+<pre>
+ <style id="Home" parent="android:Theme.White">
+ ...
+ </style>
+</pre>
+
+<p>Note, when doing this, that you must use the "android" prefix in front
+to tell the compiler the namespace to look in for the resource --
+the resources you are specifying here are in your application's namespace,
+not the system. This explicit namespace specification ensures that names
+the application uses will not accidentally conflict with those defined by
+the system.</p>
+
+<p>If you don't specify an explicit parent style, it will be inferred
+from the style name -- everything before the final '.' in the name of the
+style being defined is taken as the parent style name. Thus, to make
+another style in your application that inherits from this base Theme style,
+you can write:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style id="Theme.WhiteText">
+ <item id="android:foregroundColor">#FFFFFFFF</item>
+ <item id="android:textColor">?android:foregroundColor</item>
+ </style>
+</resources>
+</pre>
+
+<p>This results in the symbol <code>R.style.Theme_WhiteText</code> that
+can be used in Java just like we did with <code>R.style.Theme</code>
+above.</p>
+
+<a name="StylesInLayoutResources"></a>
+<h4>Styles in Layout Resources</h4>
+
+<p>Often you will have a number fo views in a layout that all use the same
+set of attributes, or want to allow resource overlays to modify the values of
+attributes. Style resources can be used for both of these purposes, to put
+attribute definitions in a single place that can be references by multiple
+XML tags and modified by overlays. To do this, you simply define a
+new style resource with the desired values:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style id="SpecialText">
+ <item id="android:textSize">18</item>
+ <item id="android:textColor">#008</item>
+ </style>
+</resources>
+</pre>
+
+<p>You can now apply this style to your TextView in the XML file:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <EditText id="text1" <b>style="@style/SpecialText"</b>
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:text="Hello, World!" />
+ <EditText id="text2" <b>style="@style/SpecialText"</b>
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:text="I love you all." />
+</root></pre>
+<h4> </h4>
+
+</body>
+</html>
+
diff --git a/core/java/android/content/pm/ActivityInfo.aidl b/core/java/android/content/pm/ActivityInfo.aidl
new file mode 100755
index 0000000..dd90302
--- /dev/null
+++ b/core/java/android/content/pm/ActivityInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable ActivityInfo;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
new file mode 100644
index 0000000..85d877a
--- /dev/null
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -0,0 +1,353 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+/**
+ * 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 {
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+ /**
+ * Options that have been set in the activity declaration in the
+ * manifest: {@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}.
+ */
+ public int flags;
+
+ /**
+ * 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>sensor</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_NOSENSOR = 5;
+ /**
+ * 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}.
+ */
+ public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ /**
+ * 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 being hidden/exposed.
+ * 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 font scaling factor. Set from the
+ * {@link android.R.attr#configChanges} attribute. This is
+ * not a core resource configutation, but a higher-level value, so its
+ * constant starts at the high bits.
+ */
+ public static final int CONFIG_FONT_SCALE = 0x40000000;
+
+ /**
+ * 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}, and
+ * {@link #CONFIG_ORIENTATION}. 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.
+ */
+ public int softInputMode;
+
+ public ActivityInfo() {
+ }
+
+ public ActivityInfo(ActivityInfo orig) {
+ super(orig);
+ theme = orig.theme;
+ launchMode = orig.launchMode;
+ permission = orig.permission;
+ taskAffinity = orig.taskAffinity;
+ targetActivity = orig.targetActivity;
+ flags = orig.flags;
+ screenOrientation = orig.screenOrientation;
+ configChanges = orig.configChanges;
+ softInputMode = orig.softInputMode;
+ }
+
+ /**
+ * 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;
+ }
+
+ public void dump(Printer pw, String prefix) {
+ super.dumpFront(pw, prefix);
+ pw.println(prefix + "permission=" + permission);
+ pw.println(prefix + "taskAffinity=" + taskAffinity
+ + " targetActivity=" + targetActivity);
+ pw.println(prefix + "launchMode=" + launchMode
+ + " flags=0x" + Integer.toHexString(flags)
+ + " theme=0x" + Integer.toHexString(theme));
+ pw.println(prefix + "screenOrientation=" + screenOrientation
+ + " configChanges=0x" + Integer.toHexString(configChanges)
+ + " softInputMode=0x" + Integer.toHexString(softInputMode));
+ super.dumpBack(pw, prefix);
+ }
+
+ 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.writeString(permission);
+ dest.writeString(taskAffinity);
+ dest.writeString(targetActivity);
+ dest.writeInt(flags);
+ dest.writeInt(screenOrientation);
+ dest.writeInt(configChanges);
+ dest.writeInt(softInputMode);
+ }
+
+ 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();
+ permission = source.readString();
+ taskAffinity = source.readString();
+ targetActivity = source.readString();
+ flags = source.readInt();
+ screenOrientation = source.readInt();
+ configChanges = source.readInt();
+ softInputMode = source.readInt();
+ }
+}
diff --git a/core/java/android/content/pm/ApplicationInfo.aidl b/core/java/android/content/pm/ApplicationInfo.aidl
new file mode 100755
index 0000000..006d1bd
--- /dev/null
+++ b/core/java/android/content/pm/ApplicationInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable ApplicationInfo;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
new file mode 100644
index 0000000..8d727ed
--- /dev/null
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -0,0 +1,310 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * 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
+ * application's dont specify it in their manifest
+ */
+ public String manageSpaceActivityName;
+
+ /**
+ * 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 iif 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}: default value for the corresponding ActivityInfo flag.
+ * {@hide}
+ */
+ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7;
+
+ /**
+ * 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}.
+ */
+ public int flags = 0;
+
+ /**
+ * Full path to the location of this package.
+ */
+ public String sourceDir;
+
+ /**
+ * Full path to the location of the publicly available parts of this package (i.e. the resources
+ * and manifest). For non-forward-locked apps this will be the same as {@link #sourceDir).
+ */
+ public String publicSourceDir;
+
+ /**
+ * 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 a directory assigned to the package for its persistent
+ * data.
+ */
+ public String dataDir;
+
+ /**
+ * 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;
+
+ /**
+ * When false, indicates that all components within this application are
+ * considered disabled, regardless of their individually set enabled status.
+ */
+ public boolean enabled = true;
+
+ public void dump(Printer pw, String prefix) {
+ super.dumpFront(pw, prefix);
+ pw.println(prefix + "className=" + className);
+ pw.println(prefix + "permission=" + permission
+ + " uid=" + uid);
+ pw.println(prefix + "taskAffinity=" + taskAffinity);
+ pw.println(prefix + "theme=0x" + Integer.toHexString(theme));
+ pw.println(prefix + "flags=0x" + Integer.toHexString(flags)
+ + " processName=" + processName);
+ pw.println(prefix + "sourceDir=" + sourceDir);
+ pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+ pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
+ pw.println(prefix + "dataDir=" + dataDir);
+ pw.println(prefix + "enabled=" + enabled);
+ pw.println(prefix+"manageSpaceActivityName="+manageSpaceActivityName);
+ pw.println(prefix+"description=0x"+Integer.toHexString(descriptionRes));
+ super.dumpBack(pw, prefix);
+ }
+
+ 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;
+ sourceDir = orig.sourceDir;
+ publicSourceDir = orig.publicSourceDir;
+ sharedLibraryFiles = orig.sharedLibraryFiles;
+ dataDir = orig.dataDir;
+ uid = orig.uid;
+ enabled = orig.enabled;
+ manageSpaceActivityName = orig.manageSpaceActivityName;
+ descriptionRes = orig.descriptionRes;
+ }
+
+
+ public String toString() {
+ return "ApplicationInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ 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.writeString(sourceDir);
+ dest.writeString(publicSourceDir);
+ dest.writeStringArray(sharedLibraryFiles);
+ dest.writeString(dataDir);
+ dest.writeInt(uid);
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeString(manageSpaceActivityName);
+ dest.writeInt(descriptionRes);
+ }
+
+ 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];
+ }
+ };
+
+ private ApplicationInfo(Parcel source) {
+ super(source);
+ taskAffinity = source.readString();
+ permission = source.readString();
+ processName = source.readString();
+ className = source.readString();
+ theme = source.readInt();
+ flags = source.readInt();
+ sourceDir = source.readString();
+ publicSourceDir = source.readString();
+ sharedLibraryFiles = source.readStringArray();
+ dataDir = source.readString();
+ uid = source.readInt();
+ enabled = source.readInt() != 0;
+ manageSpaceActivityName = source.readString();
+ descriptionRes = source.readInt();
+ }
+
+ /**
+ * Retrieve the textual description of the application. This
+ * will call back on the given PackageManager to load the description from
+ * the application.
+ *
+ * @param pm A PackageManager from which the label can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a CharSequence containing the application's description.
+ * If there is no description, null is returned.
+ */
+ public CharSequence loadDescription(PackageManager pm) {
+ if (descriptionRes != 0) {
+ CharSequence label = pm.getText(packageName, descriptionRes, null);
+ if (label != null) {
+ return label;
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
new file mode 100644
index 0000000..73c9244
--- /dev/null
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -0,0 +1,138 @@
+package android.content.pm;
+
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.util.Printer;
+
+/**
+ * Base class containing information common to all application components
+ * ({@link ActivityInfo}, {@link ServiceInfo}). This class is not intended
+ * to be used by itself; it is simply here to share common definitions
+ * between all application components. As such, it does not itself
+ * implement Parcelable, but does provide convenience methods to assist
+ * in the implementation of Parcelable in subclasses.
+ */
+public class ComponentInfo extends PackageItemInfo {
+ /**
+ * Global information about the application/package this component is a
+ * part of.
+ */
+ public ApplicationInfo applicationInfo;
+
+ /**
+ * The name of the process this component should run in.
+ * From the "android:process" attribute or, if not set, the same
+ * as <var>applicationInfo.processName</var>.
+ */
+ public String processName;
+
+ /**
+ * Indicates whether or not this component may be instantiated. Note that this value can be
+ * overriden 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;
+
+ public ComponentInfo() {
+ }
+
+ public ComponentInfo(ComponentInfo orig) {
+ super(orig);
+ applicationInfo = orig.applicationInfo;
+ processName = orig.processName;
+ enabled = orig.enabled;
+ exported = orig.exported;
+ }
+
+ @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;
+ }
+
+ @Override public Drawable loadIcon(PackageManager pm) {
+ ApplicationInfo ai = applicationInfo;
+ Drawable dr;
+ if (icon != 0) {
+ dr = pm.getDrawable(packageName, icon, ai);
+ if (dr != null) {
+ return dr;
+ }
+ }
+ if (ai.icon != 0) {
+ dr = pm.getDrawable(packageName, ai.icon, ai);
+ if (dr != null) {
+ return dr;
+ }
+ }
+ return pm.getDefaultActivityIcon();
+ }
+
+ /**
+ * 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;
+ }
+
+ protected void dumpFront(Printer pw, String prefix) {
+ super.dumpFront(pw, prefix);
+ pw.println(prefix + "enabled=" + enabled + " exported=" + exported
+ + " processName=" + processName);
+ }
+
+ protected void dumpBack(Printer pw, String prefix) {
+ if (applicationInfo != null) {
+ pw.println(prefix + "ApplicationInfo:");
+ applicationInfo.dump(pw, prefix + " ");
+ } else {
+ pw.println(prefix + "ApplicationInfo: null");
+ }
+ super.dumpBack(pw, prefix);
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ applicationInfo.writeToParcel(dest, parcelableFlags);
+ dest.writeString(processName);
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(exported ? 1 : 0);
+ }
+
+ protected ComponentInfo(Parcel source) {
+ super(source);
+ applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+ processName = source.readString();
+ enabled = (source.readInt() != 0);
+ exported = (source.readInt() != 0);
+ }
+}
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
new file mode 100755
index 0000000..dcc7463
--- /dev/null
+++ b/core/java/android/content/pm/ConfigurationInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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> 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;
+
+ public ConfigurationInfo() {
+ }
+
+ public ConfigurationInfo(ConfigurationInfo orig) {
+ reqTouchScreen = orig.reqTouchScreen;
+ reqKeyboardType = orig.reqKeyboardType;
+ reqNavigation = orig.reqNavigation;
+ reqInputFeatures = orig.reqInputFeatures;
+ }
+
+ public String toString() {
+ return "ApplicationHardwarePreferences{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + ", touchscreen = " + reqTouchScreen + "}"
+ + ", inputMethod = " + reqKeyboardType + "}"
+ + ", navigation = " + reqNavigation + "}"
+ + ", reqInputFeatures = " + reqInputFeatures + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(reqTouchScreen);
+ dest.writeInt(reqKeyboardType);
+ dest.writeInt(reqNavigation);
+ dest.writeInt(reqInputFeatures);
+ }
+
+ 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();
+ }
+}
diff --git a/core/java/android/content/pm/IPackageDataObserver.aidl b/core/java/android/content/pm/IPackageDataObserver.aidl
new file mode 100755
index 0000000..d010ee4
--- /dev/null
+++ b/core/java/android/content/pm/IPackageDataObserver.aidl
@@ -0,0 +1,28 @@
+/*
+**
+** Copyright 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;
+
+/**
+ * API for package data change related callbacks from the Package Manager.
+ * Some usage scenarios include deletion of cache directory, generate
+ * statistics related to code, data, cache usage(TODO)
+ * {@hide}
+ */
+oneway interface IPackageDataObserver {
+ void onRemoveCompleted(in String packageName, boolean succeeded);
+}
diff --git a/core/java/android/content/pm/IPackageDeleteObserver.aidl b/core/java/android/content/pm/IPackageDeleteObserver.aidl
new file mode 100644
index 0000000..bc16b3e
--- /dev/null
+++ b/core/java/android/content/pm/IPackageDeleteObserver.aidl
@@ -0,0 +1,28 @@
+/*
+**
+** Copyright 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;
+
+/**
+ * API for deletion callbacks from the Package Manager.
+ *
+ * {@hide}
+ */
+oneway interface IPackageDeleteObserver {
+ void packageDeleted(in boolean succeeded);
+}
+
diff --git a/core/java/android/content/pm/IPackageInstallObserver.aidl b/core/java/android/content/pm/IPackageInstallObserver.aidl
new file mode 100644
index 0000000..e83bbc6
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallObserver.aidl
@@ -0,0 +1,27 @@
+/*
+**
+** Copyright 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;
+
+/**
+ * API for installation callbacks from the Package Manager.
+ *
+ */
+oneway interface IPackageInstallObserver {
+ void packageInstalled(in String packageName, int returnCode);
+}
+
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
new file mode 100644
index 0000000..d3f6f3c5
--- /dev/null
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -0,0 +1,269 @@
+/*
+**
+** Copyright 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.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.app.PendingIntent;
+
+/**
+ * See {@link PackageManager} for documentation on most of the APIs
+ * here.
+ *
+ * {@hide}
+ */
+interface IPackageManager {
+ PackageInfo getPackageInfo(String packageName, int flags);
+ int getPackageUid(String packageName);
+ int[] getPackageGids(String packageName);
+
+ PermissionInfo getPermissionInfo(String name, int flags);
+
+ List<PermissionInfo> queryPermissionsByGroup(String group, int flags);
+
+ PermissionGroupInfo getPermissionGroupInfo(String name, int flags);
+
+ List<PermissionGroupInfo> getAllPermissionGroups(int flags);
+
+ ApplicationInfo getApplicationInfo(String packageName, int flags);
+
+ ActivityInfo getActivityInfo(in ComponentName className, int flags);
+
+ ActivityInfo getReceiverInfo(in ComponentName className, int flags);
+
+ ServiceInfo getServiceInfo(in ComponentName className, int flags);
+
+ int checkPermission(String permName, String pkgName);
+
+ int checkUidPermission(String permName, int uid);
+
+ boolean addPermission(in PermissionInfo info);
+
+ void removePermission(String name);
+
+ int checkSignatures(String pkg1, String pkg2);
+
+ String[] getPackagesForUid(int uid);
+
+ String getNameForUid(int uid);
+
+ int getUidForSharedUser(String sharedUserName);
+
+ ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags);
+
+ List<ResolveInfo> queryIntentActivities(in Intent intent,
+ String resolvedType, int flags);
+
+ List<ResolveInfo> queryIntentActivityOptions(
+ in ComponentName caller, in Intent[] specifics,
+ in String[] specificTypes, in Intent intent,
+ String resolvedType, int flags);
+
+ List<ResolveInfo> queryIntentReceivers(in Intent intent,
+ String resolvedType, int flags);
+
+ ResolveInfo resolveService(in Intent intent,
+ String resolvedType, int flags);
+
+ List<ResolveInfo> queryIntentServices(in Intent intent,
+ String resolvedType, int flags);
+
+ List<PackageInfo> getInstalledPackages(int flags);
+
+ List<ApplicationInfo> getInstalledApplications(int flags);
+
+ /**
+ * Retrieve all applications that are marked as persistent.
+ *
+ * @return A List<applicationInfo> containing one entry for each persistent
+ * application.
+ */
+ List<ApplicationInfo> getPersistentApplications(int flags);
+
+ ProviderInfo resolveContentProvider(String name, int flags);
+
+ /**
+ * Retrieve sync information for all content providers.
+ *
+ * @param outNames Filled in with a list of the root names of the content
+ * providers that can sync.
+ * @param outInfo Filled in with a list of the ProviderInfo for each
+ * name in 'outNames'.
+ */
+ void querySyncProviders(inout List<String> outNames,
+ inout List<ProviderInfo> outInfo);
+
+ List<ProviderInfo> queryContentProviders(
+ String processName, int uid, int flags);
+
+ InstrumentationInfo getInstrumentationInfo(
+ in ComponentName className, int flags);
+
+ List<InstrumentationInfo> queryInstrumentation(
+ String targetPackage, int flags);
+
+ /**
+ * Install a package.
+ *
+ * @param packageURI The location of the package file to install.
+ * @param observer a callback to use to notify when the package installation in finished.
+ * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE},
+ * {@link #REPLACE_EXISITING_PACKAGE}
+ */
+ void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags);
+
+ /**
+ * Delete a package.
+ *
+ * @param packageName The fully qualified name of the package to delete.
+ * @param observer a callback to use to notify when the package deletion in finished.
+ * @param flags - possible values: {@link #DONT_DELETE_DATA}
+ */
+ void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags);
+
+ void addPackageToPreferred(String packageName);
+
+ void removePackageFromPreferred(String packageName);
+
+ List<PackageInfo> getPreferredPackages(int flags);
+
+ void addPreferredActivity(in IntentFilter filter, int match,
+ in ComponentName[] set, in ComponentName activity);
+ void clearPackagePreferredActivities(String packageName);
+ int getPreferredActivities(out List<IntentFilter> outFilters,
+ out List<ComponentName> outActivities, String packageName);
+
+ /**
+ * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}.
+ */
+ void setComponentEnabledSetting(in ComponentName componentName,
+ in int newState, in int flags);
+
+ /**
+ * As per {@link android.content.pm.PackageManager#getComponentEnabledSetting}.
+ */
+ int getComponentEnabledSetting(in ComponentName componentName);
+
+ /**
+ * As per {@link android.content.pm.PackageManager#setApplicationEnabledSetting}.
+ */
+ void setApplicationEnabledSetting(in String packageName, in int newState, int flags);
+
+ /**
+ * As per {@link android.content.pm.PackageManager#getApplicationEnabledSetting}.
+ */
+ int getApplicationEnabledSetting(in String packageName);
+
+ /**
+ * 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
+ */
+ void freeStorageAndNotify(in 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 opFinishedIntent PendingIntent call back used to
+ * notify when the operation is completed.May be null
+ * to indicate that no call back is desired.
+ */
+ void freeStorage(in long freeStorageSize,
+ in PendingIntent opFinishedIntent);
+
+ /**
+ * Delete all the cache files in an applications cache directory
+ * @param packageName The package name of the application whose cache
+ * files need to be deleted
+ * @param observer a callback used to notify when the deletion is finished.
+ */
+ void deleteApplicationCacheFiles(in String packageName, IPackageDataObserver observer);
+
+ /**
+ * Clear the user data directory of an application.
+ * @param packageName The package name of the application whose cache
+ * files need to be deleted
+ * @param observer a callback used to notify when the operation is completed.
+ */
+ void clearApplicationUserData(in String packageName, IPackageDataObserver observer);
+
+ /**
+ * Get package statistics including the code, data and cache size for
+ * an already installed package
+ * @param packageName The package name of the application
+ * @param observer a callback to use to notify when the asynchronous
+ * retrieval of information is complete.
+ */
+ void getPackageSizeInfo(in String packageName, IPackageStatsObserver observer);
+
+ /**
+ * Get a list of shared libraries that are available on the
+ * system.
+ */
+ String[] getSystemSharedLibraryNames();
+
+ void enterSafeMode();
+ boolean isSafeMode();
+ void systemReady();
+ boolean hasSystemUidErrors();
+}
diff --git a/core/java/android/content/pm/IPackageStatsObserver.aidl b/core/java/android/content/pm/IPackageStatsObserver.aidl
new file mode 100755
index 0000000..ede4d1d
--- /dev/null
+++ b/core/java/android/content/pm/IPackageStatsObserver.aidl
@@ -0,0 +1,30 @@
+/*
+**
+** Copyright 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.pm.PackageStats;
+/**
+ * API for package data change related callbacks from the Package Manager.
+ * Some usage scenarios include deletion of cache directory, generate
+ * statistics related to code, data, cache usage(TODO)
+ * {@hide}
+ */
+oneway interface IPackageStatsObserver {
+
+ void onGetStatsCompleted(in PackageStats pStats, boolean succeeded);
+}
diff --git a/core/java/android/content/pm/InstrumentationInfo.aidl b/core/java/android/content/pm/InstrumentationInfo.aidl
new file mode 100755
index 0000000..3d847ae
--- /dev/null
+++ b/core/java/android/content/pm/InstrumentationInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable InstrumentationInfo;
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
new file mode 100644
index 0000000..30ca002
--- /dev/null
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -0,0 +1,98 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * 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;
+
+ /**
+ * Full path to the location of this package.
+ */
+ public String sourceDir;
+
+ /**
+ * Full path to the location of the publicly available parts of this package (i.e. the resources
+ * and manifest). For non-forward-locked apps this will be the same as {@link #sourceDir).
+ */
+ public String publicSourceDir;
+ /**
+ * Full path to a directory assigned to the package for its persistent
+ * data.
+ */
+ public String dataDir;
+
+ /**
+ * 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;
+ sourceDir = orig.sourceDir;
+ publicSourceDir = orig.publicSourceDir;
+ dataDir = orig.dataDir;
+ 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(sourceDir);
+ dest.writeString(publicSourceDir);
+ dest.writeString(dataDir);
+ 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];
+ }
+ };
+
+ private InstrumentationInfo(Parcel source) {
+ super(source);
+ targetPackage = source.readString();
+ sourceDir = source.readString();
+ publicSourceDir = source.readString();
+ dataDir = source.readString();
+ handleProfiling = source.readInt() != 0;
+ functionalTest = source.readInt() != 0;
+ }
+}
diff --git a/core/java/android/content/pm/PackageInfo.aidl b/core/java/android/content/pm/PackageInfo.aidl
new file mode 100755
index 0000000..35e2322
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable PackageInfo;
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
new file mode 100644
index 0000000..d9326f2
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -0,0 +1,199 @@
+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 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 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;
+
+ /**
+ * 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 all signatures read from the package file. This is only filled
+ * in if the flag {@link PackageManager#GET_SIGNATURES} was set.
+ */
+ 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;
+
+ public PackageInfo() {
+ }
+
+ public String toString() {
+ return "PackageInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString(packageName);
+ dest.writeInt(versionCode);
+ dest.writeString(versionName);
+ dest.writeString(sharedUserId);
+ dest.writeInt(sharedUserLabel);
+ if (applicationInfo != null) {
+ dest.writeInt(1);
+ applicationInfo.writeToParcel(dest, parcelableFlags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeIntArray(gids);
+ dest.writeTypedArray(activities, parcelableFlags);
+ dest.writeTypedArray(receivers, parcelableFlags);
+ dest.writeTypedArray(services, parcelableFlags);
+ dest.writeTypedArray(providers, parcelableFlags);
+ dest.writeTypedArray(instrumentation, parcelableFlags);
+ dest.writeTypedArray(permissions, parcelableFlags);
+ dest.writeStringArray(requestedPermissions);
+ dest.writeTypedArray(signatures, parcelableFlags);
+ dest.writeTypedArray(configPreferences, parcelableFlags);
+ }
+
+ public static final Parcelable.Creator<PackageInfo> CREATOR
+ = new Parcelable.Creator<PackageInfo>() {
+ public PackageInfo createFromParcel(Parcel source) {
+ return new PackageInfo(source);
+ }
+
+ public PackageInfo[] newArray(int size) {
+ return new PackageInfo[size];
+ }
+ };
+
+ private PackageInfo(Parcel source) {
+ packageName = source.readString();
+ versionCode = source.readInt();
+ versionName = source.readString();
+ sharedUserId = source.readString();
+ sharedUserLabel = source.readInt();
+ int hasApp = source.readInt();
+ if (hasApp != 0) {
+ applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
+ }
+ 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();
+ signatures = source.createTypedArray(Signature.CREATOR);
+ configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
+ }
+}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
new file mode 100644
index 0000000..46e7ca43
--- /dev/null
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -0,0 +1,191 @@
+package android.content.pm;
+
+import android.content.res.XmlResourceParser;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+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 {
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ public PackageItemInfo() {
+ }
+
+ public PackageItemInfo(PackageItemInfo orig) {
+ name = orig.name;
+ packageName = orig.packageName;
+ labelRes = orig.labelRes;
+ nonLocalizedLabel = orig.nonLocalizedLabel;
+ icon = orig.icon;
+ metaData = orig.metaData;
+ }
+
+ /**
+ * 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, null);
+ if (label != null) {
+ return label;
+ }
+ }
+ if(name != null) {
+ return name;
+ }
+ return packageName;
+ }
+
+ /**
+ * 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 default activity icon is returned.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ if (icon != 0) {
+ Drawable dr = pm.getDrawable(packageName, icon, null);
+ if (dr != null) {
+ return dr;
+ }
+ }
+ return pm.getDefaultActivityIcon();
+ }
+
+ /**
+ * 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, null);
+ }
+ }
+ return null;
+ }
+
+ protected void dumpFront(Printer pw, String prefix) {
+ pw.println(prefix + "name=" + name);
+ pw.println(prefix + "packageName=" + packageName);
+ pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
+ + " nonLocalizedLabel=" + nonLocalizedLabel
+ + " icon=0x" + Integer.toHexString(icon));
+ }
+
+ 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.writeBundle(metaData);
+ }
+
+ protected PackageItemInfo(Parcel source) {
+ name = source.readString();
+ packageName = source.readString();
+ labelRes = source.readInt();
+ nonLocalizedLabel
+ = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ icon = source.readInt();
+ metaData = source.readBundle();
+ }
+
+ 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/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
new file mode 100644
index 0000000..7287d9c
--- /dev/null
+++ b/core/java/android/content/pm/PackageManager.java
@@ -0,0 +1,1646 @@
+/*
+ * 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.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AndroidException;
+import android.util.DisplayMetrics;
+
+import java.io.File;
+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 {
+
+ /**
+ * This exception is thrown when a given package, application, or component
+ * name can not be found.
+ */
+ public static class NameNotFoundException extends AndroidException {
+ public NameNotFoundException() {
+ }
+
+ public NameNotFoundException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * {@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 an PackageInfo class, either
+ * directly or nested inside of another.
+ */
+ public static final int GET_GIDS = 0x00000100;
+
+ /**
+ * {@link PackageInfo} flag: include disabled components in the returned info.
+ */
+ public static final int GET_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 an 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;
+
+ /**
+ * Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
+ * This state could have resulted if applications have been deleted with flag
+ * DONT_DELETE_DATA
+ * with a possibility of being replaced or reinstalled in future
+ */
+ public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * hardware preferences
+ * {@link PackageInfo#configPreferences}
+ */
+ public static final int GET_CONFIGURATIONS = 0x00004000;
+
+ /**
+ * 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;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if the two packages have a matching signature.
+ */
+ 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 both packages are signed but there is no matching signature.
+ */
+ public static final int SIGNATURE_NO_MATCH = -3;
+
+ /**
+ * Signature check result: this is returned by {@link #checkSignatures}
+ * if either of the given package names are not valid.
+ */
+ public static final int SIGNATURE_UNKNOWN_PACKAGE = -4;
+
+ /**
+ * 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;
+
+ public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0;
+ public static final int COMPONENT_ENABLED_STATE_ENABLED = 1;
+ public static final int COMPONENT_ENABLED_STATE_DISABLED = 2;
+
+ /**
+ * Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to
+ * indicate that this package should be installed as forward locked, i.e. only the app itself
+ * should have access to it's code and non-resource assets.
+ */
+ public static final int FORWARD_LOCK_PACKAGE = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that you want to replace an already
+ * installed package, if one exists
+ */
+ public static final int REPLACE_EXISTING_PACKAGE = 0x00000002;
+
+ /**
+ * 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;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on success.
+ */
+ public static final int INSTALL_SUCCEEDED = 1;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package is
+ * already installed.
+ */
+ public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package archive
+ * file is invalid.
+ */
+ public static final int INSTALL_FAILED_INVALID_APK = -2;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the URI passed in
+ * is invalid.
+ */
+ public static final int INSTALL_FAILED_INVALID_URI = -3;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package manager
+ * service found that the device didn't have enough storage space to install the app
+ */
+ public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a
+ * package is already installed with the same name.
+ */
+ public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the requested shared user does not exist.
+ */
+ public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} 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).
+ */
+ public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package is requested a shared user which is already installed on the
+ * device and does not have matching signature.
+ */
+ public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package uses a shared library that is not available.
+ */
+ public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package uses a shared library that is not available.
+ */
+ public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package failed while optimizing and validating its dex files,
+ * either because there was not enough storage or the validation failed.
+ */
+ public static final int INSTALL_FAILED_DEXOPT = -11;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package failed because the current SDK version is older than
+ * that required by the package.
+ */
+ public static final int INSTALL_FAILED_OLDER_SDK = -12;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser was given a path that is not a file, or does not end with the expected
+ * '.apk' extension.
+ */
+ public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser was unable to retrieve the AndroidManifest.xml file.
+ */
+ public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser encountered an unexpected exception.
+ */
+ public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser did not find any certificates in the .apk.
+ */
+ public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser found inconsistent certificates on the files in the .apk.
+ */
+ public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser encountered a CertificateEncodingException in one of the
+ * files in the .apk.
+ */
+ public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser encountered a bad or missing package name in the manifest.
+ */
+ public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser encountered a bad shared user id name in the manifest.
+ */
+ public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser encountered some structural problem in the manifest.
+ */
+ public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
+
+ /**
+ * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the parser did not find any actionable tags (instrumentation or application)
+ * in the manifest.
+ */
+ public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
+
+ /**
+ * Indicates the state of installation. Used by PackageManager to
+ * figure out incomplete installations. Say a package is being installed
+ * (the state is set to PKG_INSTALL_INCOMPLETE) and remains so till
+ * the package installation is successful or unsuccesful lin which case
+ * the PackageManager will no longer maintain state information associated
+ * with the package. If some exception(like device freeze or battery being
+ * pulled out) occurs during installation of a package, the PackageManager
+ * needs this information to clean up the previously failed installation.
+ */
+ public static final int PKG_INSTALL_INCOMPLETE = 0;
+ public static final int PKG_INSTALL_COMPLETE = 1;
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
+ * package's data directory.
+ *
+ * @hide
+ */
+ public static final int DONT_DELETE_DATA = 0x00000001;
+
+ /**
+ * Retrieve overall information about an application package that is
+ * installed on the system.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return Returns a PackageInfo object containing information about the package.
+ * If flag GET_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 ie applications which had been
+ * deleted with DONT_DELTE_DATA flag set).
+ *
+ * @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SIGNATURES
+ * @see #GET_UNINSTALLED_PACKAGES
+ *
+ */
+ public abstract PackageInfo getPackageInfo(String packageName, int flags)
+ throws NameNotFoundException;
+
+ /**
+ * Return a "good" intent to launch a front-door activity in a package,
+ * for use for example to implement an "open" button when browsing through
+ * packages. The current implementation will look first for a main
+ * activity in the category {@link Intent#CATEGORY_INFO}, next for a
+ * main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
+ * null if neither are found.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The name of the package to inspect.
+ *
+ * @return Returns either a fully-qualified Intent that can be used to
+ * launch the main activity in the package, or null if the package does
+ * not contain such an activity.
+ */
+ public abstract Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Return an array of all of the secondary group-ids that have been
+ * assigned to a package.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @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.
+ */
+ public abstract int[] getPackageGids(String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular permission.
+ *
+ * <p>Throws {@link NameNotFoundException} if a permission with the given
+ * name can not be found on the system.
+ *
+ * @param name The fully qualified name (i.e. com.google.permission.LOGIN)
+ * of the permission you are interested in.
+ * @param flags Additional option flags. Use {@link #GET_META_DATA} to
+ * retrieve any meta-data associated with the permission.
+ *
+ * @return Returns a {@link PermissionInfo} containing information about the
+ * permission.
+ */
+ public abstract PermissionInfo getPermissionInfo(String name, int flags)
+ throws NameNotFoundException;
+
+ /**
+ * Query for all of the permissions associated with a particular group.
+ *
+ * <p>Throws {@link NameNotFoundException} if the given group does not
+ * exist.
+ *
+ * @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. Use {@link #GET_META_DATA} to
+ * retrieve any meta-data associated with the permissions.
+ *
+ * @return Returns a list of {@link PermissionInfo} containing information
+ * about all of the permissions in the given group.
+ */
+ public abstract List<PermissionInfo> queryPermissionsByGroup(String group,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular group of
+ * permissions.
+ *
+ * <p>Throws {@link NameNotFoundException} if a permission group with the given
+ * name can not be found on the system.
+ *
+ * @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. Use {@link #GET_META_DATA} to
+ * retrieve any meta-data associated with the permission group.
+ *
+ * @return Returns a {@link PermissionGroupInfo} containing information
+ * about the permission.
+ */
+ public abstract PermissionGroupInfo getPermissionGroupInfo(String name,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the known permission groups in the system.
+ *
+ * @param flags Additional option flags. Use {@link #GET_META_DATA} to
+ * retrieve any meta-data associated with the permission group.
+ *
+ * @return Returns a list of {@link PermissionGroupInfo} containing
+ * information about all of the known permission groups.
+ */
+ public abstract List<PermissionGroupInfo> getAllPermissionGroups(int flags);
+
+ /**
+ * Retrieve all of the information we know about a particular
+ * package/application.
+ *
+ * <p>Throws {@link NameNotFoundException} if an application with the given
+ * package name can not be found on the system.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of an
+ * application.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return {@link ApplicationInfo} Returns ApplicationInfo object containing
+ * information about the package.
+ * If flag GET_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 ie applications which had been
+ * deleted with DONT_DELTE_DATA flag set).
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ * @see #GET_UNINSTALLED_PACKAGES
+ */
+ public abstract ApplicationInfo getApplicationInfo(String packageName,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular activity
+ * class.
+ *
+ * <p>Throws {@link NameNotFoundException} if an activity with the given
+ * class name can not be found on the system.
+ *
+ * @param className The full name (i.e.
+ * com.google.apps.contacts.ContactsList) of an Activity
+ * class.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data (in ApplicationInfo) returned.
+ *
+ * @return {@link ActivityInfo} containing information about the activity.
+ *
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ */
+ public abstract ActivityInfo getActivityInfo(ComponentName className,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular receiver
+ * class.
+ *
+ * <p>Throws {@link NameNotFoundException} if a receiver with the given
+ * class name can not be found on the system.
+ *
+ * @param className The full name (i.e.
+ * com.google.apps.contacts.CalendarAlarm) of a Receiver
+ * class.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data returned.
+ *
+ * @return {@link ActivityInfo} containing information about the receiver.
+ *
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ */
+ public abstract ActivityInfo getReceiverInfo(ComponentName className,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular service
+ * class.
+ *
+ * <p>Throws {@link NameNotFoundException} if a service with the given
+ * class name can not be found on the system.
+ *
+ * @param className The full name (i.e.
+ * com.google.apps.media.BackgroundPlayback) of a Service
+ * class.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data returned.
+ *
+ * @return ServiceInfo containing information about the service.
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ */
+ public abstract ServiceInfo getServiceInfo(ComponentName className,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Return a List of all packages that are installed
+ * on the device.
+ *
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return A List of PackageInfo objects, one for each package that is
+ * installed on the device. In the unlikely case of there being no
+ * installed packages, an empty list is returned.
+ * If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+ * applications including those deleted with DONT_DELETE_DATA
+ * (partially installed apps with data directory) will be returned.
+ *
+ * @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SIGNATURES
+ * @see #GET_UNINSTALLED_PACKAGES
+ *
+ */
+ public abstract List<PackageInfo> getInstalledPackages(int flags);
+
+ /**
+ * 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
+ */
+ public abstract int checkPermission(String permName, String pkgName);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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 there is a matching
+ * signature: the value is >= 0 if there is a match (or neither package
+ * is signed), or < 0 if there is not a match. The match result can be
+ * further distinguished with the success (>= 0) constants
+ * {@link #SIGNATURE_MATCH}, {@link #SIGNATURE_NEITHER_SIGNED}; or
+ * failure (< 0) constants {@link #SIGNATURE_FIRST_NOT_SIGNED},
+ * {@link #SIGNATURE_SECOND_NOT_SIGNED}, {@link #SIGNATURE_NO_MATCH},
+ * or {@link #SIGNATURE_UNKNOWN_PACKAGE}.
+ *
+ * @see #SIGNATURE_MATCH
+ * @see #SIGNATURE_NEITHER_SIGNED
+ * @see #SIGNATURE_FIRST_NOT_SIGNED
+ * @see #SIGNATURE_SECOND_NOT_SIGNED
+ * @see #SIGNATURE_NO_MATCH
+ * @see #SIGNATURE_UNKNOWN_PACKAGE
+ */
+ public abstract int checkSignatures(String pkg1, String pkg2);
+
+ /**
+ * 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 String[] getPackagesForUid(int uid);
+
+ /**
+ * Retrieve the official name associated with a user id. This name is
+ * guaranteed to never change, though it is possibly for the underlying
+ * user id to be changed. That is, if you are storing information about
+ * user ids in persistent storage, you should use the string returned
+ * by this function instead of the raw user-id.
+ *
+ * @param uid The user id for which you would like to retrieve a name.
+ * @return Returns a unique name for the given user id, or null if the
+ * user id is not currently assigned.
+ */
+ public abstract String getNameForUid(int uid);
+
+ /**
+ * 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, or NameNotFoundException
+ * if the shared user name is not being used by any installed packages
+ * @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 DONT_DELETE_DATA(partially
+ * installed apps with data directory) will be returned.
+ *
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * {link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return A List of ApplicationInfo objects, one for each application that
+ * is installed on the device. In the unlikely case of there being
+ * no installed applications, an empty list is returned.
+ * If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+ * applications including those deleted with DONT_DELETE_DATA
+ * (partially installed apps with data directory) will be returned.
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ * @see #GET_UNINSTALLED_PACKAGES
+ */
+ public abstract List<ApplicationInfo> getInstalledApplications(int flags);
+
+ /**
+ * 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();
+
+ /**
+ * 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.
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags. The most important is
+ * MATCH_DEFAULT_ONLY, to limit the resolution to only
+ * those activities that support the CATEGORY_DEFAULT.
+ *
+ * @return Returns a ResolveInfo containing the final activity intent that
+ * was determined to be the best action. Returns null if no
+ * matching activity was found.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ */
+ public abstract ResolveInfo resolveActivity(Intent intent, int flags);
+
+ /**
+ * Retrieve all activities that can be performed for the given intent.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags. The most important is
+ * MATCH_DEFAULT_ONLY, to limit the resolution to only
+ * those activities that support the CATEGORY_DEFAULT.
+ *
+ * @return A List<ResolveInfo> containing one entry for each matching
+ * Activity. These are ordered from best to worst match -- that
+ * is, the first item in the list is what is returned by
+ * resolveActivity(). If there are no matching activities, an empty
+ * list is returned.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ */
+ public abstract List<ResolveInfo> queryIntentActivities(Intent intent,
+ int flags);
+
+ /**
+ * 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. The most important is
+ * MATCH_DEFAULT_ONLY, to limit the resolution to only
+ * those activities that support the CATEGORY_DEFAULT.
+ *
+ * @return A List<ResolveInfo> containing one entry for each matching
+ * Activity. These are 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.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ */
+ public abstract List<ResolveInfo> queryIntentActivityOptions(
+ ComponentName caller, Intent[] specifics, Intent intent, 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. The most important is
+ * MATCH_DEFAULT_ONLY, to limit the resolution to only
+ * those activities that support the CATEGORY_DEFAULT.
+ *
+ * @return A List<ResolveInfo> containing one entry for each matching
+ * Receiver. These are ordered from first to last in priority. If
+ * there are no matching receivers, an empty list is returned.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ */
+ public abstract List<ResolveInfo> queryBroadcastReceivers(Intent intent,
+ int flags);
+
+ /**
+ * 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.
+ *
+ * @return Returns a ResolveInfo containing the final service intent that
+ * was determined to be the best action. Returns null if no
+ * matching service was found.
+ *
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ */
+ public abstract ResolveInfo resolveService(Intent intent, int flags);
+
+ /**
+ * Retrieve all services that can match the given intent.
+ *
+ * @param intent The desired intent as per resolveService().
+ * @param flags Additional option flags.
+ *
+ * @return A List<ResolveInfo> containing one entry for each matching
+ * ServiceInfo. These are ordered from best to worst match -- that
+ * is, the first item in the list is what is returned by
+ * resolveService(). If there are no matching services, an empty
+ * list is returned.
+ *
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ */
+ public abstract List<ResolveInfo> queryIntentServices(Intent intent,
+ 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. Currently should always be 0.
+ *
+ * @return ContentProviderInfo Information about the provider, if found,
+ * else null.
+ */
+ public abstract ProviderInfo resolveContentProvider(String name,
+ int flags);
+
+ /**
+ * 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. Currently should always be 0.
+ *
+ * @return A List<ContentProviderInfo> containing one entry for each
+ * content provider either patching <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, int flags);
+
+ /**
+ * Retrieve all of the information we know about a particular
+ * instrumentation class.
+ *
+ * <p>Throws {@link NameNotFoundException} if instrumentation with the
+ * given class name can not be found on the system.
+ *
+ * @param className The full name (i.e.
+ * com.google.apps.contacts.InstrumentList) of an
+ * Instrumentation class.
+ * @param flags Additional option flags. Currently should always be 0.
+ *
+ * @return InstrumentationInfo containing information about the
+ * instrumentation.
+ */
+ public abstract InstrumentationInfo getInstrumentationInfo(
+ ComponentName className, 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. Currently should always be 0.
+ *
+ * @return A List<InstrumentationInfo> containing one entry for each
+ * matching available Instrumentation. Returns an empty list if
+ * there is no instrumentation available for the given package.
+ */
+ public abstract List<InstrumentationInfo> queryInstrumentation(
+ String targetPackage, 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.
+ * Can not be null.
+ * @param resid The resource identifier of the desired image. Can not 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, 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 can not 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() can not be found or the Intent can not 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;
+
+ /**
+ * 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 can not 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 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.
+ * Can not be null.
+ * @param resid The resource identifier of the desired text. Can not 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, 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.
+ * Can not be null.
+ * @param resid The resource identifier of the desired xml. Can not 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, 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
+ * can not 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 can not 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;
+
+ /**
+ * 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. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES}, to modify the data returned.
+ *
+ * @return Returns the information about the package. Returns
+ * null if the package could not be successfully parsed.
+ *
+ * @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SIGNATURES
+ *
+ */
+ public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
+ PackageParser packageParser = new PackageParser(archiveFilePath);
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ final File sourceFile = new File(archiveFilePath);
+ PackageParser.Package pkg = packageParser.parsePackage(
+ sourceFile, archiveFilePath, metrics, 0);
+ if (pkg == null) {
+ return null;
+ }
+ return PackageParser.generatePackageInfo(pkg, null, flags);
+ }
+
+ /**
+ * Install a package. Since this may take a little while, the result will
+ * be posted back to the given observer. An installation will fail if the calling context
+ * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
+ * package named in the package file's manifest is already installed, or if there's no space
+ * available on the device.
+ *
+ * @param packageURI The location of the package file to install. This can be a 'file:' or a
+ * 'content:' URI.
+ * @param observer An observer callback to get notified when the package installation is
+ * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
+ * called when that happens. observer may be null to indicate that no callback is desired.
+ * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE},
+ * {@link #REPLACE_EXISTING_PACKAGE}
+ *
+ * @see #installPackage(android.net.Uri)
+ */
+ public abstract void installPackage(
+ Uri packageURI, IPackageInstallObserver observer, 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 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".
+ * (TODO: include pointer to documentation on "system packages")
+ *
+ * @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(boolean)} will be
+ * called when that happens. observer may be null to indicate that no callback is desired.
+ * @param flags - possible values: {@link #DONT_DELETE_DATA}
+ *
+ * @hide
+ */
+ public abstract void deletePackage(
+ String packageName, IPackageDeleteObserver observer, int flags);
+ /**
+ * 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);
+
+ /**
+ * 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 abstract void freeStorageAndNotify(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 opFinishedIntent PendingIntent call back used to
+ * notify when the operation is completed.May be null
+ * to indicate that no call back is desired.
+ *
+ * @hide
+ */
+ public abstract void freeStorage(long freeStorageSize, PendingIntent opFinishedIntent);
+
+ /**
+ * 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 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.
+ *
+ * @hide
+ */
+ public abstract void getPackageSizeInfo(String packageName,
+ IPackageStatsObserver observer);
+
+ /**
+ * Install a package.
+ *
+ * @param packageURI The location of the package file to install
+ *
+ * @see #installPackage(android.net.Uri, IPackageInstallObserver, int)
+ */
+ public void installPackage(Uri packageURI) {
+ installPackage(packageURI, null, 0);
+ }
+
+ /**
+ * Add a new package to the list of preferred packages. This new package
+ * will be added to the front of the list (removed from its current location
+ * if already listed), meaning it will now be preferred over all other
+ * packages when resolving conflicts.
+ *
+ * @param packageName The package name of the new package to make preferred.
+ */
+ public abstract void addPackageToPreferred(String packageName);
+
+ /**
+ * Remove a package from the list of preferred packages. If it was on
+ * the list, it will no longer be preferred over other packages.
+ *
+ * @param packageName The package name to remove.
+ */
+ 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. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES}, to modify the data returned.
+ *
+ * @return Returns a list of PackageInfo objects describing each
+ * preferred application, in order of preference.
+ *
+ * @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SIGNATURES
+ */
+ public abstract List<PackageInfo> getPreferredPackages(int flags);
+
+ /**
+ * Add a new preferred activity mapping to the system. This will be used
+ * to automatically select the given activity component when
+ * {@link Context#startActivity(Intent) Context.startActivity()} finds
+ * multiple matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be
+ * made preferred.
+ * @param match The IntentFilter match category that this preference
+ * applies to.
+ * @param set The set of activities that the user was picking from when
+ * this preference was made.
+ * @param activity The component name of the activity that is to be
+ * preferred.
+ */
+ public abstract void addPreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity);
+
+ /**
+ * Remove all preferred activity mappings, previously added with
+ * {@link #addPreferredActivity}, from the
+ * system whose activities are implemented in the given package name.
+ *
+ * @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 list in which to place the filters of all of the
+ * preferred activities, or null for none.
+ * @param outActivities A list in which to place the component names of
+ * all of the preferred activities, or null for none.
+ * @param packageName An option 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(List<IntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName);
+
+ /**
+ * 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. The legal values for this state
+ * are:
+ * {@link #COMPONENT_ENABLED_STATE_ENABLED},
+ * {@link #COMPONENT_ENABLED_STATE_DISABLED}
+ * and
+ * {@link #COMPONENT_ENABLED_STATE_DEFAULT}
+ * The last one removes the setting, thereby restoring the component's state to
+ * whatever was set in it's manifest (or enabled, by default).
+ * @param flags Optional behavior flags: {@link #DONT_KILL_APP} or 0.
+ */
+ public abstract void setComponentEnabledSetting(ComponentName componentName,
+ int newState, int flags);
+
+
+ /**
+ * Return the 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. May
+ * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED},
+ * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or
+ * {@link #COMPONENT_ENABLED_STATE_DEFAULT}. The last one means the
+ * component's enabled state is based on the original information in
+ * the manifest as found in {@link ComponentInfo}.
+ */
+ public abstract 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 component. The legal values for this state
+ * are:
+ * {@link #COMPONENT_ENABLED_STATE_ENABLED},
+ * {@link #COMPONENT_ENABLED_STATE_DISABLED}
+ * and
+ * {@link #COMPONENT_ENABLED_STATE_DEFAULT}
+ * The last one removes the setting, thereby restoring the applications's state to
+ * whatever was set in its manifest (or enabled, by default).
+ * @param flags Optional behavior flags: {@link #DONT_KILL_APP} or 0.
+ */
+ public abstract void setApplicationEnabledSetting(String packageName,
+ int newState, int flags);
+
+ /**
+ * Return the 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 component to retrieve.
+ * @return Returns the current enabled state for the component. May
+ * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED},
+ * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or
+ * {@link #COMPONENT_ENABLED_STATE_DEFAULT}. The last one means the
+ * application's enabled state is based on the original information in
+ * the manifest as found in {@link ComponentInfo}.
+ */
+ public abstract int getApplicationEnabledSetting(String packageName);
+
+ /**
+ * Return whether the device has been booted into safe mode.
+ */
+ public abstract boolean isSafeMode();
+}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
new file mode 100644
index 0000000..4ae8b08
--- /dev/null
+++ b/core/java/android/content/pm/PackageParser.java
@@ -0,0 +1,2352 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.Bundle;
+import android.os.PatternMatcher;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import com.android.internal.util.XmlUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Package archive parsing
+ *
+ * {@hide}
+ */
+public class PackageParser {
+
+ private String mArchiveSourcePath;
+ private String[] mSeparateProcesses;
+ private int mSdkVersion;
+
+ private int mParseError = PackageManager.INSTALL_SUCCEEDED;
+
+ private static final Object mSync = new Object();
+ private static WeakReference<byte[]> mReadBuffer;
+
+ /** 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(String archiveSourcePath) {
+ mArchiveSourcePath = archiveSourcePath;
+ }
+
+ public void setSeparateProcesses(String[] procs) {
+ mSeparateProcesses = procs;
+ }
+
+ public void setSdkVersion(int sdkVersion) {
+ mSdkVersion = sdkVersion;
+ }
+
+ private static final boolean isPackageFilename(String name) {
+ return name.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) {
+
+ PackageInfo pi = new PackageInfo();
+ pi.packageName = p.packageName;
+ pi.versionCode = p.mVersionCode;
+ pi.versionName = p.mVersionName;
+ pi.sharedUserId = p.mSharedUserId;
+ pi.sharedUserLabel = p.mSharedUserLabel;
+ pi.applicationInfo = p.applicationInfo;
+ if ((flags&PackageManager.GET_GIDS) != 0) {
+ pi.gids = gids;
+ }
+ if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
+ int N = p.configPreferences.size();
+ if (N > 0) {
+ pi.configPreferences = new ConfigurationInfo[N];
+ for (int i=0; i<N; i++) {
+ pi.configPreferences[i] = p.configPreferences.get(i);
+ }
+ }
+ }
+ if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
+ int N = p.activities.size();
+ if (N > 0) {
+ pi.activities = new ActivityInfo[N];
+ for (int i=0; i<N; i++) {
+ final Activity activity = p.activities.get(i);
+ if (activity.info.enabled
+ || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+ pi.activities[i] = generateActivityInfo(p.activities.get(i), flags);
+ }
+ }
+ }
+ }
+ if ((flags&PackageManager.GET_RECEIVERS) != 0) {
+ int N = p.receivers.size();
+ if (N > 0) {
+ pi.receivers = new ActivityInfo[N];
+ for (int i=0; i<N; i++) {
+ final Activity activity = p.receivers.get(i);
+ if (activity.info.enabled
+ || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+ pi.receivers[i] = generateActivityInfo(p.receivers.get(i), flags);
+ }
+ }
+ }
+ }
+ if ((flags&PackageManager.GET_SERVICES) != 0) {
+ int N = p.services.size();
+ if (N > 0) {
+ pi.services = new ServiceInfo[N];
+ for (int i=0; i<N; i++) {
+ final Service service = p.services.get(i);
+ if (service.info.enabled
+ || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+ pi.services[i] = generateServiceInfo(p.services.get(i), flags);
+ }
+ }
+ }
+ }
+ if ((flags&PackageManager.GET_PROVIDERS) != 0) {
+ int N = p.providers.size();
+ if (N > 0) {
+ pi.providers = new ProviderInfo[N];
+ for (int i=0; i<N; i++) {
+ final Provider provider = p.providers.get(i);
+ if (provider.info.enabled
+ || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+ pi.providers[i] = generateProviderInfo(p.providers.get(i), flags);
+ }
+ }
+ }
+ }
+ 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];
+ for (int i=0; i<N; i++) {
+ pi.requestedPermissions[i] = p.requestedPermissions.get(i);
+ }
+ }
+ }
+ if ((flags&PackageManager.GET_SIGNATURES) != 0) {
+ int N = p.mSignatures.length;
+ if (N > 0) {
+ pi.signatures = new Signature[N];
+ System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N);
+ }
+ }
+ return pi;
+ }
+
+ private Certificate[] loadCertificates(JarFile jarFile, JarEntry je,
+ byte[] readBuffer) {
+ try {
+ // We must read the stream for the JarEntry to retrieve
+ // its certificates.
+ InputStream is = jarFile.getInputStream(je);
+ while (is.read(readBuffer, 0, readBuffer.length) != -1) {
+ // not using
+ }
+ is.close();
+ return je != null ? je.getCertificates() : null;
+ } catch (IOException e) {
+ Log.w(TAG, "Exception reading " + je.getName() + " in "
+ + jarFile.getName(), e);
+ }
+ return null;
+ }
+
+ public final static int PARSE_IS_SYSTEM = 0x0001;
+ public final static int PARSE_CHATTY = 0x0002;
+ public final static int PARSE_MUST_BE_APK = 0x0004;
+ public final static int PARSE_IGNORE_PROCESSES = 0x0008;
+
+ public int getParseError() {
+ return mParseError;
+ }
+
+ public Package parsePackage(File sourceFile, String destFileName,
+ DisplayMetrics metrics, int flags) {
+ mParseError = PackageManager.INSTALL_SUCCEEDED;
+
+ mArchiveSourcePath = sourceFile.getPath();
+ if (!sourceFile.isFile()) {
+ Log.w(TAG, "Skipping dir: " + mArchiveSourcePath);
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+ return null;
+ }
+ if (!isPackageFilename(sourceFile.getName())
+ && (flags&PARSE_MUST_BE_APK) != 0) {
+ if ((flags&PARSE_IS_SYSTEM) == 0) {
+ // We expect to have non-.apk files in the system dir,
+ // so don't warn about them.
+ Log.w(TAG, "Skipping non-package file: " + mArchiveSourcePath);
+ }
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+ return null;
+ }
+
+ if ((flags&PARSE_CHATTY) != 0 && Config.LOGD) Log.d(
+ TAG, "Scanning package: " + mArchiveSourcePath);
+
+ XmlResourceParser parser = null;
+ AssetManager assmgr = null;
+ boolean assetError = true;
+ try {
+ assmgr = new AssetManager();
+ if(assmgr.addAssetPath(mArchiveSourcePath) != 0) {
+ parser = assmgr.openXmlResourceParser("AndroidManifest.xml");
+ assetError = false;
+ } else {
+ Log.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to read AndroidManifest.xml of "
+ + mArchiveSourcePath, e);
+ }
+ if(assetError) {
+ if (assmgr != null) assmgr.close();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+ return null;
+ }
+ String[] errorText = new String[1];
+ Package pkg = null;
+ Exception errorException = null;
+ try {
+ // XXXX todo: need to figure out correct configuration.
+ Resources res = new Resources(assmgr, metrics, null);
+ pkg = parsePackage(res, parser, flags, errorText);
+ } catch (Exception e) {
+ errorException = e;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+ }
+
+
+ if (pkg == null) {
+ if (errorException != null) {
+ Log.w(TAG, mArchiveSourcePath, errorException);
+ } else {
+ Log.w(TAG, mArchiveSourcePath + " (at "
+ + parser.getPositionDescription()
+ + "): " + errorText[0]);
+ }
+ parser.close();
+ assmgr.close();
+ if (mParseError == PackageManager.INSTALL_SUCCEEDED) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ }
+ return null;
+ }
+
+ parser.close();
+ assmgr.close();
+
+ pkg.applicationInfo.sourceDir = destFileName;
+ pkg.applicationInfo.publicSourceDir = destFileName;
+ pkg.mSignatures = null;
+
+ return pkg;
+ }
+
+ public boolean collectCertificates(Package pkg, int flags) {
+ pkg.mSignatures = null;
+
+ WeakReference<byte[]> readBufferRef;
+ byte[] readBuffer = null;
+ synchronized (mSync) {
+ readBufferRef = mReadBuffer;
+ if (readBufferRef != null) {
+ mReadBuffer = null;
+ readBuffer = readBufferRef.get();
+ }
+ if (readBuffer == null) {
+ readBuffer = new byte[8192];
+ readBufferRef = new WeakReference<byte[]>(readBuffer);
+ }
+ }
+
+ try {
+ JarFile jarFile = new JarFile(mArchiveSourcePath);
+
+ Certificate[] certs = null;
+
+ if ((flags&PARSE_IS_SYSTEM) != 0) {
+ // If this package comes from the system image, then we
+ // can trust it... we'll just use the AndroidManifest.xml
+ // to retrieve its signatures, not validating all of the
+ // files.
+ JarEntry jarEntry = jarFile.getJarEntry("AndroidManifest.xml");
+ certs = loadCertificates(jarFile, jarEntry, readBuffer);
+ if (certs == null) {
+ Log.e(TAG, "Package " + pkg.packageName
+ + " has no certificates at entry "
+ + jarEntry.getName() + "; ignoring!");
+ jarFile.close();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+ return false;
+ }
+ if (false) {
+ Log.i(TAG, "File " + mArchiveSourcePath + ": entry=" + jarEntry
+ + " certs=" + (certs != null ? certs.length : 0));
+ if (certs != null) {
+ final int N = certs.length;
+ for (int i=0; i<N; i++) {
+ Log.i(TAG, " Public key: "
+ + certs[i].getPublicKey().getEncoded()
+ + " " + certs[i].getPublicKey());
+ }
+ }
+ }
+
+ } else {
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry je = (JarEntry)entries.nextElement();
+ if (je.isDirectory()) continue;
+ if (je.getName().startsWith("META-INF/")) continue;
+ Certificate[] localCerts = loadCertificates(jarFile, je,
+ readBuffer);
+ if (false) {
+ Log.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
+ + ": certs=" + certs + " ("
+ + (certs != null ? certs.length : 0) + ")");
+ }
+ if (localCerts == null) {
+ Log.e(TAG, "Package " + pkg.packageName
+ + " has no certificates at entry "
+ + je.getName() + "; ignoring!");
+ jarFile.close();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+ return false;
+ } else if (certs == null) {
+ certs = localCerts;
+ } else {
+ // Ensure all certificates match.
+ for (int i=0; i<certs.length; i++) {
+ boolean found = false;
+ for (int j=0; j<localCerts.length; j++) {
+ if (certs[i] != null &&
+ certs[i].equals(localCerts[j])) {
+ found = true;
+ break;
+ }
+ }
+ if (!found || certs.length != localCerts.length) {
+ Log.e(TAG, "Package " + pkg.packageName
+ + " has mismatched certificates at entry "
+ + je.getName() + "; ignoring!");
+ jarFile.close();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ return false;
+ }
+ }
+ }
+ }
+ }
+ jarFile.close();
+
+ synchronized (mSync) {
+ mReadBuffer = readBufferRef;
+ }
+
+ if (certs != null && certs.length > 0) {
+ final int N = certs.length;
+ pkg.mSignatures = new Signature[certs.length];
+ for (int i=0; i<N; i++) {
+ pkg.mSignatures[i] = new Signature(
+ certs[i].getEncoded());
+ }
+ } else {
+ Log.e(TAG, "Package " + pkg.packageName
+ + " has no certificates; ignoring!");
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+ return false;
+ }
+ } catch (CertificateEncodingException e) {
+ Log.w(TAG, "Exception reading " + mArchiveSourcePath, e);
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+ return false;
+ } catch (IOException e) {
+ Log.w(TAG, "Exception reading " + mArchiveSourcePath, e);
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+ return false;
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Exception reading " + mArchiveSourcePath, e);
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+ return false;
+ }
+
+ return true;
+ }
+
+ public static String parsePackageName(String packageFilePath, int flags) {
+ XmlResourceParser parser = null;
+ AssetManager assmgr = null;
+ try {
+ assmgr = new AssetManager();
+ int cookie = assmgr.addAssetPath(packageFilePath);
+ parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
+ } catch (Exception e) {
+ if (assmgr != null) assmgr.close();
+ Log.w(TAG, "Unable to read AndroidManifest.xml of "
+ + packageFilePath, e);
+ return null;
+ }
+ AttributeSet attrs = parser;
+ String errors[] = new String[1];
+ String packageName = null;
+ try {
+ packageName = parsePackageName(parser, attrs, flags, errors);
+ } catch (IOException e) {
+ Log.w(TAG, packageFilePath, e);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, packageFilePath, e);
+ } finally {
+ if (parser != null) parser.close();
+ if (assmgr != null) assmgr.close();
+ }
+ if (packageName == null) {
+ Log.e(TAG, "parsePackageName error: " + errors[0]);
+ return null;
+ }
+ return packageName;
+ }
+
+ private static String validateName(String name, boolean requiresSeparator) {
+ 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 + "'";
+ }
+ return hasSep || !requiresSeparator
+ ? null : "must have at least one '.' separator";
+ }
+
+ private static String parsePackageName(XmlPullParser parser,
+ AttributeSet attrs, int flags, String[] outError)
+ throws IOException, XmlPullParserException {
+
+ int type;
+ while ((type=parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != parser.START_TAG) {
+ outError[0] = "No start tag found";
+ return null;
+ }
+ if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+ TAG, "Root element name: '" + parser.getName() + "'");
+ if (!parser.getName().equals("manifest")) {
+ outError[0] = "No <manifest> tag";
+ return null;
+ }
+ String pkgName = attrs.getAttributeValue(null, "package");
+ if (pkgName == null || pkgName.length() == 0) {
+ outError[0] = "<manifest> does not specify package";
+ return null;
+ }
+ String nameError = validateName(pkgName, true);
+ if (nameError != null && !"android".equals(pkgName)) {
+ outError[0] = "<manifest> specifies bad package name \""
+ + pkgName + "\": " + nameError;
+ return null;
+ }
+
+ return pkgName.intern();
+ }
+
+ /**
+ * Temporary.
+ */
+ static public Signature stringToSignature(String str) {
+ final int N = str.length();
+ byte[] sig = new byte[N];
+ for (int i=0; i<N; i++) {
+ sig[i] = (byte)str.charAt(i);
+ }
+ return new Signature(sig);
+ }
+
+ private Package parsePackage(
+ Resources res, XmlResourceParser parser, int flags, String[] outError)
+ throws XmlPullParserException, IOException {
+ AttributeSet attrs = parser;
+
+ String pkgName = parsePackageName(parser, attrs, flags, outError);
+ if (pkgName == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+ return null;
+ }
+ int type;
+
+ final Package pkg = new Package(pkgName);
+ pkg.mSystem = (flags&PARSE_IS_SYSTEM) != 0;
+ boolean foundApp = false;
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifest);
+ pkg.mVersionCode = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
+ pkg.mVersionName = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifest_versionName);
+ if (pkg.mVersionName != null) {
+ pkg.mVersionName = pkg.mVersionName.intern();
+ }
+ String str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifest_sharedUserId);
+ if (str != null) {
+ String nameError = validateName(str, true);
+ if (nameError != null && !"android".equals(pkgName)) {
+ 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);
+ }
+ sa.recycle();
+
+ final int innerDepth = parser.getDepth();
+
+ int outerDepth = parser.getDepth();
+ while ((type=parser.next()) != parser.END_DOCUMENT
+ && (type != parser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == parser.END_TAG || type == parser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("application")) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ outError[0] = "<manifest> has more than one <application>";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ } else {
+ Log.w(TAG, "<manifest> has more than one <application>");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+
+ foundApp = true;
+ if (!parseApplication(pkg, res, parser, attrs, flags, outError)) {
+ return null;
+ }
+ } else if (tagName.equals("permission-group")) {
+ if (parsePermissionGroup(pkg, res, parser, attrs, outError) == null) {
+ return null;
+ }
+ } else if (tagName.equals("permission")) {
+ if (parsePermission(pkg, res, parser, attrs, outError) == null) {
+ return null;
+ }
+ } else if (tagName.equals("permission-tree")) {
+ if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
+ return null;
+ }
+ } else if (tagName.equals("uses-permission")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesPermission);
+
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
+
+ sa.recycle();
+
+ if (name != null && !pkg.requestedPermissions.contains(name)) {
+ pkg.requestedPermissions.add(name);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("uses-configuration")) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ sa = res.obtainAttributes(attrs,
+ 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.add(cPref);
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("uses-sdk")) {
+ if (mSdkVersion > 0) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesSdk);
+
+ int vers = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion, 0);
+
+ sa.recycle();
+
+ if (vers > mSdkVersion) {
+ outError[0] = "Requires newer sdk version #" + vers
+ + " (current version is #" + mSdkVersion + ")";
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("instrumentation")) {
+ if (parseInstrumentation(pkg, res, parser, attrs, outError) == null) {
+ return null;
+ }
+ } else if (tagName.equals("eat-comment")) {
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else if (RIGID_PARSER) {
+ outError[0] = "Bad element under <manifest>: "
+ + parser.getName();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ } else {
+ Log.w(TAG, "Bad element under <manifest>: "
+ + parser.getName());
+ 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;
+ }
+
+ if (pkg.usesLibraries.size() > 0) {
+ pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()];
+ pkg.usesLibraries.toArray(pkg.usesLibraryFiles);
+ }
+
+ return pkg;
+ }
+
+ 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).intern();
+ }
+ if (cls.indexOf('.') < 0) {
+ StringBuilder b = new StringBuilder(pkg);
+ b.append('.');
+ b.append(cls);
+ return b.toString().intern();
+ }
+ if (c >= 'a' && c <= 'z') {
+ return cls.intern();
+ }
+ outError[0] = "Bad class name " + cls + " in package " + pkg;
+ return null;
+ }
+
+ 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);
+ if (nameError != null) {
+ outError[0] = "Invalid " + type + " name " + proc + " in package "
+ + pkg + ": " + nameError;
+ return null;
+ }
+ return (pkg + proc).intern();
+ }
+ String nameError = validateName(proc, true);
+ if (nameError != null && !"system".equals(proc)) {
+ outError[0] = "Invalid " + type + " name " + proc + " in package "
+ + pkg + ": " + nameError;
+ return null;
+ }
+ return proc.intern();
+ }
+
+ 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 buildCompoundName(pkg, procSeq, "package", 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 PermissionGroup parsePermissionGroup(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, String[] outError)
+ throws XmlPullParserException, IOException {
+ PermissionGroup perm = new PermissionGroup(owner);
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup);
+
+ if (!parsePackageItemInfo(owner, perm.info, outError,
+ "<permission-group>", sa,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_label,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ perm.info.descriptionRes = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_description,
+ 0);
+
+ sa.recycle();
+
+ if (!parseAllMetaData(res, parser, attrs, "<permission-group>", perm,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ owner.permissionGroups.add(perm);
+
+ return perm;
+ }
+
+ private Permission parsePermission(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, String[] outError)
+ throws XmlPullParserException, IOException {
+ Permission perm = new Permission(owner);
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestPermission);
+
+ if (!parsePackageItemInfo(owner, perm.info, outError,
+ "<permission>", sa,
+ com.android.internal.R.styleable.AndroidManifestPermission_name,
+ com.android.internal.R.styleable.AndroidManifestPermission_label,
+ com.android.internal.R.styleable.AndroidManifestPermission_icon)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ 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.protectionLevel = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel,
+ PermissionInfo.PROTECTION_NORMAL);
+
+ sa.recycle();
+
+ if (perm.info.protectionLevel == -1) {
+ outError[0] = "<permission> does not specify protectionLevel";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ if (!parseAllMetaData(res, parser, attrs, "<permission>", perm,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ owner.permissions.add(perm);
+
+ return perm;
+ }
+
+ private Permission parsePermissionTree(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, String[] outError)
+ throws XmlPullParserException, IOException {
+ Permission perm = new Permission(owner);
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree);
+
+ if (!parsePackageItemInfo(owner, perm.info, outError,
+ "<permission-tree>", sa,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_name,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_label,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_icon)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ 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 null;
+ }
+
+ perm.info.descriptionRes = 0;
+ perm.info.protectionLevel = PermissionInfo.PROTECTION_NORMAL;
+ perm.tree = true;
+
+ if (!parseAllMetaData(res, parser, attrs, "<permission-tree>", perm,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ owner.permissions.add(perm);
+
+ return perm;
+ }
+
+ private Instrumentation parseInstrumentation(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation);
+
+ Instrumentation a = new Instrumentation(owner);
+
+ if (!parsePackageItemInfo(owner, a.info, outError, "<instrumentation>", sa,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_name,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_label,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_icon)) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ a.component = new ComponentName(owner.applicationInfo.packageName,
+ a.info.name);
+
+ String str;
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_targetPackage);
+ a.info.targetPackage = 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, attrs, "<instrumentation>", a,
+ outError)) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return null;
+ }
+
+ owner.instrumentation.add(a);
+
+ return a;
+ }
+
+ private boolean parseApplication(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
+ throws XmlPullParserException, IOException {
+ final ApplicationInfo ai = owner.applicationInfo;
+ final String pkgName = owner.applicationInfo.packageName;
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestApplication);
+
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_name);
+ if (name != null) {
+ ai.className = buildClassName(pkgName, name, outError);
+ if (ai.className == null) {
+ sa.recycle();
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ }
+
+ String manageSpaceActivity = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity);
+ if (manageSpaceActivity != null) {
+ ai.manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity,
+ outError);
+ }
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestApplication_label);
+ if (v != null && (ai.labelRes=v.resourceId) == 0) {
+ ai.nonLocalizedLabel = v.coerceToString();
+ }
+
+ ai.icon = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
+ 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)) {
+ ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
+ }
+ }
+
+ 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_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;
+ }
+
+ String str;
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_permission);
+ ai.permission = (str != null && str.length() > 0) ? str.intern() : null;
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity);
+ ai.taskAffinity = buildTaskAffinityName(ai.packageName, ai.packageName,
+ str, outError);
+
+ if (outError[0] == null) {
+ ai.processName = buildProcessName(ai.packageName, null, sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_process),
+ flags, mSeparateProcesses, outError);
+
+ ai.enabled = sa.getBoolean(com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
+ }
+
+ sa.recycle();
+
+ if (outError[0] != null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ final int innerDepth = parser.getDepth();
+
+ int type;
+ while ((type=parser.next()) != parser.END_DOCUMENT
+ && (type != parser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == parser.END_TAG || type == parser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("activity")) {
+ Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false);
+ 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, attrs, flags, outError, true);
+ 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, attrs, flags, outError);
+ 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, attrs, flags, outError);
+ 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, attrs, flags, outError, false);
+ 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, attrs, owner.mAppMetaData,
+ outError)) == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ } else if (tagName.equals("uses-library")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary);
+
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+
+ sa.recycle();
+
+ if (lname != null && !owner.usesLibraries.contains(lname)) {
+ owner.usesLibraries.add(lname);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "Unknown element under <application>: " + tagName);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ } else {
+ outError[0] = "Bad element under <application>: " + tagName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
+ String[] outError, String tag, TypedArray sa,
+ int nameRes, int labelRes, int iconRes) {
+ String name = sa.getNonResourceString(nameRes);
+ if (name == null) {
+ outError[0] = tag + " does not specify android:name";
+ return false;
+ }
+
+ outInfo.name
+ = buildClassName(owner.applicationInfo.packageName, name, outError);
+ if (outInfo.name == null) {
+ return false;
+ }
+
+ int iconVal = sa.getResourceId(iconRes, 0);
+ if (iconVal != 0) {
+ outInfo.icon = iconVal;
+ outInfo.nonLocalizedLabel = null;
+ }
+
+ TypedValue v = sa.peekValue(labelRes);
+ if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
+ outInfo.nonLocalizedLabel = v.coerceToString();
+ }
+
+ outInfo.packageName = owner.packageName;
+
+ return true;
+ }
+
+ private boolean parseComponentInfo(Package owner, int flags,
+ ComponentInfo outInfo, String[] outError, String tag, TypedArray sa,
+ int nameRes, int labelRes, int iconRes, int processRes,
+ int enabledRes) {
+ if (!parsePackageItemInfo(owner, outInfo, outError, tag, sa,
+ nameRes, labelRes, iconRes)) {
+ return false;
+ }
+
+ if (processRes != 0) {
+ outInfo.processName = buildProcessName(owner.applicationInfo.packageName,
+ owner.applicationInfo.processName, sa.getNonResourceString(processRes),
+ flags, mSeparateProcesses, outError);
+ }
+ outInfo.enabled = sa.getBoolean(enabledRes, true);
+
+ return outError[0] == null;
+ }
+
+ private Activity parseActivity(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
+ boolean receiver) throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestActivity);
+
+ Activity a = new Activity(owner);
+
+ if (!parseComponentInfo(owner, flags, a.info, outError,
+ receiver ? "<receiver>" : "<activity>", sa,
+ com.android.internal.R.styleable.AndroidManifestActivity_name,
+ com.android.internal.R.styleable.AndroidManifestActivity_label,
+ com.android.internal.R.styleable.AndroidManifestActivity_icon,
+ com.android.internal.R.styleable.AndroidManifestActivity_process,
+ com.android.internal.R.styleable.AndroidManifestActivity_enabled)) {
+ sa.recycle();
+ return null;
+ }
+
+ final boolean setExported = sa.hasValue(
+ com.android.internal.R.styleable.AndroidManifestActivity_exported);
+ if (setExported) {
+ a.info.exported = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_exported, false);
+ }
+
+ a.component = new ComponentName(owner.applicationInfo.packageName,
+ a.info.name);
+
+ a.info.theme = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestActivity_theme, 0);
+
+ String str;
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestActivity_permission);
+ if (str == null) {
+ a.info.permission = owner.applicationInfo.permission;
+ } else {
+ a.info.permission = str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity);
+ a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
+ owner.applicationInfo.taskAffinity, str, outError);
+
+ a.info.flags = 0;
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_multiprocess,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_finishOnTaskLaunch,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_clearTaskOnLaunch,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_noHistory,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_alwaysRetainTaskState,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_stateNotNeeded,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_excludeFromRecents,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ }
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_allowTaskReparenting,
+ (owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) {
+ a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING;
+ }
+
+ if (!receiver) {
+ a.info.launchMode = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestActivity_launchMode,
+ ActivityInfo.LAUNCH_MULTIPLE);
+ a.info.screenOrientation = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation,
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ a.info.configChanges = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestActivity_configChanges,
+ 0);
+ a.info.softInputMode = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
+ 0);
+ } else {
+ a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ a.info.configChanges = 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, attrs, flags, intent, outError, !receiver)) {
+ return null;
+ }
+ if (intent.countActions() == 0) {
+ Log.w(TAG, "Intent filter for activity " + intent
+ + " defines no actions");
+ } else {
+ a.intents.add(intent);
+ }
+ } else if (parser.getName().equals("meta-data")) {
+ if ((a.metaData=parseMetaData(res, parser, attrs, a.metaData,
+ outError)) == null) {
+ return null;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ if (receiver) {
+ Log.w(TAG, "Unknown element under <receiver>: " + parser.getName());
+ } else {
+ Log.w(TAG, "Unknown element under <activity>: " + parser.getName());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ 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 Activity parseActivityAlias(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
+ boolean receiver) throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias);
+
+ String targetActivity = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity);
+ 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;
+ }
+
+ Activity a = new Activity(owner);
+ 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;
+ }
+
+ a.info.targetActivity = targetActivity;
+
+ a.info.configChanges = target.info.configChanges;
+ a.info.flags = target.info.flags;
+ a.info.icon = target.info.icon;
+ a.info.labelRes = target.info.labelRes;
+ a.info.launchMode = target.info.launchMode;
+ a.info.nonLocalizedLabel = target.info.nonLocalizedLabel;
+ a.info.processName = target.info.processName;
+ a.info.screenOrientation = target.info.screenOrientation;
+ a.info.taskAffinity = target.info.taskAffinity;
+ a.info.theme = target.info.theme;
+
+ if (!parseComponentInfo(owner, flags, a.info, outError,
+ receiver ? "<receiver>" : "<activity>", sa,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_name,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_label,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
+ 0,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled)) {
+ 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);
+ }
+
+ a.component = new ComponentName(owner.applicationInfo.packageName,
+ a.info.name);
+
+ String str;
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_permission);
+ if (str != null) {
+ a.info.permission = str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ 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, attrs, flags, intent, outError, true)) {
+ return null;
+ }
+ if (intent.countActions() == 0) {
+ Log.w(TAG, "Intent filter for activity alias " + intent
+ + " defines no actions");
+ } else {
+ a.intents.add(intent);
+ }
+ } else if (parser.getName().equals("meta-data")) {
+ if ((a.metaData=parseMetaData(res, parser, attrs, a.metaData,
+ outError)) == null) {
+ return null;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "Unknown element under <activity-alias>: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ 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,
+ XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestProvider);
+
+ Provider p = new Provider(owner);
+
+ if (!parseComponentInfo(owner, flags, p.info, outError, "<provider>", sa,
+ 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_process,
+ com.android.internal.R.styleable.AndroidManifestProvider_enabled)) {
+ sa.recycle();
+ return null;
+ }
+
+ p.info.exported = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_exported, true);
+
+ p.component = new ComponentName(owner.applicationInfo.packageName,
+ p.info.name);
+
+ String cpname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestProvider_authorities);
+
+ p.info.isSyncable = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_syncable,
+ false);
+
+ String permission = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestProvider_permission);
+ String str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestProvider_readPermission);
+ 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.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestProvider_writePermission);
+ 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);
+
+ sa.recycle();
+
+ if (cpname == null) {
+ outError[0] = "<provider> does not incude authorities attribute";
+ return null;
+ }
+ p.info.authority = cpname.intern();
+
+ if (!parseProviderTags(res, parser, attrs, p, outError)) {
+ return null;
+ }
+
+ return p;
+ }
+
+ private boolean parseProviderTags(Resources res,
+ XmlPullParser parser, AttributeSet attrs,
+ 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("meta-data")) {
+ if ((outInfo.metaData=parseMetaData(res, parser, attrs,
+ outInfo.metaData, outError)) == null) {
+ return false;
+ }
+ } else if (parser.getName().equals("grant-uri-permission")) {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission);
+
+ PatternMatcher pa = null;
+
+ String str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_path);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPrefix);
+ if (str != null) {
+ pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPattern);
+ 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;
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "Unknown element under <provider>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "Bad element under <provider>: "
+ + parser.getName();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Service parseService(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestService);
+
+ Service s = new Service(owner);
+
+ if (!parseComponentInfo(owner, flags, s.info, outError, "<service>", sa,
+ 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_process,
+ com.android.internal.R.styleable.AndroidManifestService_enabled)) {
+ sa.recycle();
+ return null;
+ }
+
+ final 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);
+ }
+
+ s.component = new ComponentName(owner.applicationInfo.packageName,
+ s.info.name);
+
+ String str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestService_permission);
+ if (str == null) {
+ s.info.permission = owner.applicationInfo.permission;
+ } else {
+ s.info.permission = str.length() > 0 ? str.toString().intern() : null;
+ }
+
+ 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;
+ }
+
+ if (parser.getName().equals("intent-filter")) {
+ ServiceIntentInfo intent = new ServiceIntentInfo(s);
+ if (!parseIntent(res, parser, attrs, flags, intent, outError, false)) {
+ return null;
+ }
+
+ s.intents.add(intent);
+ } else if (parser.getName().equals("meta-data")) {
+ if ((s.metaData=parseMetaData(res, parser, attrs, s.metaData,
+ outError)) == null) {
+ return null;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "Unknown element under <service>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "Bad element under <service>: "
+ + parser.getName();
+ return null;
+ }
+ }
+
+ if (!setExported) {
+ s.info.exported = s.intents.size() > 0;
+ }
+
+ return s;
+ }
+
+ private boolean parseAllMetaData(Resources res,
+ XmlPullParser parser, AttributeSet attrs, 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, attrs,
+ outInfo.metaData, outError)) == null) {
+ return false;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "Unknown element under " + tag + ": "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "Bad element under " + tag + ": "
+ + parser.getName();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Bundle parseMetaData(Resources res,
+ XmlPullParser parser, AttributeSet attrs,
+ Bundle data, String[] outError)
+ throws XmlPullParserException, IOException {
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestMetaData);
+
+ if (data == null) {
+ data = new Bundle();
+ }
+
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestMetaData_name);
+ if (name == null) {
+ outError[0] = "<meta-data> requires an android:name attribute";
+ sa.recycle();
+ return null;
+ }
+
+ boolean success = true;
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestMetaData_resource);
+ if (v != null && v.resourceId != 0) {
+ //Log.i(TAG, "Meta data ref " + name + ": " + v);
+ data.putInt(name, v.resourceId);
+ } else {
+ v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestMetaData_value);
+ //Log.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) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "<meta-data> only supports string, integer, float, color, boolean, and resource reference types");
+ } 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 final String ANDROID_RESOURCES
+ = "http://schemas.android.com/apk/res/android";
+
+ private boolean parseIntent(Resources res,
+ XmlPullParser parser, AttributeSet attrs, int flags,
+ IntentInfo outInfo, String[] outError, boolean isActivity)
+ throws XmlPullParserException, IOException {
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestIntentFilter);
+
+ int priority = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0);
+ if (priority > 0 && isActivity && (flags&PARSE_IS_SYSTEM) == 0) {
+ Log.w(TAG, "Activity with priority > 0, forcing to 0 at "
+ + parser.getPositionDescription());
+ 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();
+ }
+
+ outInfo.icon = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
+
+ sa.recycle();
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != parser.END_DOCUMENT
+ && (type != parser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == parser.END_TAG || type == parser.TEXT) {
+ continue;
+ }
+
+ String nodeName = parser.getName();
+ if (nodeName.equals("action")) {
+ String value = attrs.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 = attrs.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(attrs,
+ com.android.internal.R.styleable.AndroidManifestData);
+
+ String str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_mimeType);
+ if (str != null) {
+ try {
+ outInfo.addDataType(str);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ outError[0] = e.toString();
+ sa.recycle();
+ return false;
+ }
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_scheme);
+ if (str != null) {
+ outInfo.addDataScheme(str);
+ }
+
+ String host = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_host);
+ String port = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_port);
+ if (host != null) {
+ outInfo.addDataAuthority(host, port);
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_path);
+ if (str != null) {
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_pathPrefix);
+ if (str != null) {
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
+ }
+
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestData_pathPattern);
+ if (str != null) {
+ outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ sa.recycle();
+ XmlUtils.skipCurrentTag(parser);
+ } else if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "Unknown element under <intent-filter>: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ outError[0] = "Bad element under <intent-filter>: " + parser.getName();
+ return false;
+ }
+ }
+
+ outInfo.hasDefault = outInfo.hasCategory(Intent.CATEGORY_DEFAULT);
+ if (false) {
+ String cats = "";
+ Iterator<String> it = outInfo.categoriesIterator();
+ while (it != null && it.hasNext()) {
+ cats += " " + it.next();
+ }
+ System.out.println("Intent d=" +
+ outInfo.hasDefault + ", cat=" + cats);
+ }
+
+ return true;
+ }
+
+ public final static class Package {
+ public final String packageName;
+
+ // For now we only support one application per package.
+ public final 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 final ArrayList<String> usesLibraries = new ArrayList<String>();
+ public String[] usesLibraryFiles = null;
+
+ // We store the application meta-data independently to avoid multiple unwanted references
+ public Bundle mAppMetaData = null;
+
+ // If this is a 3rd party app, this is the path of the zip file.
+ public String mPath;
+
+ // True if this package is part of the system image.
+ public boolean mSystem;
+
+ // 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[];
+
+ // For use by package manager service for quick lookup of
+ // preferred up order.
+ public int mPreferredOrder = 0;
+
+ // Additional data supplied by callers.
+ public Object mExtras;
+
+ /*
+ * Applications hardware preferences
+ */
+ public final ArrayList<ConfigurationInfo> configPreferences =
+ new ArrayList<ConfigurationInfo>();
+
+ public Package(String _name) {
+ packageName = _name;
+ applicationInfo.packageName = _name;
+ applicationInfo.uid = -1;
+ }
+
+ public String toString() {
+ return "Package{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+ }
+
+ public static class Component<II extends IntentInfo> {
+ public final Package owner;
+ public final ArrayList<II> intents = new ArrayList<II>(0);
+ public ComponentName component;
+ public Bundle metaData;
+
+ public Component(Package _owner) {
+ owner = _owner;
+ }
+
+ public Component(Component<II> clone) {
+ owner = clone.owner;
+ metaData = clone.metaData;
+ }
+ }
+
+ public final static class Permission extends Component<IntentInfo> {
+ 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 String toString() {
+ return "Permission{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + info.name + "}";
+ }
+ }
+
+ public final static class PermissionGroup extends Component<IntentInfo> {
+ public final PermissionGroupInfo info;
+
+ public PermissionGroup(Package _owner) {
+ super(_owner);
+ info = new PermissionGroupInfo();
+ }
+
+ public PermissionGroup(Package _owner, PermissionGroupInfo _info) {
+ super(_owner);
+ info = _info;
+ }
+
+ public String toString() {
+ return "PermissionGroup{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + info.name + "}";
+ }
+ }
+
+ private static boolean copyNeeded(int flags, Package p, Bundle metaData) {
+ 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;
+ }
+ return false;
+ }
+
+ public static ApplicationInfo generateApplicationInfo(Package p, int flags) {
+ if (p == null) return null;
+ if (!copyNeeded(flags, p, null)) {
+ return p.applicationInfo;
+ }
+
+ // Make shallow copy so we can store the metadata/libraries safely
+ ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ ai.metaData = p.mAppMetaData;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+ ai.sharedLibraryFiles = p.usesLibraryFiles;
+ }
+ 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> {
+ public final ActivityInfo info =
+ new ActivityInfo();
+
+ public Activity(Package _owner) {
+ super(_owner);
+ info.applicationInfo = owner.applicationInfo;
+ }
+
+ public String toString() {
+ return "Activity{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + component.flattenToString() + "}";
+ }
+ }
+
+ public static final ActivityInfo generateActivityInfo(Activity a,
+ int flags) {
+ if (a == null) return null;
+ if (!copyNeeded(flags, a.owner, a.metaData)) {
+ 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);
+ return ai;
+ }
+
+ public final static class Service extends Component<ServiceIntentInfo> {
+ public final ServiceInfo info =
+ new ServiceInfo();
+
+ public Service(Package _owner) {
+ super(_owner);
+ info.applicationInfo = owner.applicationInfo;
+ }
+
+ public String toString() {
+ return "Service{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + component.flattenToString() + "}";
+ }
+ }
+
+ public static final ServiceInfo generateServiceInfo(Service s, int flags) {
+ if (s == null) return null;
+ if (!copyNeeded(flags, s.owner, s.metaData)) {
+ 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);
+ return si;
+ }
+
+ public final static class Provider extends Component {
+ public final ProviderInfo info;
+ public boolean syncable;
+
+ public Provider(Package _owner) {
+ super(_owner);
+ info = new ProviderInfo();
+ info.applicationInfo = owner.applicationInfo;
+ syncable = false;
+ }
+
+ public Provider(Provider existingProvider) {
+ super(existingProvider);
+ this.info = existingProvider.info;
+ this.syncable = existingProvider.syncable;
+ }
+
+ public String toString() {
+ return "Provider{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + info.name + "}";
+ }
+ }
+
+ public static final ProviderInfo generateProviderInfo(Provider p,
+ int flags) {
+ if (p == null) return null;
+ if (!copyNeeded(flags, p.owner, p.metaData)
+ && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
+ || p.info.uriPermissionPatterns == null)) {
+ 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);
+ return pi;
+ }
+
+ public final static class Instrumentation extends Component {
+ public final InstrumentationInfo info =
+ new InstrumentationInfo();
+
+ public Instrumentation(Package _owner) {
+ super(_owner);
+ }
+
+ public String toString() {
+ return "Instrumentation{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + component.flattenToString() + "}";
+ }
+ }
+
+ 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 class IntentInfo extends IntentFilter {
+ public boolean hasDefault;
+ public int labelRes;
+ public CharSequence nonLocalizedLabel;
+ public int icon;
+ }
+
+ public final static class ActivityIntentInfo extends IntentInfo {
+ public final Activity activity;
+
+ public ActivityIntentInfo(Activity _activity) {
+ activity = _activity;
+ }
+
+ public String toString() {
+ return "ActivityIntentInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + activity.info.name + "}";
+ }
+ }
+
+ public final static class ServiceIntentInfo extends IntentInfo {
+ public final Service service;
+
+ public ServiceIntentInfo(Service _service) {
+ service = _service;
+ }
+
+ public String toString() {
+ return "ServiceIntentInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + service.info.name + "}";
+ }
+ }
+}
diff --git a/core/java/android/content/pm/PackageStats.aidl b/core/java/android/content/pm/PackageStats.aidl
new file mode 100755
index 0000000..8c9786f
--- /dev/null
+++ b/core/java/android/content/pm/PackageStats.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable PackageStats;
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
new file mode 100755
index 0000000..66c6efd
--- /dev/null
+++ b/core/java/android/content/pm/PackageStats.java
@@ -0,0 +1,63 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * implementation of PackageStats associated with a
+ * application package.
+ */
+public class PackageStats implements Parcelable {
+ public String packageName;
+ public long codeSize;
+ public long dataSize;
+ public long cacheSize;
+
+ 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() {
+ return "PackageStats{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public PackageStats(String pkgName) {
+ packageName = pkgName;
+ }
+
+ public PackageStats(Parcel source) {
+ packageName = source.readString();
+ codeSize = source.readLong();
+ dataSize = source.readLong();
+ cacheSize = source.readLong();
+ }
+
+ public PackageStats(PackageStats pStats) {
+ packageName = pStats.packageName;
+ codeSize = pStats.codeSize;
+ dataSize = pStats.dataSize;
+ cacheSize = pStats.cacheSize;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags){
+ dest.writeString(packageName);
+ dest.writeLong(codeSize);
+ dest.writeLong(dataSize);
+ dest.writeLong(cacheSize);
+ }
+}
diff --git a/core/java/android/content/pm/PermissionGroupInfo.aidl b/core/java/android/content/pm/PermissionGroupInfo.aidl
new file mode 100755
index 0000000..9f215f1
--- /dev/null
+++ b/core/java/android/content/pm/PermissionGroupInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable PermissionGroupInfo;
diff --git a/core/java/android/content/pm/PermissionGroupInfo.java b/core/java/android/content/pm/PermissionGroupInfo.java
new file mode 100644
index 0000000..02eb816
--- /dev/null
+++ b/core/java/android/content/pm/PermissionGroupInfo.java
@@ -0,0 +1,108 @@
+/*
+ * 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 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;
+
+ /**
+ * 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;
+
+ public PermissionGroupInfo() {
+ }
+
+ public PermissionGroupInfo(PermissionGroupInfo orig) {
+ super(orig);
+ descriptionRes = orig.descriptionRes;
+ 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 "PermissionGroupInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + name + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ super.writeToParcel(dest, parcelableFlags);
+ dest.writeInt(descriptionRes);
+ TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ }
+
+ 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();
+ nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+}
diff --git a/core/java/android/content/pm/PermissionInfo.aidl b/core/java/android/content/pm/PermissionInfo.aidl
new file mode 100755
index 0000000..5a7d4f4
--- /dev/null
+++ b/core/java/android/content/pm/PermissionInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable PermissionInfo;
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
new file mode 100644
index 0000000..3cc884b
--- /dev/null
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -0,0 +1,156 @@
+/*
+ * 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 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;
+
+ /**
+ * System-level value for {@link #protectionLevel}, corresponding
+ * to the <code>signatureOrSystem</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
+
+ /**
+ * The group this permission is a part of, as per
+ * {@link android.R.attr#permissionGroup}.
+ */
+ public String group;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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}.
+ */
+ public int protectionLevel;
+
+ public PermissionInfo() {
+ }
+
+ public PermissionInfo(PermissionInfo orig) {
+ super(orig);
+ group = orig.group;
+ descriptionRes = orig.descriptionRes;
+ protectionLevel = orig.protectionLevel;
+ 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.writeString(group);
+ dest.writeInt(descriptionRes);
+ dest.writeInt(protectionLevel);
+ 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);
+ group = source.readString();
+ descriptionRes = source.readInt();
+ protectionLevel = source.readInt();
+ nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+}
diff --git a/core/java/android/content/pm/ProviderInfo.aidl b/core/java/android/content/pm/ProviderInfo.aidl
new file mode 100755
index 0000000..18fbc8a
--- /dev/null
+++ b/core/java/android/content/pm/ProviderInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable ProviderInfo;
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
new file mode 100644
index 0000000..b67ddf6
--- /dev/null
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+/**
+ * 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 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;
+
+ /** Whether or not this provider is syncable. */
+ 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;
+ multiprocess = orig.multiprocess;
+ initOrder = orig.initOrder;
+ isSyncable = orig.isSyncable;
+ }
+
+ 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.writeInt(multiprocess ? 1 : 0);
+ out.writeInt(initOrder);
+ 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
+ + " isSyncable=" + (isSyncable ? "true" : "false") + "}";
+ }
+
+ private ProviderInfo(Parcel in) {
+ super(in);
+ authority = in.readString();
+ readPermission = in.readString();
+ writePermission = in.readString();
+ grantUriPermissions = in.readInt() != 0;
+ uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+ multiprocess = in.readInt() != 0;
+ initOrder = in.readInt();
+ isSyncable = in.readInt() != 0;
+ }
+}
diff --git a/core/java/android/content/pm/ResolveInfo.aidl b/core/java/android/content/pm/ResolveInfo.aidl
new file mode 100755
index 0000000..b4e7f8b
--- /dev/null
+++ b/core/java/android/content/pm/ResolveInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable ResolveInfo;
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
new file mode 100644
index 0000000..ee49c02
--- /dev/null
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -0,0 +1,280 @@
+package android.content.pm;
+
+import android.content.IntentFilter;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Printer;
+
+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 {
+ /**
+ * The activity that corresponds to this resolution match, if this
+ * resolution is for an activity. One and only one of this and
+ * serviceInfo must be non-null.
+ */
+ public ActivityInfo activityInfo;
+
+ /**
+ * The service that corresponds to this resolution match, if this
+ * resolution is for a service. One and only one of this and
+ * activityInfo must be non-null.
+ */
+ public ServiceInfo serviceInfo;
+
+ /**
+ * 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.
+ */
+ public int icon;
+
+ /**
+ * 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;
+ }
+ ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+ ApplicationInfo ai = ci.applicationInfo;
+ CharSequence label;
+ if (labelRes != 0) {
+ label = pm.getText(ci.packageName, labelRes, ai);
+ if (label != null) {
+ return label;
+ }
+ }
+ return ci.loadLabel(pm);
+ }
+
+ /**
+ * 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) {
+ ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+ ApplicationInfo ai = ci.applicationInfo;
+ Drawable dr;
+ if (icon != 0) {
+ dr = pm.getDrawable(ci.packageName, icon, ai);
+ if (dr != null) {
+ return dr;
+ }
+ }
+ 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.
+ *
+ * @return The icon associated with this match.
+ */
+ public final int getIconResource() {
+ if (icon != 0) return icon;
+ if (activityInfo != null) return activityInfo.getIconResource();
+ if (serviceInfo != null) return serviceInfo.getIconResource();
+ return 0;
+ }
+
+ public void dump(Printer pw, String prefix) {
+ if (filter != null) {
+ pw.println(prefix + "Filter:");
+ filter.dump(pw, prefix + " ");
+ } else {
+ pw.println(prefix + "Filter: null");
+ }
+ pw.println(prefix + "priority=" + priority
+ + " preferredOrder=" + preferredOrder
+ + " match=0x" + Integer.toHexString(match)
+ + " specificIndex=" + specificIndex
+ + " isDefault=" + isDefault);
+ 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 + " ");
+ } else if (serviceInfo != null) {
+ pw.println(prefix + "ServiceInfo:");
+ // TODO
+ //serviceInfo.dump(pw, prefix + " ");
+ }
+ }
+
+ public ResolveInfo() {
+ }
+
+ public String toString() {
+ ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+ return "ResolveInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + ci.name + " p=" + priority + " o="
+ + preferredOrder + " m=0x" + Integer.toHexString(match) + "}";
+ }
+
+ 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 {
+ 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);
+ }
+
+ 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) {
+ switch (source.readInt()) {
+ case 1:
+ activityInfo = ActivityInfo.CREATOR.createFromParcel(source);
+ serviceInfo = null;
+ break;
+ case 2:
+ serviceInfo = ServiceInfo.CREATOR.createFromParcel(source);
+ activityInfo = null;
+ break;
+ default:
+ activityInfo = null;
+ serviceInfo = null;
+ 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();
+ }
+
+ public static class DisplayNameComparator
+ implements Comparator<ResolveInfo> {
+ public DisplayNameComparator(PackageManager pm) {
+ mPM = pm;
+ }
+
+ public final int compare(ResolveInfo a, ResolveInfo b) {
+ 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 sCollator.compare(sa.toString(), sb.toString());
+ }
+
+ private final Collator sCollator = Collator.getInstance();
+ private PackageManager mPM;
+ }
+}
diff --git a/core/java/android/content/pm/ServiceInfo.aidl b/core/java/android/content/pm/ServiceInfo.aidl
new file mode 100755
index 0000000..5ddae1a
--- /dev/null
+++ b/core/java/android/content/pm/ServiceInfo.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable ServiceInfo;
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
new file mode 100644
index 0000000..b60650c
--- /dev/null
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -0,0 +1,56 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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;
+
+ public ServiceInfo() {
+ }
+
+ public ServiceInfo(ServiceInfo orig) {
+ super(orig);
+ permission = orig.permission;
+ }
+
+ 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);
+ }
+
+ 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();
+ }
+}
diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl
new file mode 100755
index 0000000..3a0d775
--- /dev/null
+++ b/core/java/android/content/pm/Signature.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable Signature;
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
new file mode 100644
index 0000000..1bb3857
--- /dev/null
+++ b/core/java/android/content/pm/Signature.java
@@ -0,0 +1,158 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Opaque, immutable representation of a signature associated with an
+ * application package.
+ */
+public class Signature implements Parcelable {
+ private final byte[] mSignature;
+ private int mHashCode;
+ private boolean mHaveHashCode;
+ private String mString;
+
+ /**
+ * Create Signature from an existing raw byte array.
+ */
+ public Signature(byte[] signature) {
+ mSignature = signature.clone();
+ }
+
+ /**
+ * Create Signature from a text representation previously returned by
+ * {@link #toChars} or {@link #toCharsString()}.
+ */
+ public Signature(String text) {
+ final int N = text.length()/2;
+ byte[] sig = new byte[N];
+ for (int i=0; i<N; i++) {
+ char c = text.charAt(i*2);
+ byte b = (byte)(
+ (c >= 'a' ? (c - 'a' + 10) : (c - '0'))<<4);
+ c = text.charAt(i*2 + 1);
+ b |= (byte)(c >= 'a' ? (c - 'a' + 10) : (c - '0'));
+ sig[i] = b;
+ }
+ 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. This result is
+ * cached so future calls will return the same String.
+ */
+ public String toCharsString() {
+ if (mString != null) return mString;
+ String str = new String(toChars());
+ mString = str;
+ return mString;
+ }
+
+ /**
+ * @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;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ try {
+ if (obj != null) {
+ Signature other = (Signature)obj;
+ return 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();
+ }
+}
diff --git a/core/java/android/content/pm/package.html b/core/java/android/content/pm/package.html
new file mode 100644
index 0000000..766b7dd
--- /dev/null
+++ b/core/java/android/content/pm/package.html
@@ -0,0 +1,7 @@
+<HTML>
+<BODY>
+Contains classes for accessing information about an
+application package, including information about its activities,
+permissions, services, signatures, and providers.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
new file mode 100644
index 0000000..231e3e2
--- /dev/null
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -0,0 +1,348 @@
+/*
+ * 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.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+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 {
+ /**
+ * 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;
+
+ /**
+ * 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) {
+ if (length < 0 && startOffset != 0) {
+ throw new IllegalArgumentException(
+ "startOffset must be 0 when using UNKNOWN_LENGTH");
+ }
+ mFd = fd;
+ mStartOffset = startOffset;
+ mLength = length;
+ }
+
+ /**
+ * 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 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>.
+ */
+ 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 {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int res = super.read();
+ if (res >= 0) mRemaining--;
+ return res;
+ }
+
+ return super.read();
+ }
+
+ @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 {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, 0, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer);
+ }
+
+ @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;
+ }
+
+ // TODO Auto-generated method stub
+ 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 */
+ public int describeContents() {
+ return mFd.describeContents();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ mFd.writeToParcel(out, flags);
+ out.writeLong(mStartOffset);
+ out.writeLong(mLength);
+ }
+
+ AssetFileDescriptor(Parcel src) {
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
+ mStartOffset = src.readLong();
+ mLength = src.readLong();
+ }
+
+ 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/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
new file mode 100644
index 0000000..fadcb35
--- /dev/null
+++ b/core/java/android/content/res/AssetManager.java
@@ -0,0 +1,697 @@
+/*
+ * 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.ParcelFileDescriptor;
+import android.util.Config;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * 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 {
+ /* 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 = Config.LOGV || false;
+
+ private static final Object mSync = new Object();
+ private static final TypedValue mValue = new TypedValue();
+ private static final long[] mOffsets = new long[2];
+ private static AssetManager mSystem = null;
+
+ // For communication with native code.
+ private int mObject;
+
+ private StringBlock mStringBlocks[] = null;
+
+ private int mNumRefs = 1;
+ private boolean mOpen = true;
+ private String mAssetDir;
+ private String mAppName;
+
+ /**
+ * 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 (mSync) {
+ init();
+ if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+ ensureSystemAssets();
+ }
+ }
+
+ private static void ensureSystemAssets() {
+ synchronized (mSync) {
+ if (mSystem == null) {
+ AssetManager system = new AssetManager(true);
+ system.makeStringBlocks(false);
+ mSystem = system;
+ }
+ }
+ }
+
+ private AssetManager(boolean isSystem) {
+ init();
+ 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 mSystem;
+ }
+
+ /**
+ * Close this asset manager.
+ */
+ public void close() {
+ synchronized(mSync) {
+ //System.out.println("Release: num=" + mNumRefs
+ // + ", released=" + mReleased);
+ if (mOpen) {
+ mOpen = false;
+ decRefsLocked();
+ }
+ }
+ }
+
+ /**
+ * Retrieve the string value associated with a particular resource
+ * identifier for the current configuration / skin.
+ */
+ /*package*/ final CharSequence getResourceText(int ident) {
+ synchronized (mSync) {
+ TypedValue tmpValue = mValue;
+ int block = loadResourceValue(ident, tmpValue, true);
+ if (block >= 0) {
+ if (tmpValue.type == TypedValue.TYPE_STRING) {
+ return mStringBlocks[block].get(tmpValue.data);
+ }
+ return tmpValue.coerceToString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the string value associated with a particular resource
+ * identifier for the current configuration / skin.
+ */
+ /*package*/ final CharSequence getResourceBagText(int ident, int bagEntryId) {
+ synchronized (mSync) {
+ TypedValue tmpValue = mValue;
+ int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true);
+ if (block >= 0) {
+ if (tmpValue.type == TypedValue.TYPE_STRING) {
+ return mStringBlocks[block].get(tmpValue.data);
+ }
+ return tmpValue.coerceToString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the string array associated with a particular resource
+ * identifier.
+ * @param id Resource id of the string array
+ */
+ /*package*/ final String[] getResourceStringArray(final int id) {
+ String[] retArray = getArrayStringResource(id);
+ return retArray;
+ }
+
+
+ /*package*/ final boolean getResourceValue(int ident,
+ TypedValue outValue,
+ boolean resolveRefs)
+ {
+ int block = loadResourceValue(ident, outValue, resolveRefs);
+ if (block >= 0) {
+ if (outValue.type != TypedValue.TYPE_STRING) {
+ return true;
+ }
+ outValue.string = mStringBlocks[block].get(outValue.data);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the text array associated with a particular resource
+ * identifier.
+ * @param id Resource id of the string array
+ */
+ /*package*/ final CharSequence[] getResourceTextArray(final int id) {
+ int[] rawInfoArray = getArrayStringInfo(id);
+ int rawInfoArrayLen = rawInfoArray.length;
+ final int infoArrayLen = rawInfoArrayLen / 2;
+ int block;
+ int index;
+ 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;
+ }
+
+ /*package*/ final boolean getThemeValue(int theme, int ident,
+ TypedValue outValue, boolean resolveRefs) {
+ int block = loadThemeAttributeValue(theme, ident, outValue, resolveRefs);
+ if (block >= 0) {
+ if (outValue.type != TypedValue.TYPE_STRING) {
+ return true;
+ }
+ StringBlock[] blocks = mStringBlocks;
+ if (blocks == null) {
+ ensureStringBlocks();
+ }
+ outValue.string = blocks[block].get(outValue.data);
+ return true;
+ }
+ return false;
+ }
+
+ /*package*/ final void ensureStringBlocks() {
+ if (mStringBlocks == null) {
+ synchronized (mSync) {
+ if (mStringBlocks == null) {
+ makeStringBlocks(true);
+ }
+ }
+ }
+ }
+
+ private final void makeStringBlocks(boolean copyFromSystem) {
+ final int sysNum = copyFromSystem ? mSystem.mStringBlocks.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 < sysNum) {
+ mStringBlocks[i] = mSystem.mStringBlocks[i];
+ } else {
+ mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
+ }
+ }
+ }
+
+ /*package*/ final CharSequence getPooledString(int block, int id) {
+ //System.out.println("Get pooled: block=" + block
+ // + ", id=#" + Integer.toHexString(id)
+ // + ", blocks=" + mStringBlocks);
+ return mStringBlocks[block-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 (mSync) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ int asset = openAsset(fileName, accessMode);
+ if (asset != 0) {
+ mNumRefs++;
+ return new AssetInputStream(asset);
+ }
+ }
+ throw new FileNotFoundException("Asset file: " + fileName);
+ }
+
+ public final AssetFileDescriptor openFd(String fileName)
+ throws IOException {
+ synchronized (mSync) {
+ 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 (mSync) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ int asset = openNonAssetNative(cookie, fileName, accessMode);
+ if (asset != 0) {
+ mNumRefs++;
+ return new AssetInputStream(asset);
+ }
+ }
+ 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 (mSync) {
+ 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 (mSync) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ int xmlBlock = openXmlAssetNative(cookie, fileName);
+ if (xmlBlock != 0) {
+ mNumRefs++;
+ return new XmlBlock(this, xmlBlock);
+ }
+ }
+ throw new FileNotFoundException("Asset XML file: " + fileName);
+ }
+
+ /*package*/ void xmlBlockGone() {
+ synchronized (mSync) {
+ decRefsLocked();
+ }
+ }
+
+ /*package*/ final int createTheme() {
+ synchronized (mSync) {
+ if (!mOpen) {
+ throw new RuntimeException("Assetmanager has been closed");
+ }
+ mNumRefs++;
+ return newTheme();
+ }
+ }
+
+ /*package*/ final void releaseTheme(int theme) {
+ synchronized (mSync) {
+ deleteTheme(theme);
+ decRefsLocked();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ destroy();
+ }
+
+ public final class AssetInputStream extends InputStream {
+ public final int getAssetInt() {
+ return mAsset;
+ }
+ private AssetInputStream(int 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.mSync) {
+ if (mAsset != 0) {
+ destroyAsset(mAsset);
+ mAsset = 0;
+ decRefsLocked();
+ }
+ }
+ }
+ 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 int 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. A
+ * zero return value indicates failure.
+ * {@hide}
+ */
+ public native final int addAssetPath(String path);
+
+ /**
+ * 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();
+
+ /**
+ * Change the locale being used by this asset manager. Not for use by
+ * applications.
+ * {@hide}
+ */
+ public native final void setLocale(String locale);
+
+ /**
+ * Get the locales that this asset manager contains data for.
+ */
+ public native final String[] getLocales();
+
+ /**
+ * 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 majorVersion);
+
+ /**
+ * Retrieve the resource identifier for the given resource name.
+ */
+ /*package*/ native final int getResourceIdentifier(String type,
+ String name,
+ 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 int openAsset(String fileName, int accessMode);
+ private final native ParcelFileDescriptor openAssetFd(String fileName,
+ long[] outOffsets) throws IOException;
+ private native final int openNonAssetNative(int cookie, String fileName,
+ int accessMode);
+ private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
+ String fileName, long[] outOffsets) throws IOException;
+ private native final void destroyAsset(int asset);
+ private native final int readAssetChar(int asset);
+ private native final int readAsset(int asset, byte[] b, int off, int len);
+ private native final long seekAsset(int asset, long offset, int whence);
+ private native final long getAssetLength(int asset);
+ private native final long getAssetRemainingLength(int asset);
+
+ /** Returns true if the resource was found, filling in mRetStringBlock and
+ * mRetData. */
+ private native final int loadResourceValue(int ident, 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 = 5;
+ /*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;
+ /*package*/ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
+ /*package*/ native static final boolean applyStyle(int theme,
+ int defStyleAttr, int defStyleRes, int xmlParser,
+ int[] inAttrs, int[] outValues, int[] outIndices);
+ /*package*/ native final boolean retrieveAttributes(
+ int 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 int getNativeStringBlock(int block);
+
+ /**
+ * {@hide}
+ */
+ public native final String getCookieName(int cookie);
+
+ /**
+ * {@hide}
+ */
+ public native static final int getGlobalAssetCount();
+
+ /**
+ * {@hide}
+ */
+ public native static final int getGlobalAssetManagerCount();
+
+ private native final int newTheme();
+ private native final void deleteTheme(int theme);
+ /*package*/ native static final void applyThemeStyle(int theme, int styleRes, boolean force);
+ /*package*/ native static final void copyTheme(int dest, int source);
+ /*package*/ native static final int loadThemeAttributeValue(int theme, int ident,
+ TypedValue outValue,
+ boolean resolve);
+ /*package*/ native static final void dumpTheme(int theme, int priority, String tag, String prefix);
+
+ private native final int 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);
+
+ private native final void init();
+ private native final void destroy();
+
+ private final void decRefsLocked() {
+ mNumRefs--;
+ //System.out.println("Dec streams: mNumRefs=" + mNumRefs
+ // + " mReleased=" + mReleased);
+ if (mNumRefs == 0) {
+ destroy();
+ }
+ }
+}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
new file mode 100644
index 0000000..0f3f270
--- /dev/null
+++ b/core/java/android/content/res/ColorStateList.java
@@ -0,0 +1,327 @@
+/*
+ * 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 com.android.internal.util.ArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.util.AttributeSet;
+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.
+ *
+ * {@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/testcolor1"/>
+ * <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" />
+ * <item android:state_enabled="false" android:colore="@color/testcolor3" />
+ * <item android:state_active="true" android:color="@color/testcolor4" />
+ * <item android:color="@color/testcolor5"/>
+ * </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. The list of state specs will be processed in order of the items in the XML 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. Note that if you have such an item before any other items
+ * in the list then any subsequent items will end up being ignored.
+ */
+public class ColorStateList implements Parcelable {
+
+ private int[][] mStateSpecs; // must be parallel to mColors
+ private int[] mColors; // must be parallel to mStateSpecs
+ private int mDefaultColor = 0xffff0000;
+
+ private static final int[][] EMPTY = new int[][] { new int[0] };
+ private static final SparseArray<WeakReference<ColorStateList>> sCache =
+ new SparseArray<WeakReference<ColorStateList>>();
+
+ private ColorStateList() { }
+
+ /**
+ * Creates a ColorStateList that returns the specified mapping from
+ * states to colors.
+ */
+ public ColorStateList(int[][] states, int[] colors) {
+ mStateSpecs = states;
+ mColors = colors;
+
+ if (states.length > 0) {
+ mDefaultColor = colors[0];
+
+ for (int i = 0; i < states.length; i++) {
+ if (states[i].length == 0) {
+ mDefaultColor = colors[i];
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates or retrieves a ColorStateList that always returns a single color.
+ */
+ public static ColorStateList valueOf(int color) {
+ // TODO: should we collect these eventually?
+ synchronized (sCache) {
+ WeakReference<ColorStateList> ref = sCache.get(color);
+ ColorStateList csl = ref != null ? ref.get() : null;
+
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = new ColorStateList(EMPTY, new int[] { color });
+ sCache.put(color, new WeakReference<ColorStateList>(csl));
+ return csl;
+ }
+ }
+
+ /**
+ * Create a ColorStateList from an XML document, given a set of {@link Resources}.
+ */
+ public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ return createFromXmlInner(r, parser, attrs);
+ }
+
+ /* 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.
+ * Returns null if the tag is not a valid ColorStateList.
+ */
+ private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
+ AttributeSet attrs) throws XmlPullParserException, IOException {
+
+ ColorStateList colorStateList;
+
+ final String name = parser.getName();
+
+ if (name.equals("selector")) {
+ colorStateList = new ColorStateList();
+ } else {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ": invalid drawable tag " + name);
+ }
+
+ colorStateList.inflate(r, parser, attrs);
+ 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).
+ */
+ public ColorStateList withAlpha(int alpha) {
+ int[] colors = new int[mColors.length];
+
+ 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(Resources r, XmlPullParser parser, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+
+ int type;
+
+ final int innerDepth = parser.getDepth()+1;
+ int depth;
+
+ int listAllocated = 20;
+ int listSize = 0;
+ int[] colorList = new int[listAllocated];
+ int[][] stateSpecList = new int[listAllocated][];
+
+ 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;
+ }
+
+ int colorRes = 0;
+ int color = 0xffff0000;
+ boolean haveColor = false;
+
+ int i;
+ int j = 0;
+ final int numAttrs = attrs.getAttributeCount();
+ int[] stateSpec = new int[numAttrs];
+ for (i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ if (stateResId == 0) break;
+ if (stateResId == com.android.internal.R.attr.color) {
+ colorRes = attrs.getAttributeResourceValue(i, 0);
+
+ if (colorRes == 0) {
+ color = attrs.getAttributeIntValue(i, color);
+ haveColor = true;
+ }
+ } else {
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId
+ : -stateResId;
+ }
+ }
+ stateSpec = StateSet.trimStateSet(stateSpec, j);
+
+ if (colorRes != 0) {
+ color = r.getColor(colorRes);
+ } else if (!haveColor) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription()
+ + ": <item> tag requires a 'android:color' attribute.");
+ }
+
+ if (listSize == 0 || stateSpec.length == 0) {
+ mDefaultColor = color;
+ }
+
+ if (listSize + 1 >= listAllocated) {
+ listAllocated = ArrayUtils.idealIntArraySize(listSize + 1);
+
+ int[] ncolor = new int[listAllocated];
+ System.arraycopy(colorList, 0, ncolor, 0, listSize);
+
+ int[][] nstate = new int[listAllocated][];
+ System.arraycopy(stateSpecList, 0, nstate, 0, listSize);
+
+ colorList = ncolor;
+ stateSpecList = nstate;
+ }
+
+ colorList[listSize] = color;
+ stateSpecList[listSize] = stateSpec;
+ listSize++;
+ }
+
+ mColors = new int[listSize];
+ mStateSpecs = new int[listSize][];
+ System.arraycopy(colorList, 0, mColors, 0, listSize);
+ System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
+ }
+
+ public boolean isStateful() {
+ return mStateSpecs.length > 1;
+ }
+
+ /**
+ * 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 not 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(int[] stateSet, int defaultColor) {
+ final int setLength = mStateSpecs.length;
+ for (int i = 0; i < setLength; i++) {
+ 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}.
+ */
+ public int getDefaultColor() {
+ return mDefaultColor;
+ }
+
+ public String toString() {
+ return "ColorStateList{" +
+ "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
+ "mColors=" + Arrays.toString(mColors) +
+ "mDefaultColor=" + mDefaultColor + '}';
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ final int N = mStateSpecs.length;
+ dest.writeInt(N);
+ for (int i=0; i<N; i++) {
+ dest.writeIntArray(mStateSpecs[i]);
+ }
+ dest.writeArray(mStateSpecs);
+ dest.writeIntArray(mColors);
+ }
+
+ public static final Parcelable.Creator<ColorStateList> CREATOR =
+ new Parcelable.Creator<ColorStateList>() {
+ public ColorStateList[] newArray(int size) {
+ return new ColorStateList[size];
+ }
+
+ public ColorStateList createFromParcel(Parcel source) {
+ final int N = source.readInt();
+ int[][] stateSpecs = new int[N][];
+ for (int i=0; i<N; i++) {
+ stateSpecs[i] = source.createIntArray();
+ }
+ int[] colors = source.createIntArray();
+ return new ColorStateList(stateSpecs, colors);
+ }
+ };
+}
diff --git a/core/java/android/content/res/Configuration.aidl b/core/java/android/content/res/Configuration.aidl
new file mode 100755
index 0000000..bb7f2dd
--- /dev/null
+++ b/core/java/android/content/res/Configuration.aidl
@@ -0,0 +1,21 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 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;
+
+parcelable Configuration;
+
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
new file mode 100644
index 0000000..7e4b7ac
--- /dev/null
+++ b/core/java/android/content/res/Configuration.java
@@ -0,0 +1,436 @@
+package android.content.res;
+
+import android.content.pm.ActivityInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+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 and scaling) as well
+ * as dynamic device configuration (various types of input devices).
+ */
+public final class Configuration implements Parcelable, Comparable<Configuration> {
+ /**
+ * Current user preference for the scaling factor for fonts, relative
+ * to the base density scaling.
+ */
+ public float fontScale;
+
+ /**
+ * IMSI MCC (Mobile Country Code). 0 if undefined.
+ */
+ public int mcc;
+
+ /**
+ * IMSI MNC (Mobile Network Code). 0 if undefined.
+ */
+ public int mnc;
+
+ /**
+ * Current user preference for the locale.
+ */
+ public Locale locale;
+
+ /**
+ * Locale should persist on setting
+ * @hide pending API council approval
+ */
+ public boolean userSetLocale;
+
+ public static final int TOUCHSCREEN_UNDEFINED = 0;
+ public static final int TOUCHSCREEN_NOTOUCH = 1;
+ public static final int TOUCHSCREEN_STYLUS = 2;
+ public static final int TOUCHSCREEN_FINGER = 3;
+
+ /**
+ * The kind of touch screen attached to the device.
+ * One of: {@link #TOUCHSCREEN_NOTOUCH}, {@link #TOUCHSCREEN_STYLUS},
+ * {@link #TOUCHSCREEN_FINGER}.
+ */
+ public int touchscreen;
+
+ public static final int KEYBOARD_UNDEFINED = 0;
+ public static final int KEYBOARD_NOKEYS = 1;
+ public static final int KEYBOARD_QWERTY = 2;
+ public static final int KEYBOARD_12KEY = 3;
+
+ /**
+ * The kind of keyboard attached to the device.
+ * One of: {@link #KEYBOARD_QWERTY}, {@link #KEYBOARD_12KEY}.
+ */
+ public int keyboard;
+
+ public static final int KEYBOARDHIDDEN_UNDEFINED = 0;
+ public static final int KEYBOARDHIDDEN_NO = 1;
+ 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;
+
+ public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0;
+ public static final int HARDKEYBOARDHIDDEN_NO = 1;
+ 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;
+
+ public static final int NAVIGATION_UNDEFINED = 0;
+ public static final int NAVIGATION_NONAV = 1;
+ public static final int NAVIGATION_DPAD = 2;
+ public static final int NAVIGATION_TRACKBALL = 3;
+ public static final int NAVIGATION_WHEEL = 4;
+
+ /**
+ * The kind of navigation method available on the device.
+ * One of: {@link #NAVIGATION_DPAD}, {@link #NAVIGATION_TRACKBALL},
+ * {@link #NAVIGATION_WHEEL}.
+ */
+ public int navigation;
+
+ public static final int ORIENTATION_UNDEFINED = 0;
+ public static final int ORIENTATION_PORTRAIT = 1;
+ public static final int ORIENTATION_LANDSCAPE = 2;
+ public static final int ORIENTATION_SQUARE = 3;
+
+ /**
+ * Overall orientation of the screen. May be one of
+ * {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT},
+ * or {@link #ORIENTATION_SQUARE}.
+ */
+ public int orientation;
+
+ /**
+ * Construct an invalid Configuration. You must call {@link #setToDefaults}
+ * for this object to be valid. {@more}
+ */
+ public Configuration() {
+ setToDefaults();
+ }
+
+ /**
+ * Makes a deep copy suitable for modification.
+ */
+ public Configuration(Configuration o) {
+ fontScale = o.fontScale;
+ mcc = o.mcc;
+ mnc = o.mnc;
+ if (o.locale != null) {
+ locale = (Locale) o.locale.clone();
+ }
+ userSetLocale = o.userSetLocale;
+ touchscreen = o.touchscreen;
+ keyboard = o.keyboard;
+ keyboardHidden = o.keyboardHidden;
+ hardKeyboardHidden = o.hardKeyboardHidden;
+ navigation = o.navigation;
+ orientation = o.orientation;
+ }
+
+ public String toString() {
+ return "{ scale=" + fontScale + " imsi=" + mcc + "/" + mnc
+ + " locale=" + locale
+ + " touch=" + touchscreen + " key=" + keyboard + "/"
+ + keyboardHidden + "/" + hardKeyboardHidden
+ + " nav=" + navigation + " orien=" + orientation + " }";
+ }
+
+ /**
+ * Set this object to the system defaults.
+ */
+ public void setToDefaults() {
+ fontScale = 1;
+ mcc = mnc = 0;
+ locale = Locale.getDefault();
+ userSetLocale = false;
+ touchscreen = TOUCHSCREEN_UNDEFINED;
+ keyboard = KEYBOARD_UNDEFINED;
+ keyboardHidden = KEYBOARDHIDDEN_UNDEFINED;
+ hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED;
+ navigation = NAVIGATION_UNDEFINED;
+ orientation = ORIENTATION_UNDEFINED;
+ }
+
+ /** {@hide} */
+ @Deprecated public void makeDefault() {
+ setToDefaults();
+ }
+
+ /**
+ * Copy the fields from delta into this Configuration object, keeping
+ * track of which ones have changed. Any undefined fields in
+ * <var>delta</var> are ignored and not copied in to the current
+ * Configuration.
+ * @return Returns a bit mask of the changed fields, as per
+ * {@link #diff}.
+ */
+ public int updateFrom(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;
+ }
+ if (delta.locale != null
+ && (locale == null || !locale.equals(delta.locale))) {
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ locale = delta.locale != null
+ ? (Locale) delta.locale.clone() : null;
+ }
+ if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
+ {
+ userSetLocale = true;
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ }
+ 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.orientation != ORIENTATION_UNDEFINED
+ && orientation != delta.orientation) {
+ changed |= ActivityInfo.CONFIG_ORIENTATION;
+ orientation = delta.orientation;
+ }
+
+ 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}, or
+ * {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION
+ * PackageManager.ActivityInfo.CONFIG_ORIENTATION}.
+ */
+ public int diff(Configuration delta) {
+ int changed = 0;
+ if (delta.fontScale > 0 && fontScale != delta.fontScale) {
+ changed |= ActivityInfo.CONFIG_FONT_SCALE;
+ }
+ if (delta.mcc != 0 && mcc != delta.mcc) {
+ changed |= ActivityInfo.CONFIG_MCC;
+ }
+ if (delta.mnc != 0 && mnc != delta.mnc) {
+ changed |= ActivityInfo.CONFIG_MNC;
+ }
+ if (delta.locale != null
+ && (locale == null || !locale.equals(delta.locale))) {
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ }
+ if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
+ && touchscreen != delta.touchscreen) {
+ changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
+ }
+ if (delta.keyboard != KEYBOARD_UNDEFINED
+ && keyboard != delta.keyboard) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD;
+ }
+ if (delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED
+ && keyboardHidden != delta.keyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ }
+ if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+ && hardKeyboardHidden != delta.hardKeyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ }
+ if (delta.navigation != NAVIGATION_UNDEFINED
+ && navigation != delta.navigation) {
+ changed |= ActivityInfo.CONFIG_NAVIGATION;
+ }
+ if (delta.orientation != ORIENTATION_UNDEFINED
+ && orientation != delta.orientation) {
+ changed |= ActivityInfo.CONFIG_ORIENTATION;
+ }
+
+ return changed;
+ }
+
+ /**
+ * Determine 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 handled, as given in {@link android.util.TypedValue#changingConfigurations}.
+ *
+ * @return Return true if the resource needs to be loaded, else false.
+ */
+ public static boolean needNewResources(int configChanges, int interestingChanges) {
+ return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0;
+ }
+
+ /**
+ * Parcelable methods
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(fontScale);
+ dest.writeInt(mcc);
+ dest.writeInt(mnc);
+ if (locale == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeString(locale.getLanguage());
+ dest.writeString(locale.getCountry());
+ dest.writeString(locale.getVariant());
+ }
+ 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(orientation);
+ }
+
+ 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) {
+ fontScale = source.readFloat();
+ mcc = source.readInt();
+ mnc = source.readInt();
+ if (source.readInt() != 0) {
+ locale = new Locale(source.readString(), source.readString(),
+ source.readString());
+ }
+ userSetLocale = (source.readInt()==1);
+ touchscreen = source.readInt();
+ keyboard = source.readInt();
+ keyboardHidden = source.readInt();
+ hardKeyboardHidden = source.readInt();
+ navigation = source.readInt();
+ orientation = source.readInt();
+ }
+
+ 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;
+ n = this.locale.getLanguage().compareTo(that.locale.getLanguage());
+ if (n != 0) return n;
+ n = this.locale.getCountry().compareTo(that.locale.getCountry());
+ if (n != 0) return n;
+ n = this.locale.getVariant().compareTo(that.locale.getVariant());
+ 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.orientation - that.orientation;
+ //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() {
+ return ((int)this.fontScale) + this.mcc + this.mnc
+ + this.locale.hashCode() + this.touchscreen
+ + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
+ + this.navigation + this.orientation;
+ }
+}
diff --git a/core/java/android/content/res/PluralRules.java b/core/java/android/content/res/PluralRules.java
new file mode 100644
index 0000000..2dce3c1
--- /dev/null
+++ b/core/java/android/content/res/PluralRules.java
@@ -0,0 +1,111 @@
+/*
+ * 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 java.util.Locale;
+
+/*
+ * Yuck-o. This is not the right way to implement this. When the ICU PluralRules
+ * object has been integrated to android, we should switch to that. For now, yuck-o.
+ */
+
+abstract class PluralRules {
+
+ static final int QUANTITY_OTHER = 0x0000;
+ static final int QUANTITY_ZERO = 0x0001;
+ static final int QUANTITY_ONE = 0x0002;
+ static final int QUANTITY_TWO = 0x0004;
+ static final int QUANTITY_FEW = 0x0008;
+ static final int QUANTITY_MANY = 0x0010;
+
+ static final int ID_OTHER = 0x01000004;
+
+ abstract int quantityForNumber(int n);
+
+ final int attrForNumber(int n) {
+ return PluralRules.attrForQuantity(quantityForNumber(n));
+ }
+
+ static final int attrForQuantity(int quantity) {
+ // see include/utils/ResourceTypes.h
+ switch (quantity) {
+ case QUANTITY_ZERO: return 0x01000005;
+ case QUANTITY_ONE: return 0x01000006;
+ case QUANTITY_TWO: return 0x01000007;
+ case QUANTITY_FEW: return 0x01000008;
+ case QUANTITY_MANY: return 0x01000009;
+ default: return ID_OTHER;
+ }
+ }
+
+ static final String stringForQuantity(int quantity) {
+ switch (quantity) {
+ case QUANTITY_ZERO:
+ return "zero";
+ case QUANTITY_ONE:
+ return "one";
+ case QUANTITY_TWO:
+ return "two";
+ case QUANTITY_FEW:
+ return "few";
+ case QUANTITY_MANY:
+ return "many";
+ default:
+ return "other";
+ }
+ }
+
+ static final PluralRules ruleForLocale(Locale locale) {
+ String lang = locale.getLanguage();
+ if ("cs".equals(lang)) {
+ if (cs == null) cs = new cs();
+ return cs;
+ }
+ else {
+ if (en == null) en = new en();
+ return en;
+ }
+ }
+
+ private static PluralRules cs;
+ private static class cs extends PluralRules {
+ int quantityForNumber(int n) {
+ if (n == 1) {
+ return QUANTITY_ONE;
+ }
+ else if (n >= 2 && n <= 4) {
+ return QUANTITY_FEW;
+ }
+ else {
+ return QUANTITY_OTHER;
+ }
+ }
+ }
+
+ private static PluralRules en;
+ private static class en extends PluralRules {
+ int quantityForNumber(int n) {
+ if (n == 1) {
+ return QUANTITY_ONE;
+ }
+ else {
+ return QUANTITY_OTHER;
+ }
+ }
+ }
+}
+
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
new file mode 100644
index 0000000..1a963f6
--- /dev/null
+++ b/core/java/android/content/res/Resources.java
@@ -0,0 +1,1890 @@
+/*
+ * 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 com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+
+/**
+ * Class for accessing an application's resources. This sits on top of the
+ * asset manager of the application (accessible through getAssets()) and
+ * provides a higher-level API for getting typed data from the assets.
+ */
+public class Resources {
+ static final String TAG = "Resources";
+ private static final boolean DEBUG_LOAD = false;
+ private static final boolean DEBUG_CONFIG = false;
+ private static final boolean TRACE_FOR_PRELOAD = false;
+
+ private static final int sSdkVersion = SystemProperties.getInt(
+ "ro.build.version.sdk", 0);
+ private static final Object mSync = new Object();
+ private static Resources mSystem = null;
+
+ // 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 SparseArray<Drawable.ConstantState> mPreloadedDrawables
+ = new SparseArray<Drawable.ConstantState>();
+ private static final SparseArray<ColorStateList> mPreloadedColorStateLists
+ = new SparseArray<ColorStateList>();
+ private static boolean mPreloaded;
+
+ /*package*/ final TypedValue mTmpValue = new TypedValue();
+
+ // These are protected by the mTmpValue lock.
+ private final SparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
+ = new SparseArray<WeakReference<Drawable.ConstantState> >();
+ private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
+ = new SparseArray<WeakReference<ColorStateList> >();
+ private boolean mPreloading;
+
+ /*package*/ TypedArray mCachedStyledAttributes = null;
+
+ private int mLastCachedXmlBlockIndex = -1;
+ private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
+ private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
+
+ /*package*/ final AssetManager mAssets;
+ private final Configuration mConfiguration = new Configuration();
+ /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
+ PluralRules mPluralRule;
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Create a new Resources object on top of an existing set of assets in an
+ * AssetManager.
+ *
+ * @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).
+ */
+ public Resources(AssetManager assets, DisplayMetrics metrics,
+ Configuration config) {
+ mAssets = assets;
+ mConfiguration.setToDefaults();
+ mMetrics.setToDefaults();
+ updateConfiguration(config, metrics);
+ assets.ensureStringBlocks();
+ }
+
+ /**
+ * 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 (mSync) {
+ Resources ret = mSystem;
+ if (ret == null) {
+ ret = new Resources();
+ mSystem = ret;
+ }
+
+ return ret;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public CharSequence getText(int id) throws NotFoundException {
+ CharSequence res = mAssets.getResourceText(id);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("String resource ID #0x"
+ + Integer.toHexString(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 CharSequence The string data associated with the resource, plus
+ * possibly styled text information.
+ */
+ public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+ PluralRules rule = getPluralRule();
+ CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity));
+ if (res != null) {
+ return res;
+ }
+ res = mAssets.getResourceBagText(id, PluralRules.ID_OTHER);
+ if (res != null) {
+ return res;
+ }
+ throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
+ + " quantity=" + quantity
+ + " item=" + PluralRules.stringForQuantity(rule.quantityForNumber(quantity)));
+ }
+
+ private PluralRules getPluralRule() {
+ synchronized (mSync) {
+ if (mPluralRule == null) {
+ mPluralRule = PluralRules.ruleForLocale(mConfiguration.locale);
+ }
+ return mPluralRule;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public String getString(int id) throws NotFoundException {
+ CharSequence res = getText(id);
+ if (res != null) {
+ return res.toString();
+ }
+ throw new NotFoundException("String resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+
+ /**
+ * 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.
+ */
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ String raw = getString(id);
+ return String.format(mConfiguration.locale, raw, formatArgs);
+ }
+
+ /**
+ * Return the string value associated with a particular resource ID for a particular
+ * numerical quantity, 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 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.
+ */
+ public String getQuantityString(int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ String raw = getQuantityText(id, quantity).toString();
+ return String.format(mConfiguration.locale, raw, formatArgs);
+ }
+
+ /**
+ * Return the string value associated with a particular resource ID for a particular
+ * numerical quantity.
+ *
+ * @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.
+ */
+ public String getQuantityString(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(int id, CharSequence def) {
+ CharSequence res = id != 0 ? mAssets.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.
+ */
+ public CharSequence[] getTextArray(int id) throws NotFoundException {
+ CharSequence[] res = mAssets.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.
+ */
+ public String[] getStringArray(int id) throws NotFoundException {
+ String[] res = mAssets.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.
+ */
+ public int[] getIntArray(int id) throws NotFoundException {
+ int[] res = mAssets.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.
+ */
+ public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ int len = mAssets.getArraySize(id);
+ if (len < 0) {
+ throw new NotFoundException("Array resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+ TypedArray array = getCachedStyledAttributes(len);
+ array.mLength = mAssets.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(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimension(value.data, mMetrics);
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /**
+ * 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(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(
+ value.data, mMetrics);
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /**
+ * 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(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(
+ value.data, mMetrics);
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /**
+ * 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(int id, int base, int pbase) {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ 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");
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @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 Drawable An object that can be used to draw this resource.
+ */
+ public Drawable getDrawable(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ return loadDrawable(value, id);
+ }
+ }
+
+ /**
+ * 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(int id) throws NotFoundException {
+ InputStream is = openRawResource(id);
+ Movie movie = Movie.decodeStream(is);
+ try {
+ is.close();
+ }
+ catch (java.io.IOException e) {
+ // don't care, since the return value is valid
+ }
+ return movie;
+ }
+
+ /**
+ * Return a color integer associated with a particular resource ID.
+ * If the resource holds a complex
+ * {@link android.content.res.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 Returns a single color value in the form 0xAARRGGBB.
+ */
+ public int getColor(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ 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) {
+ ColorStateList csl = loadColorStateList(mTmpValue, id);
+ return csl.getDefaultColor();
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /**
+ * Return a color state list associated with a particular resource ID. The
+ * resource may contain either a single raw color value, or a complex
+ * {@link android.content.res.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 Returns a ColorStateList object containing either a single
+ * solid color or multiple colors that can be selected based on a state.
+ */
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ return loadColorStateList(value, id);
+ }
+ }
+
+ /**
+ * 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(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ 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");
+ }
+ }
+
+ /**
+ * 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(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ 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");
+ }
+ }
+
+ /**
+ * 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(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(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(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 appt
+ * tool.
+ *
+ * @return InputStream Access to the resource data.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ */
+ public InputStream openRawResource(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ return openRawResource(id, mTmpValue);
+ }
+ }
+
+ /**
+ * 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 appt 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.
+ *
+ * @hide Pending API council approval
+ */
+ public InputStream openRawResource(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) {
+ NotFoundException rnf = new NotFoundException("File " + value.string.toString() +
+ " from drawable resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ }
+
+ /**
+ * 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 appt
+ * 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(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+
+ try {
+ return mAssets.openNonAssetFd(
+ value.assetCookie, value.string.toString());
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + value.string.toString()
+ + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+
+ }
+ }
+
+ /**
+ * 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(int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ boolean found = mAssets.getResourceValue(id, outValue, resolveRefs);
+ if (found) {
+ return;
+ }
+ throw new NotFoundException("Resource ID #0x"
+ + Integer.toHexString(id));
+ }
+
+ /**
+ * 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 {
+ int id = getIdentifier(name, "string", null);
+ if (id != 0) {
+ getValue(id, outValue, resolveRefs);
+ return;
+ }
+ throw new NotFoundException("String resource name " + name);
+ }
+
+ /**
+ * 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 {
+ /**
+ * 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) {
+ AssetManager.applyThemeStyle(mTheme, 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) {
+ AssetManager.copyTheme(mTheme, other.mTheme);
+ }
+
+ /**
+ * Return a StyledAttributes holding the values defined by
+ * <var>Theme</var> which are listed in <var>attrs</var>.
+ *
+ * <p>Be sure to call StyledAttributes.recycle() when you are done with
+ * the array.
+ *
+ * @param attrs The desired attributes.
+ *
+ * @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(int[] attrs) {
+ int len = attrs.length;
+ TypedArray array = getCachedStyledAttributes(len);
+ array.mRsrcs = attrs;
+ AssetManager.applyStyle(mTheme, 0, 0, 0, attrs,
+ array.mData, array.mIndices);
+ return array;
+ }
+
+ /**
+ * Return a StyledAttributes holding the values defined by the style
+ * resource <var>resid</var> which are listed in <var>attrs</var>.
+ *
+ * <p>Be sure to call StyledAttributes.recycle() when you are done with
+ * the array.
+ *
+ * @param resid The desired style resource.
+ * @param attrs The desired attributes in the style.
+ *
+ * @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(int resid, int[] attrs)
+ throws NotFoundException {
+ int len = attrs.length;
+ TypedArray array = getCachedStyledAttributes(len);
+ array.mRsrcs = attrs;
+
+ AssetManager.applyStyle(mTheme, 0, resid, 0, attrs,
+ array.mData, array.mIndices);
+ if (false) {
+ int[] data = array.mData;
+
+ System.out.println("**********************************************************");
+ System.out.println("**********************************************************");
+ System.out.println("**********************************************************");
+ System.out.println("Attributes:");
+ String s = " Attrs:";
+ int i;
+ for (i=0; i<attrs.length; i++) {
+ s = s + " 0x" + Integer.toHexString(attrs[i]);
+ }
+ System.out.println(s);
+ s = " Found:";
+ TypedValue value = new TypedValue();
+ for (i=0; i<attrs.length; i++) {
+ int d = i*AssetManager.STYLE_NUM_ENTRIES;
+ value.type = data[d+AssetManager.STYLE_TYPE];
+ value.data = data[d+AssetManager.STYLE_DATA];
+ value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
+ value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
+ s = s + " 0x" + Integer.toHexString(attrs[i])
+ + "=" + value;
+ }
+ System.out.println(s);
+ }
+ return array;
+ }
+
+ /**
+ * Return a StyledAttributes 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 StyledAttributes.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.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the StyledAttributes. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the StyledAttributes,
+ * 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,
+ int[] attrs, int defStyleAttr, int defStyleRes) {
+ int len = attrs.length;
+ TypedArray array = getCachedStyledAttributes(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;
+ AssetManager.applyStyle(
+ mTheme, defStyleAttr, defStyleRes,
+ parser != null ? parser.mParseState : 0, attrs,
+ array.mData, array.mIndices);
+
+ array.mRsrcs = attrs;
+ array.mXml = parser;
+
+ if (false) {
+ int[] data = array.mData;
+
+ System.out.println("Attributes:");
+ String s = " Attrs:";
+ int i;
+ for (i=0; i<set.getAttributeCount(); i++) {
+ s = s + " " + set.getAttributeName(i);
+ int id = set.getAttributeNameResource(i);
+ if (id != 0) {
+ s = s + "(0x" + Integer.toHexString(id) + ")";
+ }
+ s = s + "=" + set.getAttributeValue(i);
+ }
+ System.out.println(s);
+ s = " Found:";
+ TypedValue value = new TypedValue();
+ for (i=0; i<attrs.length; i++) {
+ int d = i*AssetManager.STYLE_NUM_ENTRIES;
+ value.type = data[d+AssetManager.STYLE_TYPE];
+ value.data = data[d+AssetManager.STYLE_DATA];
+ value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
+ value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
+ s = s + " 0x" + Integer.toHexString(attrs[i])
+ + "=" + value;
+ }
+ System.out.println(s);
+ }
+
+ return array;
+ }
+
+ /**
+ * 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) {
+ boolean got = mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
+ if (false) {
+ System.out.println(
+ "resolveAttribute #" + Integer.toHexString(resid)
+ + " got=" + got + ", type=0x" + Integer.toHexString(outValue.type)
+ + ", data=0x" + Integer.toHexString(outValue.data));
+ }
+ return got;
+ }
+
+ /**
+ * 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) {
+ AssetManager.dumpTheme(mTheme, priority, tag, prefix);
+ }
+
+ protected void finalize() throws Throwable {
+ super.finalize();
+ mAssets.releaseTheme(mTheme);
+ }
+
+ /*package*/ Theme() {
+ mAssets = Resources.this.mAssets;
+ mTheme = mAssets.createTheme();
+ }
+
+ private final AssetManager mAssets;
+ private final int mTheme;
+ }
+
+ /**
+ * 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() {
+ return new 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.
+ * @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, int[] attrs) {
+ int len = attrs.length;
+ TypedArray array = getCachedStyledAttributes(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;
+ mAssets.retrieveAttributes(parser.mParseState, attrs,
+ array.mData, array.mIndices);
+
+ array.mRsrcs = attrs;
+ array.mXml = parser;
+
+ return array;
+ }
+
+ /**
+ * Store the newly updated configuration.
+ */
+ public void updateConfiguration(Configuration config,
+ DisplayMetrics metrics) {
+ synchronized (mTmpValue) {
+ int configChanges = 0xfffffff;
+ if (config != null) {
+ configChanges = mConfiguration.updateFrom(config);
+ }
+ if (metrics != null) {
+ mMetrics.setTo(metrics);
+ }
+ mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
+ String locale = null;
+ if (mConfiguration.locale != null) {
+ locale = mConfiguration.locale.getLanguage();
+ if (mConfiguration.locale.getCountry() != null) {
+ locale += "-" + mConfiguration.locale.getCountry();
+ }
+ }
+ 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;
+ }
+ int keyboardHidden = mConfiguration.keyboardHidden;
+ if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+ && mConfiguration.hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
+ }
+ mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
+ locale, mConfiguration.orientation,
+ mConfiguration.touchscreen,
+ (int)(mMetrics.density*160), mConfiguration.keyboard,
+ keyboardHidden, mConfiguration.navigation, width, height,
+ sSdkVersion);
+ int N = mDrawableCache.size();
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "Cleaning up drawables config changes: 0x"
+ + Integer.toHexString(configChanges));
+ }
+ for (int i=0; i<N; i++) {
+ WeakReference<Drawable.ConstantState> ref = mDrawableCache.valueAt(i);
+ if (ref != null) {
+ Drawable.ConstantState cs = ref.get();
+ if (cs != null) {
+ if (Configuration.needNewResources(
+ configChanges, cs.getChangingConfigurations())) {
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "FLUSHING #0x"
+ + Integer.toHexString(mDrawableCache.keyAt(i))
+ + " / " + cs + " with changes: 0x"
+ + Integer.toHexString(cs.getChangingConfigurations()));
+ }
+ mDrawableCache.setValueAt(i, null);
+ } else if (DEBUG_CONFIG) {
+ Log.d(TAG, "(Keeping #0x"
+ + Integer.toHexString(mDrawableCache.keyAt(i))
+ + " / " + cs + " with changes: 0x"
+ + Integer.toHexString(cs.getChangingConfigurations())
+ + ")");
+ }
+ }
+ }
+ }
+ mDrawableCache.clear();
+ mColorStateListCache.clear();
+ flushLayoutCache();
+ }
+ synchronized (mSync) {
+ if (mPluralRule != null) {
+ mPluralRule = PluralRules.ruleForLocale(config.locale);
+ }
+ }
+ }
+
+ /**
+ * Update the system resources configuration if they have previously
+ * been initialized.
+ *
+ * @hide
+ */
+ public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+ if (mSystem != null) {
+ mSystem.updateConfiguration(config, metrics);
+ //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 mMetrics;
+ }
+
+ /**
+ * 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 mConfiguration;
+ }
+
+ /**
+ * 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) {
+ try {
+ return Integer.parseInt(name);
+ } catch (Exception e) {
+ // Ignore
+ }
+ return mAssets.getResourceIdentifier(name, defType, defPackage);
+ }
+
+ /**
+ * 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(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));
+ }
+
+ /**
+ * 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(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));
+ }
+
+ /**
+ * 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(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));
+ }
+
+ /**
+ * 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(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));
+ }
+
+ /**
+ * 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 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 mAssets;
+ }
+
+ /**
+ * 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() {
+ synchronized (mCachedXmlBlockIds) {
+ // First see if this block is in our cache.
+ final int num = mCachedXmlBlockIds.length;
+ for (int i=0; i<num; i++) {
+ mCachedXmlBlockIds[i] = -0;
+ XmlBlock oldBlock = mCachedXmlBlocks[i];
+ if (oldBlock != null) {
+ oldBlock.close();
+ }
+ mCachedXmlBlocks[i] = null;
+ }
+ }
+ }
+
+ /**
+ * 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 (mSync) {
+ if (mPreloaded) {
+ throw new IllegalStateException("Resources already preloaded");
+ }
+ mPreloaded = true;
+ mPreloading = true;
+ }
+ }
+
+ /**
+ * Called by zygote when it is done preloading resources, to change back
+ * to normal Resources operation.
+ */
+ public final void finishPreloading() {
+ if (mPreloading) {
+ mPreloading = false;
+ flushLayoutCache();
+ }
+ }
+
+ /*package*/ Drawable loadDrawable(TypedValue value, int id)
+ 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("PreloadDrawable", name);
+ }
+ }
+
+ final int key = (value.assetCookie << 24) | value.data;
+ Drawable dr = getCachedDrawable(key);
+
+ if (dr != null) {
+ return dr;
+ }
+
+ Drawable.ConstantState cs = mPreloadedDrawables.get(key);
+ if (cs != null) {
+ dr = cs.newDrawable();
+ } else {
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ dr = new ColorDrawable(value.data);
+ }
+
+ if (dr == null) {
+ if (value.string == null) {
+ throw new NotFoundException(
+ "Resource is not a Drawable (color or path): " + value);
+ }
+
+ String file = value.string.toString();
+
+ if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
+ + value.assetCookie + ": " + file);
+
+ if (file.endsWith(".xml")) {
+ try {
+ XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXml(this, rp);
+ rp.close();
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+
+ } else {
+ try {
+ InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_BUFFER);
+ // System.out.println("Opened file " + file + ": " + is);
+ dr = Drawable.createFromResourceStream(this, value, is, file);
+ is.close();
+ // System.out.println("Created stream: " + dr);
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ }
+ }
+ }
+
+ if (dr != null) {
+ dr.setChangingConfigurations(value.changingConfigurations);
+ cs = dr.getConstantState();
+ if (cs != null) {
+ if (mPreloading) {
+ mPreloadedDrawables.put(key, cs);
+ } else {
+ synchronized (mTmpValue) {
+ //Log.i(TAG, "Saving cached drawable @ #" +
+ // Integer.toHexString(key.intValue())
+ // + " in " + this + ": " + cs);
+ mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
+ }
+ }
+ }
+ }
+
+ return dr;
+ }
+
+ private Drawable getCachedDrawable(int key) {
+ synchronized (mTmpValue) {
+ WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key);
+ if (wr != null) { // we have the key
+ Drawable.ConstantState entry = wr.get();
+ if (entry != null) {
+ //Log.i(TAG, "Returning cached drawable @ #" +
+ // Integer.toHexString(((Integer)key).intValue())
+ // + " in " + this + ": " + entry);
+ return entry.newDrawable();
+ }
+ else { // our entry has been purged
+ mDrawableCache.delete(key);
+ }
+ }
+ }
+ return null;
+ }
+
+ /*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
+ 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 int key = (value.assetCookie << 24) | value.data;
+
+ ColorStateList csl;
+
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+
+ csl = mPreloadedColorStateLists.get(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = ColorStateList.valueOf(value.data);
+ if (mPreloading) {
+ mPreloadedColorStateLists.put(key, csl);
+ }
+
+ return csl;
+ }
+
+ csl = getCachedColorStateList(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = mPreloadedColorStateLists.get(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ if (value.string == null) {
+ throw new NotFoundException(
+ "Resource is not a ColorStateList (color or path): " + value);
+ }
+
+ String file = value.string.toString();
+
+ if (file.endsWith(".xml")) {
+ try {
+ XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "colorstatelist");
+ csl = ColorStateList.createFromXml(this, rp);
+ rp.close();
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from color state list 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");
+ }
+
+ if (csl != null) {
+ if (mPreloading) {
+ mPreloadedColorStateLists.put(key, csl);
+ } else {
+ synchronized (mTmpValue) {
+ //Log.i(TAG, "Saving cached color state list @ #" +
+ // Integer.toHexString(key.intValue())
+ // + " in " + this + ": " + csl);
+ mColorStateListCache.put(
+ key, new WeakReference<ColorStateList>(csl));
+ }
+ }
+ }
+
+ return csl;
+ }
+
+ private ColorStateList getCachedColorStateList(int key) {
+ synchronized (mTmpValue) {
+ WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
+ if (wr != null) { // we have the key
+ ColorStateList entry = wr.get();
+ if (entry != null) {
+ //Log.i(TAG, "Returning cached color state list @ #" +
+ // Integer.toHexString(((Integer)key).intValue())
+ // + " in " + this + ": " + entry);
+ return entry;
+ }
+ else { // our entry has been purged
+ mColorStateListCache.delete(key);
+ }
+ }
+ }
+ return null;
+ }
+
+ /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
+ throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_STRING) {
+ return loadXmlResourceParser(value.string.toString(), id,
+ value.assetCookie, type);
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
+ int assetCookie, String type) throws NotFoundException {
+ if (id != 0) {
+ try {
+ // These may be compiled...
+ synchronized (mCachedXmlBlockIds) {
+ // First see if this block is in our cache.
+ final int num = mCachedXmlBlockIds.length;
+ for (int i=0; i<num; i++) {
+ if (mCachedXmlBlockIds[i] == id) {
+ //System.out.println("**** REUSING XML BLOCK! id="
+ // + id + ", index=" + i);
+ return mCachedXmlBlocks[i].newParser();
+ }
+ }
+
+ // Not in the cache, create a new block and put it at
+ // the next slot in the cache.
+ XmlBlock block = mAssets.openXmlBlockAsset(
+ assetCookie, file);
+ if (block != null) {
+ int pos = mLastCachedXmlBlockIndex+1;
+ if (pos >= num) pos = 0;
+ mLastCachedXmlBlockIndex = pos;
+ XmlBlock oldBlock = mCachedXmlBlocks[pos];
+ if (oldBlock != null) {
+ oldBlock.close();
+ }
+ mCachedXmlBlockIds[pos] = id;
+ mCachedXmlBlocks[pos] = block;
+ //System.out.println("**** CACHING NEW XML BLOCK! id="
+ // + id + ", index=" + pos);
+ return block.newParser();
+ }
+ }
+ } catch (Exception e) {
+ 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));
+ }
+
+ private TypedArray getCachedStyledAttributes(int len) {
+ synchronized (mTmpValue) {
+ TypedArray attrs = mCachedStyledAttributes;
+ if (attrs != null) {
+ mCachedStyledAttributes = null;
+
+ attrs.mLength = len;
+ int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ if (attrs.mData.length >= fullLen) {
+ return attrs;
+ }
+ attrs.mData = new int[fullLen];
+ attrs.mIndices = new int[1+len];
+ return attrs;
+ }
+ return new TypedArray(this,
+ new int[len*AssetManager.STYLE_NUM_ENTRIES],
+ new int[1+len], len);
+ }
+ }
+
+ private Resources() {
+ mAssets = AssetManager.getSystem();
+ // NOTE: Intentionally leaving this uninitialized (all values set
+ // to zero), so that anyone who tries to do something that requires
+ // metrics will get a very wrong value.
+ mConfiguration.setToDefaults();
+ mMetrics.setToDefaults();
+ updateConfiguration(null, null);
+ mAssets.ensureStringBlocks();
+ }
+}
+
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
new file mode 100644
index 0000000..e684cb8
--- /dev/null
+++ b/core/java/android/content/res/StringBlock.java
@@ -0,0 +1,395 @@
+/*
+ * 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.text.*;
+import android.text.style.*;
+import android.util.Config;
+import android.util.Log;
+import android.util.SparseArray;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import com.android.internal.util.XmlUtils;
+
+/**
+ * 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 = Config.LOGV || false;
+
+ private final int 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: " + style);
+ if (style != null) {
+ if (mStyleIDs == null) {
+ mStyleIDs = new StyleIDs();
+ mStyleIDs.boldId = nativeIndexOfString(mNative, "b");
+ mStyleIDs.italicId = nativeIndexOfString(mNative, "i");
+ mStyleIDs.underlineId = nativeIndexOfString(mNative, "u");
+ mStyleIDs.ttId = nativeIndexOfString(mNative, "tt");
+ mStyleIDs.bigId = nativeIndexOfString(mNative, "big");
+ mStyleIDs.smallId = nativeIndexOfString(mNative, "small");
+ mStyleIDs.supId = nativeIndexOfString(mNative, "sup");
+ mStyleIDs.subId = nativeIndexOfString(mNative, "sub");
+ mStyleIDs.strikeId = nativeIndexOfString(mNative, "strike");
+ mStyleIDs.listItemId = nativeIndexOfString(mNative, "li");
+ mStyleIDs.marqueeId = nativeIndexOfString(mNative, "marquee");
+
+ if (localLOGV) Log.v(TAG, "BoldId=" + mStyleIDs.boldId
+ + ", ItalicId=" + mStyleIDs.italicId
+ + ", UnderlineId=" + mStyleIDs.underlineId);
+ }
+
+ res = applyStyles(str, style, mStyleIDs);
+ }
+ if (mStrings != null) mStrings[idx] = res;
+ else mSparseStrings.put(idx, res);
+ return res;
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ if (mOwnsNative) {
+ nativeDestroy(mNative);
+ }
+ }
+
+ static final class StyleIDs {
+ private int boldId;
+ private int italicId;
+ private int underlineId;
+ private int ttId;
+ private int bigId;
+ private int smallId;
+ private int subId;
+ private int supId;
+ private int strikeId;
+ private int listItemId;
+ private int marqueeId;
+ }
+
+ 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),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ sub = subtag(tag, ";fgcolor=");
+ if (sub != null) {
+ int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
+ buffer.setSpan(new ForegroundColorSpan(color),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ sub = subtag(tag, ";bgcolor=");
+ if (sub != null) {
+ int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
+ buffer.setSpan(new BackgroundColorSpan(color),
+ 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);
+ }
+
+ /**
+ * 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 {
+ 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) {
+ if (fm.bottom - fm.top < mSize) {
+ fm.top = fm.bottom - mSize;
+ fm.ascent = fm.ascent - mSize;
+ } 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 (mSize - fm.descent >= need) {
+ /*
+ * It is safe to shrink the ascent this much.
+ */
+
+ fm.top = fm.bottom - mSize;
+ fm.ascent = fm.descent - mSize;
+ } else if (mSize >= 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 + mSize;
+ } else {
+ /*
+ * Show as much of the ascent as we can, and no descent.
+ */
+
+ fm.top = fm.ascent = -mSize;
+ 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(int obj, boolean useSparse) {
+ mNative = obj;
+ mUseSparse = useSparse;
+ mOwnsNative = false;
+ if (localLOGV) Log.v(TAG, "Created string block " + this
+ + ": " + nativeGetSize(mNative));
+ }
+
+ private static final native int nativeCreate(byte[] data,
+ int offset,
+ int size);
+ private static final native int nativeGetSize(int obj);
+ private static final native String nativeGetString(int obj, int idx);
+ private static final native int[] nativeGetStyle(int obj, int idx);
+ private static final native int nativeIndexOfString(int obj, String str);
+ private static final native void nativeDestroy(int obj);
+}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
new file mode 100644
index 0000000..3a32c03
--- /dev/null
+++ b/core/java/android/content/res/TypedArray.java
@@ -0,0 +1,688 @@
+package android.content.res;
+
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import com.android.internal.util.XmlUtils;
+
+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 {
+ private final Resources mResources;
+ /*package*/ XmlBlock.Parser mXml;
+ /*package*/ int[] mRsrcs;
+ /*package*/ int[] mData;
+ /*package*/ int[] mIndices;
+ /*package*/ int mLength;
+ private TypedValue mValue = new TypedValue();
+
+ /**
+ * Return the number of values in this array.
+ */
+ public int length() {
+ return mLength;
+ }
+
+ /**
+ * Return the number of indices in the array that actually have data.
+ */
+ public int getIndexCount() {
+ return mIndices[0];
+ }
+
+ /**
+ * Return an index in the array that has data.
+ *
+ * @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.
+ */
+ public int getIndex(int at) {
+ return mIndices[1+at];
+ }
+
+ /**
+ * Return the Resources object this array was loaded from.
+ */
+ public Resources getResources() {
+ return mResources;
+ }
+
+ /**
+ * 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.
+ */
+ public CharSequence getText(int index) {
+ 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);
+ }
+
+ TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ Log.w(Resources.TAG, "Converting to string: " + v);
+ return v.coerceToString();
+ }
+ Log.w(Resources.TAG, "getString of bad type: 0x"
+ + Integer.toHexString(type));
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public String getString(int index) {
+ 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();
+ }
+
+ TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ Log.w(Resources.TAG, "Converting to string: " + v);
+ CharSequence cs = v.coerceToString();
+ return cs != null ? cs.toString() : null;
+ }
+ Log.w(Resources.TAG, "getString of bad type: 0x"
+ + Integer.toHexString(type));
+ return null;
+ }
+
+ /**
+ * Retrieve 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 null if the attribute is not defined or is not
+ * an immediate string value.
+ */
+ public String getNonResourceString(int index) {
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ public boolean getBoolean(int index, boolean defValue) {
+ 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;
+ }
+
+ TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ Log.w(Resources.TAG, "Converting to boolean: " + v);
+ return XmlUtils.convertValueToBoolean(
+ v.coerceToString(), defValue);
+ }
+ Log.w(Resources.TAG, "getBoolean of bad type: 0x"
+ + Integer.toHexString(type));
+ return 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.
+ */
+ public int getInt(int index, int defValue) {
+ 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];
+ }
+
+ TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ Log.w(Resources.TAG, "Converting to int: " + v);
+ return XmlUtils.convertValueToInt(
+ v.coerceToString(), defValue);
+ }
+ Log.w(Resources.TAG, "getInt of bad type: 0x"
+ + Integer.toHexString(type));
+ 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..
+ */
+ public float getFloat(int index, float defValue) {
+ 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];
+ }
+
+ TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ Log.w(Resources.TAG, "Converting to float: " + v);
+ CharSequence str = v.coerceToString();
+ if (str != null) {
+ return Float.parseFloat(str.toString());
+ }
+ }
+ Log.w(Resources.TAG, "getFloat of bad type: 0x"
+ + Integer.toHexString(type));
+ 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.
+ */
+ public int getColor(int index, int defValue) {
+ 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)) {
+ ColorStateList csl = mResources.loadColorStateList(
+ value, value.resourceId);
+ return csl.getDefaultColor();
+ }
+ return defValue;
+ }
+
+ throw new UnsupportedOperationException("Can't convert to color: type=0x"
+ + Integer.toHexString(type));
+ }
+
+ /**
+ * 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.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return ColorStateList for the attribute, or null if not defined.
+ */
+ public ColorStateList getColorStateList(int index) {
+ final TypedValue value = mValue;
+ if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ return mResources.loadColorStateList(value, value.resourceId);
+ }
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public int getInteger(int index, int defValue) {
+ 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];
+ }
+
+ throw new UnsupportedOperationException("Can't convert 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.
+ *
+ * @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
+ */
+ public float getDimension(int index, float defValue) {
+ 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], mResources.mMetrics);
+ }
+
+ throw new UnsupportedOperationException("Can't convert 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.
+ *
+ * @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
+ */
+ public int getDimensionPixelOffset(int index, int defValue) {
+ 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], mResources.mMetrics);
+ }
+
+ throw new UnsupportedOperationException("Can't convert 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.
+ *
+ * @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
+ */
+ public int getDimensionPixelSize(int index, int defValue) {
+ 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], mResources.mMetrics);
+ }
+
+ throw new UnsupportedOperationException("Can't convert 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}.
+ *
+ * @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.
+ */
+ public int getLayoutDimension(int index, String name) {
+ 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], mResources.mMetrics);
+ }
+
+ throw new RuntimeException(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.
+ */
+ public int getLayoutDimension(int index, int defValue) {
+ 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], mResources.mMetrics);
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve a fractional unit attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value, or defValue if not defined.
+ */
+ public float getFraction(int index, int base, int pbase, float defValue) {
+ 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);
+ }
+
+ throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
+ + Integer.toHexString(type));
+ }
+
+ /**
+ * 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.
+ */
+ public int getResourceId(int index, int defValue) {
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ public Drawable getDrawable(int index) {
+ final TypedValue value = mValue;
+ if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (false) {
+ System.out.println("******************************************************************");
+ System.out.println("Got drawable resource: type="
+ + value.type
+ + " str=" + value.string
+ + " int=0x" + Integer.toHexString(value.data)
+ + " cookie=" + value.assetCookie);
+ System.out.println("******************************************************************");
+ }
+ return mResources.loadDrawable(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[].
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence[] for the attribute, or null if not defined.
+ */
+ public CharSequence[] getTextArray(int index) {
+ final TypedValue value = mValue;
+ if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (false) {
+ System.out.println("******************************************************************");
+ System.out.println("Got drawable resource: type="
+ + value.type
+ + " str=" + value.string
+ + " int=0x" + Integer.toHexString(value.data)
+ + " cookie=" + value.assetCookie);
+ System.out.println("******************************************************************");
+ }
+ 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 Returns true if the value was retrieved, else false.
+ */
+ public boolean getValue(int index, TypedValue outValue) {
+ return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
+ }
+
+ /**
+ * 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.
+ */
+ public boolean hasValue(int index) {
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ return type != TypedValue.TYPE_NULL;
+ }
+
+ /**
+ * 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.)
+ */
+ public TypedValue peekValue(int index) {
+ 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.
+ */
+ public String getPositionDescription() {
+ return mXml != null ? mXml.getPositionDescription() : "<internal>";
+ }
+
+ /**
+ * Give back a previously retrieved StyledAttributes, for later re-use.
+ */
+ public void recycle() {
+ synchronized (mResources.mTmpValue) {
+ TypedArray cached = mResources.mCachedStyledAttributes;
+ if (cached == null || cached.mData.length < mData.length) {
+ mXml = null;
+ mResources.mCachedStyledAttributes = this;
+ }
+ }
+ }
+
+ 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 = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
+ if (type == TypedValue.TYPE_STRING) {
+ outValue.string = loadStringValueAt(index);
+ }
+ 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;
+ }
+ //System.out.println("Getting pooled from: " + v);
+ return mResources.mAssets.getPooledString(
+ cookie, data[index+AssetManager.STYLE_DATA]);
+ }
+
+ /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
+ mResources = resources;
+ mData = data;
+ mIndices = indices;
+ mLength = len;
+ }
+
+ public String toString() {
+ return Arrays.toString(mData);
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
new file mode 100644
index 0000000..6336678
--- /dev/null
+++ b/core/java/android/content/res/XmlBlock.java
@@ -0,0 +1,515 @@
+/*
+ * 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 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();
+ }
+ }
+ }
+
+ public XmlResourceParser newParser() {
+ synchronized (this) {
+ if (mNative != 0) {
+ return new Parser(nativeCreateParseState(mNative), this);
+ }
+ return null;
+ }
+ }
+
+ /*package*/ final class Parser implements XmlResourceParser {
+ Parser(int 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 appt 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 appt 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 appt 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 appt 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 appt 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*/ int 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, int xmlBlock) {
+ mAssets = assets;
+ mNative = xmlBlock;
+ mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
+ }
+
+ private final AssetManager mAssets;
+ private final int mNative;
+ private final StringBlock mStrings;
+ private boolean mOpen = true;
+ private int mOpenCount = 1;
+
+ private static final native int nativeCreate(byte[] data,
+ int offset,
+ int size);
+ private static final native int nativeGetStringBlock(int obj);
+
+ private static final native int nativeCreateParseState(int obj);
+ private static final native int nativeNext(int state);
+ private static final native int nativeGetNamespace(int state);
+ private static final native int nativeGetName(int state);
+ private static final native int nativeGetText(int state);
+ private static final native int nativeGetLineNumber(int state);
+ private static final native int nativeGetAttributeCount(int state);
+ private static final native int nativeGetAttributeNamespace(int state, int idx);
+ private static final native int nativeGetAttributeName(int state, int idx);
+ private static final native int nativeGetAttributeResource(int state, int idx);
+ private static final native int nativeGetAttributeDataType(int state, int idx);
+ private static final native int nativeGetAttributeData(int state, int idx);
+ private static final native int nativeGetAttributeStringValue(int state, int idx);
+ private static final native int nativeGetIdAttribute(int state);
+ private static final native int nativeGetClassAttribute(int state);
+ private static final native int nativeGetStyleAttribute(int state);
+ private static final native int nativeGetAttributeIndex(int state, String namespace, String name);
+ private static final native void nativeDestroyParseState(int state);
+
+ private static final native void nativeDestroy(int obj);
+}
diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java
new file mode 100644
index 0000000..c59e6d4
--- /dev/null
+++ b/core/java/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 {
+ /**
+ * Close this interface to the resource. Calls on the interface are no
+ * longer value after this call.
+ */
+ public void close();
+}
+
diff --git a/core/java/android/content/res/package.html b/core/java/android/content/res/package.html
new file mode 100644
index 0000000..bb09dc7
--- /dev/null
+++ b/core/java/android/content/res/package.html
@@ -0,0 +1,8 @@
+<HTML>
+<BODY>
+Contains classes for accessing application resources,
+such as raw asset files, colors, drawables, media or other other files
+in the package, plus important device configuration details
+(orientation, input types, etc.) that affect how the application may behave.
+</BODY>
+</HTML>
\ No newline at end of file