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 &lt;receiver&gt;}
+ * tag in your <code>AndroidManifest.xml</code>. <em><strong>Note:</strong></em>
+ * &nbsp;&nbsp;&nbsp;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 &lt;receiver&gt;}
+ * 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 &lt;uses-permission&gt;}
+ * 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 &lt;receiver&gt;}
+ * 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 &lt;uses-permission&gt;}
+ * 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 &lt;receiver&gt; tag,
+     * then the object is no longer alive after returning from this
+     * function.</b>  This means you should not perform any operations that
+     * return a result to you asynchronously -- 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 &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param message A message to include in the exception if it is thrown.
+     *
+     * @see #checkPermission(String, int, int)
+     */
+    public abstract void enforcePermission(
+            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
+     * &lt;grant-uri-permissions&gt;} tag.
+     *
+     * @param toPackage The package you would like to allow to access the Uri.
+     * @param uri The Uri you would like to grant access to.
+     * @param modeFlags The desired access modes.  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 &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The 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 &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The 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 &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The 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 &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The 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>
+ * &lt;row uri="content://contacts/people">
+ *  &lt;Col column = "name" value = "foo feebe "/>
+ *  &lt;Col column = "addr" value = "Tx"/>
+ * &lt;/row></pre>
+ * <br/>
+ * Delete, it must be in order of uri, select, and arg:
+ * <pre>
+ * &lt;del uri="content://contacts/people" select="name=? and addr=?" 
+ *  arg1 = "foo feebe" arg2 ="Tx"/></pre>
+ * <br/>
+ *  Use first row's uri to insert into another table,
+ *  content://contacts/people/1/phones:
+ * <pre>
+ * &lt;row uri="content://contacts/people">
+ *  &lt;col column = "name" value = "foo feebe"/>
+ *  &lt;col column = "addr" value = "Tx"/>
+ *  &lt;row postfix="phones">
+ *    &lt;col column="number" value="512-514-6535"/>
+ *  &lt;/row>
+ *  &lt;row postfix="phones">
+ *    &lt;col column="cell" value="512-514-6535"/>
+ *  &lt;/row>  
+ * &lt;/row></pre>
+ * <br/>
+ *  Insert multiple rows in to same table and same attributes:
+ * <pre>
+ * &lt;row uri="content://contacts/people" >
+ *  &lt;row>
+ *   &lt;col column= "name" value = "foo feebe"/>
+ *   &lt;col column= "addr" value = "Tx"/>
+ *  &lt;/row>
+ *  &lt;row>
+ *  &lt;/row>
+ * &lt;/row></pre>
+ *
+ * @hide
+ */ 
+public class DefaultDataHandler implements ContentInsertHandler {
+    private final static String ROW = "row";
+    private final static String COL = "col";
+    private final static String URI_STR = "uri";
+    private final static String POSTFIX = "postfix";
+    private final static String DEL = "del";
+    private final static String SELECT = "select";
+    private final static String ARG = "arg";   
+   
+    private Stack<Uri> mUris = new Stack<Uri>();
+    private ContentValues mValues;
+    private ContentResolver mContentResolver;   
+    
+    public void insert(ContentResolver contentResolver, InputStream in)
+            throws IOException, SAXException {
+        mContentResolver = contentResolver;
+        Xml.parse(in, Xml.Encoding.UTF_8, this);
+    }
+    
+    public void insert(ContentResolver contentResolver, String in)
+        throws SAXException {
+        mContentResolver = contentResolver;
+        Xml.parse(in, this);
+    }
+    
+    private void parseRow(Attributes atts) throws SAXException {
+        String uriStr = atts.getValue(URI_STR);
+        Uri uri;
+        if (uriStr != null) {
+            // case 1
+            uri = Uri.parse(uriStr);
+            if (uri == null) {
+                throw new SAXException("attribute " +
+                        atts.getValue(URI_STR) + " parsing failure"); 
+            }
+            
+        } else if (mUris.size() > 0){
+            // case 2
+            String postfix = atts.getValue(POSTFIX);
+            if (postfix != null) {
+                uri = Uri.withAppendedPath(mUris.lastElement(),
+                        postfix);
+            } else {
+                uri = mUris.lastElement();
+            } 
+        } else {
+            throw new SAXException("attribute parsing failure"); 
+        }
+        
+        mUris.push(uri);
+        
+    }
+    
+    private Uri insertRow() {
+        Uri u = mContentResolver.insert(mUris.lastElement(), mValues);
+        mValues = null;
+        return u;
+    }
+    
+    public void startElement(String uri, String localName, String name,
+            Attributes atts) throws SAXException {
+        if (ROW.equals(localName)) {            
+            if (mValues != null) {
+                // case 2, <Col> before <Row> insert last uri
+                if (mUris.empty()) {
+                    throw new SAXException("uri is empty");
+                }
+                Uri nextUri = insertRow();
+                if (nextUri == null) {
+                    throw new SAXException("insert to uri " + 
+                            mUris.lastElement().toString() + " failure");
+                } else {
+                    // make sure the stack lastElement save uri for more than one row
+                    mUris.pop();
+                    mUris.push(nextUri);
+                    parseRow(atts);
+                }
+            } else {
+                int attrLen = atts.getLength();
+                if (attrLen == 0) {
+                    // case 3, share same uri as last level
+                    mUris.push(mUris.lastElement());
+                } else {
+                    parseRow(atts);
+                }
+            }                
+        } else if (COL.equals(localName)) {
+            int attrLen = atts.getLength();
+            if (attrLen != 2) {
+                throw new SAXException("illegal attributes number " + attrLen);
+            }
+            String key = atts.getValue(0);
+            String value = atts.getValue(1);
+            if (key != null && key.length() > 0 && value != null && value.length() > 0) {
+                if (mValues == null) {
+                    mValues = new ContentValues();
+                }
+                mValues.put(key, value);
+            } else {
+                throw new SAXException("illegal attributes value");
+            }            
+        } else if (DEL.equals(localName)){
+            Uri u = Uri.parse(atts.getValue(URI_STR));
+            if (u == null) {
+                throw new SAXException("attribute " +
+                        atts.getValue(URI_STR) + " parsing failure"); 
+            }
+            int attrLen = atts.getLength() - 2;
+            if (attrLen > 0) {
+                String[] selectionArgs = new String[attrLen];
+                for (int i = 0; i < attrLen; i++) {
+                    selectionArgs[i] = atts.getValue(i+2);
+                }
+                mContentResolver.delete(u, atts.getValue(1), selectionArgs);
+            } else if (attrLen == 0){
+                mContentResolver.delete(u, atts.getValue(1), null);
+            } else {
+                mContentResolver.delete(u, null, null);
+            }
+            
+        } else {
+            throw new SAXException("unknown element: " + localName);
+        }
+    }
+    
+    public void endElement(String uri, String localName, String name)
+            throws SAXException {
+        if (ROW.equals(localName)) {
+            if (mUris.empty()) {
+                throw new SAXException("uri mismatch"); 
+            }
+            if (mValues != null) {
+                insertRow();
+            }
+            mUris.pop();                
+        } 
+    }
+
+
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void endDocument() throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void endPrefixMapping(String prefix) throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void setDocumentLocator(Locator locator) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void skippedEntity(String name) throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void startDocument() throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+        // TODO Auto-generated method stub
+
+    }
+    
+}
diff --git a/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 &lt;intent-filter&gt; descriptions in the
+ * installed application packages.  (Plus, in the case of broadcasts, any {@link BroadcastReceiver}
+ * objects explicitly registered with {@link Context#registerReceiver}.)  More
+ * details on this can be found in the documentation on the {@link
+ * IntentFilter} class.</p>
+ *
+ * <p>There are three pieces of information in the Intent that are used for
+ * resolution: the action, type, and category.  Using this information, a query
+ * is done on the {@link PackageManager} for a component that can handle the
+ * intent. The appropriate component is determined based on the intent
+ * information supplied in the <code>AndroidManifest.xml</code> file as
+ * follows:</p>
+ *
+ * <ul>
+ *     <li> <p>The <b>action</b>, if given, must be listed by the component as
+ *         one it handles.</p>
+ *     <li> <p>The <b>type</b> is retrieved from the Intent's data, if not
+ *         already supplied in the Intent.  Like the action, if a type is
+ *         included in the intent (either explicitly or implicitly in its
+ *         data), then this must be listed by the component as one it handles.</p>
+ *     <li> For data that is not a <code>content:</code> URI and where no explicit
+ *         type is included in the Intent, instead the <b>scheme</b> of the
+ *         intent data (such as <code>http:</code> or <code>mailto:</code>) is
+ *         considered. Again like the action, if we are matching a scheme it
+ *         must be listed by the component as one it can handle.
+ *     <li> <p>The <b>categories</b>, if supplied, must <em>all</em> be listed
+ *         by the activity as categories it handles.  That is, if you include
+ *         the categories {@link #CATEGORY_LAUNCHER} and
+ *         {@link #CATEGORY_ALTERNATIVE}, then you will only resolve to components
+ *         with an intent that lists <em>both</em> of those categories.
+ *         Activities will very often need to support the
+ *         {@link #CATEGORY_DEFAULT} so that they can be found by
+ *         {@link Context#startActivity Context.startActivity()}.</p>
+ * </ul>
+ *
+ * <p>For example, consider the Note Pad sample application that
+ * allows user to browse through a list of notes data and view details about
+ * individual items.  Text in italics indicate places were you would replace a
+ * name with one specific to your own package.</p>
+ *
+ * <pre> &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ *       package="<i>com.android.notepad</i>"&gt;
+ *     &lt;application android:icon="@drawable/app_notes"
+ *             android:label="@string/app_name"&gt;
+ *
+ *         &lt;provider class=".NotePadProvider"
+ *                 android:authorities="<i>com.google.provider.NotePad</i>" /&gt;
+ *
+ *         &lt;activity class=".NotesList" android:label="@string/title_notes_list"&gt;
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.MAIN" /&gt;
+ *                 &lt;category android:value="android.intent.category.LAUNCHER" /&gt;
+ *             &lt;/intent-filter&gt;
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.VIEW" /&gt;
+ *                 &lt;action android:value="android.intent.action.EDIT" /&gt;
+ *                 &lt;action android:value="android.intent.action.PICK" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.GET_CONTENT" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *         &lt;/activity&gt;
+ *
+ *         &lt;activity class=".NoteEditor" android:label="@string/title_note"&gt;
+ *             &lt;intent-filter android:label="@string/resolve_edit"&gt;
+ *                 &lt;action android:value="android.intent.action.VIEW" /&gt;
+ *                 &lt;action android:value="android.intent.action.EDIT" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *
+ *             &lt;intent-filter&gt;
+ *                 &lt;action android:value="android.intent.action.INSERT" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *
+ *         &lt;/activity&gt;
+ *
+ *         &lt;activity class=".TitleEditor" android:label="@string/title_edit_title"
+ *                 android:theme="@android:style/Theme.Dialog"&gt;
+ *             &lt;intent-filter android:label="@string/resolve_title"&gt;
+ *                 &lt;action android:value="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ *                 &lt;category android:value="android.intent.category.DEFAULT" /&gt;
+ *                 &lt;category android:value="android.intent.category.ALTERNATIVE" /&gt;
+ *                 &lt;category android:value="android.intent.category.SELECTED_ALTERNATIVE" /&gt;
+ *                 &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ *             &lt;/intent-filter&gt;
+ *         &lt;/activity&gt;
+ *
+ *     &lt;/application&gt;
+ * &lt;/manifest&gt;</pre>
+ *
+ * <p>The first activity,
+ * <code>com.android.notepad.NotesList</code>, serves as our main
+ * entry into the app.  It can do three things as described by its three intent
+ * templates:
+ * <ol>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_MAIN android.intent.action.MAIN}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>This provides a top-level entry into the NotePad application: the standard
+ * MAIN action is a main entry point (not requiring any other information in
+ * the Intent), and the LAUNCHER category says that this entry point should be
+ * listed in the application launcher.</p>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ *     &lt;action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ *     &lt;action android:value="{@link #ACTION_PICK android.intent.action.PICK}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>This declares the things that the activity can do on a directory of
+ * notes.  The type being supported is given with the &lt;type&gt; tag, where
+ * <code>vnd.android.cursor.dir/vnd.google.note</code> is a URI from which
+ * a Cursor of zero or more items (<code>vnd.android.cursor.dir</code>) can
+ * be retrieved which holds our note pad data (<code>vnd.google.note</code>).
+ * The activity allows the user to view or edit the directory of data (via
+ * the VIEW and EDIT actions), or to pick a particular note and return it
+ * to the caller (via the PICK action).  Note also the DEFAULT category
+ * supplied here: this is <em>required</em> for the
+ * {@link Context#startActivity Context.startActivity} method to resolve your
+ * activity when its component name is not explicitly specified.</p>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</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>
+ * &lt;intent-filter android:label="@string/resolve_edit"&gt;
+ *     &lt;action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ *     &lt;action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>The first, primary, purpose of this activity is to let the user interact
+ * with a single note, as decribed by the MIME type
+ * <code>vnd.android.cursor.item/vnd.google.note</code>.  The activity can
+ * either VIEW a note or allow the user to EDIT it.  Again we support the
+ * DEFAULT category to allow the activity to be launched without explicitly
+ * specifying its component.</p>
+ * <li><pre>
+ * &lt;intent-filter&gt;
+ *     &lt;action android:value="{@link #ACTION_INSERT android.intent.action.INSERT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ * <p>The secondary use of this activity is to insert a new note entry into
+ * an existing directory of notes.  This is used when the user creates a new
+ * note: the INSERT action is executed on the directory of notes, causing
+ * this activity to run and have the user create the new note data which
+ * it then adds to the content provider.</p>
+ * </ol>
+ *
+ * <p>Given these capabilities, the following intents will resolve to the
+ * NoteEditor activity:</p>
+ *
+ * <ul>
+ *     <li> <p><b>{ action=android.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>
+ * &lt;intent-filter android:label="@string/resolve_title"&gt;
+ *     &lt;action android:value="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" /&gt;
+ *     &lt;category android:value="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" /&gt;
+ *     &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;/intent-filter&gt;</pre>
+ *
+ * <p>In the single intent template here, we
+ * have created our own private action called
+ * <code>com.android.notepad.action.EDIT_TITLE</code> which means to
+ * edit the title of a note.  It must be invoked on a specific note
+ * (data type <code>vnd.android.cursor.item/vnd.google.note</code>) like the previous
+ * view and edit actions, but here displays and edits the title contained
+ * in the note data.
+ *
+ * <p>In addition to supporting the default category as usual, our title editor
+ * also supports two other standard categories: ALTERNATIVE and
+ * SELECTED_ALTERNATIVE.  Implementing
+ * these categories allows others to find the special action it provides
+ * without directly knowing about it, through the
+ * {@link android.content.pm.PackageManager#queryIntentActivityOptions} method, or
+ * more often to build dynamic menu items with
+ * {@link android.view.Menu#addIntentOptions}.  Note that in the intent
+ * template here was also supply an explicit name for the template
+ * (via <code>android:label="@string/resolve_title"</code>) to better control
+ * what the user sees when presented with this activity as an alternative
+ * action to the data they are viewing.
+ *
+ * <p>Given these capabilities, the following intent will resolve to the
+ * TitleEditor activity:</p>
+ *
+ * <ul>
+ *     <li> <p><b>{ action=com.android.notepad.action.EDIT_TITLE
+ *          data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b>
+ *         displays and allows the user to edit the title associated
+ *         with note <var>{ID}</var>.</p>
+ * </ul>
+ *
+ * <h3>Standard Activity Actions</h3>
+ *
+ * <p>These are the current standard actions that Intent defines for launching
+ * activities (usually through {@link Context#startActivity}.  The most
+ * important, and by far most frequently used, are {@link #ACTION_MAIN} and
+ * {@link #ACTION_EDIT}.
+ *
+ * <ul>
+ *     <li> {@link #ACTION_MAIN}
+ *     <li> {@link #ACTION_VIEW}
+ *     <li> {@link #ACTION_ATTACH_DATA}
+ *     <li> {@link #ACTION_EDIT}
+ *     <li> {@link #ACTION_PICK}
+ *     <li> {@link #ACTION_CHOOSER}
+ *     <li> {@link #ACTION_GET_CONTENT}
+ *     <li> {@link #ACTION_DIAL}
+ *     <li> {@link #ACTION_CALL}
+ *     <li> {@link #ACTION_SEND}
+ *     <li> {@link #ACTION_SENDTO}
+ *     <li> {@link #ACTION_ANSWER}
+ *     <li> {@link #ACTION_INSERT}
+ *     <li> {@link #ACTION_DELETE}
+ *     <li> {@link #ACTION_RUN}
+ *     <li> {@link #ACTION_SYNC}
+ *     <li> {@link #ACTION_PICK_ACTIVITY}
+ *     <li> {@link #ACTION_SEARCH}
+ *     <li> {@link #ACTION_WEB_SEARCH}
+ *     <li> {@link #ACTION_FACTORY_TEST}
+ * </ul>
+ *
+ * <h3>Standard Broadcast Actions</h3>
+ *
+ * <p>These are the current standard actions that Intent defines for receiving
+ * broadcasts (usually through {@link Context#registerReceiver} or a
+ * &lt;receiver&gt; tag in a manifest).
+ *
+ * <ul>
+ *     <li> {@link #ACTION_TIME_TICK}
+ *     <li> {@link #ACTION_TIME_CHANGED}
+ *     <li> {@link #ACTION_TIMEZONE_CHANGED}
+ *     <li> {@link #ACTION_BOOT_COMPLETED}
+ *     <li> {@link #ACTION_PACKAGE_ADDED}
+ *     <li> {@link #ACTION_PACKAGE_CHANGED}
+ *     <li> {@link #ACTION_PACKAGE_REMOVED}
+ *     <li> {@link #ACTION_PACKAGE_RESTARTED}
+ *     <li> {@link #ACTION_PACKAGE_DATA_CLEARED}
+ *     <li> {@link #ACTION_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 &lt;intent-filter&gt; of this action
+     * describing to the user what it does.
+     *
+     * <p>The action of IntentFilter with this category is important in that it
+     * describes the specific action the target will perform.  This generally
+     * should not be a generic action (such as {@link #ACTION_VIEW}, but rather
+     * a specific name such as "com.android.camera.action.CROP.  Only one
+     * alternative of any particular action will be shown to the user, so using
+     * a specific action like this makes sure that your alternative will be
+     * displayed while also allowing other applications to provide their own
+     * overrides of that particular action.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
+    /**
+     * Set if the activity should be considered as an alternative selection
+     * action to the data the user has currently selected.  This is like
+     * {@link #CATEGORY_ALTERNATIVE}, but is used in activities showing a list
+     * of items from which the user can select, giving them alternatives to the
+     * default action that will be performed on it.
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE";
+    /**
+     * Intended to be used as a tab inside of 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">
+ *     &lt;!-- Content provider for search suggestions --&gt;
+ *     &lt;provider android:name="YourSuggestionProviderClass"
+ *               android:authorities="your.suggestion.authority" /&gt;</pre>
+ * </li>
+ * <li>Please note that you <i>do not</i> instantiate this content provider directly from within
+ * your code.  This is done automatically by the system Content Resolver, when the search dialog
+ * looks for suggestions.</li>
+ * <li>In order for the Content Resolver to do this, you must update your searchable activity's 
+ * XML configuration file with information about your content provider.  The following additions 
+ * are usually sufficient:
+ * <pre class="prettyprint">
+ *     android:searchSuggestAuthority="your.suggestion.authority"
+ *     android:searchSuggestSelection=" ? "</pre>
+ * </li>
+ * <li>In your searchable activities, capture any user-generated queries and record them
+ * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery
+ * SearchRecentSuggestions.saveRecentQuery()}.</li>
+ * </ul>
+ * 
+ * @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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;string id="mainLabel"&gt;Hello &lt;u&gt;th&lt;ignore&gt;e&lt;/ignore&gt;re&lt;/u&gt;, &lt;i&gt;you&lt;/i&gt; &lt;b&gt;Activity&lt;/b&gt;!&lt;/string&gt;
+    &lt;string id="back"&gt;Back&lt;/string&gt;
+    &lt;string id="clear"&gt;Clear&lt;/string&gt;
+&lt;/resources&gt;
+</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 &lt;color&gt; 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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;color id="opaque_red"&gt;#ffff0000&lt;/color&gt;
+    &lt;color id="transparent_red"&gt;#80ff0000&lt;/color&gt;
+    &lt;color id="opaque_blue"&gt;#0000ff&lt;/color&gt;
+    &lt;color id="opaque_green"&gt;#0f0&lt;/color&gt;
+&lt;/resources&gt;
+</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/&lt;myimage&gt;.&lt;ext&gt;</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 &lt;drawable&gt; tag to create a
+drawable resource.</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;drawable id="opaque_red"&gt;#ffff0000&lt;/drawable&gt;
+    &lt;drawable id="transparent_red"&gt;#80ff0000&lt;/drawable&gt;
+    &lt;drawable id="opaque_blue"&gt;#0000ff&lt;/drawable&gt;
+    &lt;drawable id="opaque_green"&gt;#0f0&lt;/drawable&gt;
+&lt;/resources&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill-parent" android:layout_height="fill-parent"
+        android:text="Hello, World!" /&gt;
+&lt;/root&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;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!" /&gt;
+&lt;/root&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        <b>android:textColor="@color/opaque_red"</b>
+        android:text="Hello, World!" /&gt;
+&lt;/root&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;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!" /&gt;
+&lt;/root&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        android:textColor="@android:color/opaque_red"
+        android:text="@string/hello_world" /&gt;
+&lt;/root&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;drawable id="my_background"&gt;@android:drawable/theme2_background&lt;/drawable&gt;
+&lt;/resources&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text"
+        android:layout_width="fill_parent" android:layout_height="fill_parent"
+        <b>android:textColor="?android:textDisabledColor"</b>
+        android:text="@string/hello_world" /&gt;
+&lt;/root&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;style id="Theme" parent="android:Theme.White"&gt;
+        &lt;item id="android:foregroundColor"&gt;#FFF8D96F&lt;/item&gt;
+        &lt;item id="android:textColor"&gt;@color/opaque_blue&lt;/item&gt;
+        &lt;item id="android:textSelectedColor"&gt;?android:textColor&lt;/item&gt;
+    &lt;/style&gt;
+&lt;/resources&gt;
+</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>
+    &lt;style id="Home" parent="android:Theme.White"&gt;
+        ...
+    &lt;/style&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;style id="Theme.WhiteText"&gt;
+        &lt;item id="android:foregroundColor"&gt;#FFFFFFFF&lt;/item&gt;
+        &lt;item id="android:textColor"&gt;?android:foregroundColor&lt;/item&gt;
+    &lt;/style&gt;
+&lt;/resources&gt;
+</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>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;resources&gt;
+    &lt;style id="SpecialText"&gt;
+        &lt;item id="android:textSize"&gt;18&lt;/item&gt;
+        &lt;item id="android:textColor"&gt;#008&lt;/item&gt;
+    &lt;/style&gt;
+&lt;/resources&gt;
+</pre>
+
+<p>You can now apply this style to your TextView in the XML file:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;root&gt;
+    &lt;EditText id="text1" <b>style="@style/SpecialText"</b>
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="Hello, World!" /&gt;
+    &lt;EditText id="text2" <b>style="@style/SpecialText"</b>
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="I love you all." /&gt;
+&lt;/root&gt;</pre>
+<h4>&nbsp;</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 &lt;activity&gt; and
+ * &lt;receiver&gt; 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
+ * &lt;application&gt; tag.
+ */
+public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+    
+    /**
+     * Default task affinity of all activities in this application. See 
+     * {@link ActivityInfo#taskAffinity} for more information.  This comes 
+     * from the "taskAffinity" attribute. 
+     */
+    public String taskAffinity;
+    
+    /**
+     * Optional name of a permission required to be able to access this
+     * application's components.  From the "permission" attribute.
+     */
+    public String permission;
+    
+    /**
+     * The name of the process this application should run in.  From the
+     * "process" attribute or, if not set, the same as
+     * <var>packageName</var>.
+     */
+    public String processName;
+    
+    /**
+     * Class implementing the Application object.  From the "class"
+     * attribute.
+     */
+    public String className;
+    
+    /**
+     * A style resource identifier (in the package's resources) of the
+     * description of an application.  From the "description" attribute
+     * or, if not set, 0.
+     */
+    public int descriptionRes;    
+    
+    /**
+     * A style resource identifier (in the package's resources) of the
+     * default visual theme of the application.  From the "theme" attribute
+     * or, if not set, 0.
+     */
+    public int theme;
+    
+    /**
+     * Class implementing the Application's manage space
+     * functionality.  From the "manageSpaceActivity"
+     * attribute. This is an optional attribute and will be null if
+     * 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 &lt;application&gt; tag.
+     */
+    public static final int FLAG_DEBUGGABLE = 1<<1;
+    
+    /**
+     * Value for {@link #flags}: set to true if this application has code
+     * associated with it.  Comes
+     * from {@link android.R.styleable#AndroidManifestApplication_hasCode
+     * android:hasCode} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_HAS_CODE = 1<<2;
+    
+    /**
+     * Value for {@link #flags}: set to true if this application is persistent.
+     * Comes from {@link android.R.styleable#AndroidManifestApplication_persistent
+     * android:persistent} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_PERSISTENT = 1<<3;
+
+    /**
+     * Value for {@link #flags}: set to true 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 &lt;application&gt; tag.
+     */
+    public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5;
+    
+    /**
+     * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+     * Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData
+     * android:allowClearUserData} of the &lt;application&gt; tag.
+     */
+    public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6;
+    
+    
+    /**
+     * Value for {@link #flags}: 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
+     * &lt;activity&gt;, &lt;receiver&gt;, &lt;service&gt;, or
+     * &lt;provider&gt; 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 &lt;uses-configuration&gt; tags.
+ */
+public class ConfigurationInfo implements Parcelable {    
+    /**
+     * The kind of touch screen attached to the device.
+     * One of: {@link android.content.res.Configuration#TOUCHSCREEN_NOTOUCH},
+     * {@link android.content.res.Configuration#TOUCHSCREEN_STYLUS}, 
+     * {@link android.content.res.Configuration#TOUCHSCREEN_FINGER}. 
+     */
+    public int reqTouchScreen;
+    
+    /**
+     * Application's input method preference.
+     * One of: {@link android.content.res.Configuration#KEYBOARD_UNDEFINED},
+     * {@link android.content.res.Configuration#KEYBOARD_NOKEYS},
+     * {@link android.content.res.Configuration#KEYBOARD_QWERTY},
+     * {@link android.content.res.Configuration#KEYBOARD_12KEY}
+     */
+    public int reqKeyboardType;
+    
+    /**
+     * A flag indicating whether any keyboard is available.
+     * one of: {@link android.content.res.Configuration#NAVIGATION_UNDEFINED},
+     * {@link android.content.res.Configuration#NAVIGATION_DPAD}, 
+     * {@link android.content.res.Configuration#NAVIGATION_TRACKBALL},
+     * {@link android.content.res.Configuration#NAVIGATION_WHEEL}
+     */
+    public int reqNavigation;
+    
+    /**
+     * Value for {@link #reqInputFeatures}: if set, indicates that the application
+     * requires a hard keyboard
+     */
+    public static final int INPUT_FEATURE_HARD_KEYBOARD = 0x00000001;
+    
+    /**
+     * Value for {@link #reqInputFeatures}: if set, indicates that the application
+     * requires a five way navigation device
+     */
+    public static final int INPUT_FEATURE_FIVE_WAY_NAV = 0x00000002;
+    
+    /**
+     * Flags associated with the input features.  Any combination of
+     * {@link #INPUT_FEATURE_HARD_KEYBOARD},
+     * {@link #INPUT_FEATURE_FIVE_WAY_NAV}
+     */
+    public int reqInputFeatures = 0;
+
+    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&lt;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 &lt;instrumentation&gt; tag.
+ */
+public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
+    /**
+     * The name of the application package being instrumented.  From the
+     * "package" attribute.
+     */
+    public String targetPackage;
+    
+    /**
+     * 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 &lt;manifest&gt; tag's "name"
+     * attribute.
+     */
+    public String packageName;
+
+    /**
+     * The version number of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
+     * attribute.
+     */
+    public int versionCode;
+    
+    /**
+     * The version name of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_versionName versionName}
+     * attribute.
+     */
+    public String versionName;
+    
+    /**
+     * The shared user ID name of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
+     * attribute.
+     */
+    public String sharedUserId;
+    
+    /**
+     * The shared user ID label of this package, as specified by the &lt;manifest&gt;
+     * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
+     * attribute.
+     */
+    public int sharedUserLabel;
+    
+    /**
+     * Information collected from the &lt;application&gt; tag, or null if
+     * there was none.
+     */
+    public ApplicationInfo applicationInfo;
+    
+    /**
+     * All kernel group-IDs that have been assigned to this package.
+     * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set.
+     */
+    public int[] gids;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestActivity
+     * &lt;activity&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_ACTIVITIES} was set.
+     */
+    public ActivityInfo[] activities;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestReceiver
+     * &lt;receiver&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_RECEIVERS} was set.
+     */
+    public ActivityInfo[] receivers;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestService
+     * &lt;service&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_SERVICES} was set.
+     */
+    public ServiceInfo[] services;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestProvider
+     * &lt;provider&gt;} tags included under &lt;application&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PROVIDERS} was set.
+     */
+    public ProviderInfo[] providers;
+
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestInstrumentation
+     * &lt;instrumentation&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_INSTRUMENTATION} was set.
+     */
+    public InstrumentationInfo[] instrumentation;
+    
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestPermission
+     * &lt;permission&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PERMISSIONS} was set.
+     */
+    public PermissionInfo[] permissions;
+    
+    /**
+     * Array of all {@link android.R.styleable#AndroidManifestUsesPermission
+     * &lt;uses-permission&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PERMISSIONS} was set.  This list includes
+     * all permissions requested, even those that were not granted or known
+     * by the system at install time.
+     */
+    public String[] requestedPermissions;
+    
+    /**
+     * Array of 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
+     * &lt;uses-configuration&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none. This is only filled in if the flag
+     * {@link PackageManager#GET_CONFIGURATIONS} was set.  
+     */
+    public ConfigurationInfo[] configPreferences;
+
+    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
+     * &lt;permission-tree&gt;} tag in its manifest.  A package can only add
+     * permissions to trees that were defined by either its own package or
+     * another with the same user id; a permission is in a tree if it
+     * matches the name of the permission tree + ".": for example,
+     * "com.foo.bar" is a member of the permission tree "com.foo".
+     *
+     * <p>It is good to make your permission tree name descriptive, because you
+     * are taking possession of that entire set of permission names.  Thus, it
+     * must be under a domain you control, with a suffix that will not match
+     * any normal permissions that may be declared in any applications that
+     * are part of that domain.
+     *
+     * <p>New permissions must be added before
+     * any .apks are installed that use those permissions.  Permissions you
+     * add through this method are remembered across reboots of the device.
+     * If the given permission already exists, the info you supply here
+     * will be used to update it.
+     *
+     * @param info Description of the permission to be added.
+     *
+     * @return Returns true if a new permission was created, false if an
+     * existing one was updated.
+     *
+     * @throws SecurityException if you are not allowed to add the
+     * given permission name.
+     *
+     * @see #removePermission(String)
+     */
+    public abstract boolean addPermission(PermissionInfo info);
+
+    /**
+     * 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 &lt;permission-group&gt; tags.
+ */
+public class PermissionGroupInfo extends PackageItemInfo implements Parcelable {
+    /**
+     * A string resource identifier (in the package's resources) of this
+     * permission's description.  From the "description" attribute or,
+     * if not set, 0.
+     */
+    public int descriptionRes;
+
+    /**
+     * 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 &lt;permission&gt; tags.
+ */
+public class PermissionInfo extends PackageItemInfo implements Parcelable {
+    /**
+     * A normal application value for {@link #protectionLevel}, corresponding
+     * to the <code>normal</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_NORMAL = 0;
+
+    /**
+     * Dangerous value for {@link #protectionLevel}, corresponding
+     * to the <code>dangerous</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_DANGEROUS = 1;
+
+    /**
+     * System-level value for {@link #protectionLevel}, corresponding
+     * to the <code>signature</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     */
+    public static final int PROTECTION_SIGNATURE = 2;
+
+    /**
+     * 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
+ * &lt;intent&gt; 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 &lt;service&gt; tags.
+ */
+public class ServiceInfo extends ComponentInfo
+        implements Parcelable {
+    /**
+     * Optional name of a permission required to be able to access this
+     * Service.  From the "permission" attribute.
+     */
+    public String permission;
+
+    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>
+ * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ *   &lt;item android:state_focused="true" android:color="@color/testcolor1"/&gt;
+ *   &lt;item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /&gt;
+ *   &lt;item android:state_enabled="false" android:colore="@color/testcolor3" /&gt;
+ *   &lt;item android:state_active="true" android:color="@color/testcolor4" /&gt;
+ *   &lt;item android:color="@color/testcolor5"/&gt;
+ * &lt;/selector&gt;
+ * </pre>
+ *
+ * This defines a set of state spec / color pairs where each state spec specifies a set of
+ * states that a view must either be in or not be in and the color specifies the color associated
+ * with that spec.  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>&lt;Button
+         * textColor="#ff000000"&gt;</code>, then the button's text will
+         * <em>always</em> be black, regardless of what is specified in any of
+         * the styles.
+         * 
+         * @param set The base set of attribute values.  May be null.
+         * @param attrs The desired attributes to be retrieved.
+         * @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 &lt;extra&gt;} 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