Merge "Enable early termination of the prefetcher's preparation phase."
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index df48f04..5c8ee18 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -215,7 +215,9 @@
         // the accounts are not set yet
         sendCheckAlarmsMessage();
 
-        mSyncStorageEngine.doDatabaseCleanup(accounts);
+        if (mBootCompleted) {
+            mSyncStorageEngine.doDatabaseCleanup(accounts);
+        }
 
         if (accounts.length > 0) {
             // If this is the first time this was called after a bootup then
@@ -1317,6 +1319,7 @@
         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
         public void onBootCompleted() {
             mBootCompleted = true;
+            mSyncStorageEngine.doDatabaseCleanup(AccountManager.get(mContext).getAccounts());
             if (mReadyToRunLatch != null) {
                 mReadyToRunLatch.countDown();
             }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 03e606f..daad95c 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -122,7 +122,15 @@
     private static final boolean SYNC_ENABLED_DEFAULT = false;
 
     // the version of the accounts xml file format
-    private static final int ACCOUNTS_VERSION = 1;
+    private static final int ACCOUNTS_VERSION = 2;
+
+    private static HashMap<String, String> sAuthorityRenames;
+
+    static {
+        sAuthorityRenames = new HashMap<String, String>();
+        sAuthorityRenames.put("contacts", "com.android.contacts");
+        sAuthorityRenames.put("calendar", "com.android.calendar");
+    }
 
     public static class PendingOperation {
         final Account account;
@@ -1281,7 +1289,9 @@
     private void removeAuthorityLocked(Account account, String authorityName) {
         AccountInfo accountInfo = mAccounts.get(account);
         if (accountInfo != null) {
-            if (accountInfo.authorities.remove(authorityName) != null) {
+            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
+            if (authorityInfo != null) {
+                mAuthorities.remove(authorityInfo.ident);
                 writeAccountInfoLocked();
             }
         }
@@ -1407,11 +1417,61 @@
             }
         }
 
+        if (maybeMigrateSettingsForRenamedAuthorities()) {
+            writeNeeded = true;
+        }
+
         if (writeNeeded) {
             writeAccountInfoLocked();
         }
     }
 
+    /**
+     * some authority names have changed. copy over their settings and delete the old ones
+     * @return true if a change was made
+     */
+    private boolean maybeMigrateSettingsForRenamedAuthorities() {
+        boolean writeNeeded = false;
+
+        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
+        final int N = mAuthorities.size();
+        for (int i=0; i<N; i++) {
+            AuthorityInfo authority = mAuthorities.valueAt(i);
+            // skip this authority if it isn't one of the renamed ones
+            final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+            if (newAuthorityName == null) {
+                continue;
+            }
+
+            // remember this authority so we can remove it later. we can't remove it
+            // now without messing up this loop iteration
+            authoritiesToRemove.add(authority);
+
+            // this authority isn't enabled, no need to copy it to the new authority name since
+            // the default is "disabled"
+            if (!authority.enabled) {
+                continue;
+            }
+
+            // if we already have a record of this new authority then don't copy over the settings
+            if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) {
+                continue;
+            }
+
+            AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
+                    newAuthorityName, -1 /* ident */, false /* doWrite */);
+            newAuthority.enabled = true;
+            writeNeeded = true;
+        }
+
+        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
+            removeAuthorityLocked(authorityInfo.account, authorityInfo.authority);
+            writeNeeded = true;
+        }
+
+        return writeNeeded;
+    }
+
     private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
         AuthorityInfo authority = null;
         int id = -1;
@@ -1424,14 +1484,15 @@
             Log.e(TAG, "the id of the authority is null", e);
         }
         if (id >= 0) {
+            String authorityName = parser.getAttributeValue(null, "authority");
+            String enabled = parser.getAttributeValue(null, "enabled");
+            String syncable = parser.getAttributeValue(null, "syncable");
             String accountName = parser.getAttributeValue(null, "account");
             String accountType = parser.getAttributeValue(null, "type");
             if (accountType == null) {
                 accountType = "com.google";
+                syncable = "unknown";
             }
-            String authorityName = parser.getAttributeValue(null, "authority");
-            String enabled = parser.getAttributeValue(null, "enabled");
-            String syncable = parser.getAttributeValue(null, "syncable");
             authority = mAuthorities.get(id);
             if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
                     + accountName + " auth=" + authorityName
@@ -1456,7 +1517,7 @@
                     authority.syncable = -1;
                 } else {
                     authority.syncable =
-                            (syncable == null || Boolean.parseBoolean(enabled)) ? 1 : 0;
+                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
                 }
             } else {
                 Log.w(TAG, "Failure adding authority: account="
@@ -1546,13 +1607,11 @@
                 out.attribute(null, "account", authority.account.name);
                 out.attribute(null, "type", authority.account.type);
                 out.attribute(null, "authority", authority.authority);
-                if (!authority.enabled) {
-                    out.attribute(null, "enabled", "false");
-                }
+                out.attribute(null, "enabled", Boolean.toString(authority.enabled));
                 if (authority.syncable < 0) {
                     out.attribute(null, "syncable", "unknown");
-                } else if (authority.syncable == 0) {
-                    out.attribute(null, "syncable", "false");
+                } else {
+                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
                 }
                 for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
                     out.startTag(null, "periodicSync");
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e6f9bed..b52f6e0 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -213,16 +213,23 @@
 
     // Things related to query logging/sampling for debugging
     // slow/frequent queries during development.  Always log queries
-    // which take 500ms+; shorter queries are sampled accordingly.
-    // Commit statements, which are typically slow, are logged
-    // together with the most recently executed SQL statement, for
-    // disambiguation.
-    private static final int QUERY_LOG_TIME_IN_MILLIS = 500;
+    // which take (by default) 500ms+; shorter queries are sampled
+    // accordingly.  Commit statements, which are typically slow, are
+    // logged together with the most recently executed SQL statement,
+    // for disambiguation.  The 500ms value is configurable via a
+    // SystemProperty, but developers actively debugging database I/O
+    // should probably use the regular log tunable,
+    // LOG_SLOW_QUERIES_PROPERTY, defined below.
+    private static int sQueryLogTimeInMillis = 0;  // lazily initialized
     private static final int QUERY_LOG_SQL_LENGTH = 64;
     private static final String COMMIT_SQL = "COMMIT;";
     private final Random mRandom = new Random();
     private String mLastSqlStatement = null;
 
+    // String prefix for slow database query EventLog records that show
+    // lock acquistions of the database.
+    /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
+
     /** Used by native code, do not rename */
     /* package */ int mNativeHandle = 0;
 
@@ -340,15 +347,18 @@
     private boolean mLockingEnabled = true;
 
     /* package */ void onCorruption() {
+        Log.e(TAG, "Removing corrupt database: " + mPath);
+        EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
         try {
             // Close the database (if we can), which will cause subsequent operations to fail.
             close();
         } finally {
-            Log.e(TAG, "Removing corrupt database: " + mPath);
-            EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
             // Delete the corrupt file.  Don't re-create it now -- that would just confuse people
             // -- but the next time someone tries to open it, they can set it up from scratch.
-            new File(mPath).delete();
+            if (!mPath.equalsIgnoreCase(":memory")) {
+                // delete is only for non-memory database files
+                new File(mPath).delete();
+            }
         }
     }
 
@@ -490,6 +500,9 @@
      * {@link #yieldIfContendedSafely}.
      */
     public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         lockForced();
         boolean ok = false;
         try {
@@ -535,6 +548,9 @@
      * are committed and rolled back.
      */
     public void endTransaction() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         if (!mLock.isHeldByCurrentThread()) {
             throw new IllegalStateException("no transaction pending");
         }
@@ -595,6 +611,9 @@
      * transaction is already marked as successful.
      */
     public void setTransactionSuccessful() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         if (!mLock.isHeldByCurrentThread()) {
             throw new IllegalStateException("no transaction pending");
         }
@@ -807,7 +826,10 @@
             // TODO: should we do this for other open failures?
             Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
             EventLog.writeEvent(EVENT_DB_CORRUPT, path);
-            new File(path).delete();
+            if (!path.equalsIgnoreCase(":memory")) {
+                // delete is only for non-memory database files
+                new File(path).delete();
+            }
             sqliteDatabase = new SQLiteDatabase(path, factory, flags);
         }
         ActiveDatabases.getInstance().mActiveDatabases.add(
@@ -849,14 +871,14 @@
      * Close the database.
      */
     public void close() {
+        if (!isOpen()) {
+            return; // already closed
+        }
         lock();
         try {
             closeClosable();
             // close this database instance - regardless of its reference count value
             onAllReferencesReleased();
-            // set path to null, to cause bad stuff to happen if this object is reused without
-            // being opened first
-            mPath = null;
         } finally {
             unlock();
         }
@@ -893,6 +915,9 @@
      * @return the database version
      */
     public int getVersion() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         SQLiteStatement prog = null;
         lock();
         try {
@@ -911,6 +936,9 @@
      * @param version the new database version
      */
     public void setVersion(int version) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         execSQL("PRAGMA user_version = " + version);
     }
 
@@ -920,6 +948,9 @@
      * @return the new maximum database size
      */
     public long getMaximumSize() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         SQLiteStatement prog = null;
         lock();
         try {
@@ -941,6 +972,9 @@
      * @return the new maximum database size
      */
     public long setMaximumSize(long numBytes) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         SQLiteStatement prog = null;
         lock();
         try {
@@ -966,6 +1000,9 @@
      * @return the database page size, in bytes
      */
     public long getPageSize() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         SQLiteStatement prog = null;
         lock();
         try {
@@ -987,6 +1024,9 @@
      * @param numBytes the database page size, in bytes
      */
     public void setPageSize(long numBytes) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         execSQL("PRAGMA page_size = " + numBytes);
     }
 
@@ -1103,6 +1143,9 @@
      * @return a pre-compiled statement object.
      */
     public SQLiteStatement compileStatement(String sql) throws SQLException {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         lock();
         try {
             return new SQLiteStatement(this, sql);
@@ -1183,6 +1226,9 @@
             boolean distinct, String table, String[] columns,
             String selection, String[] selectionArgs, String groupBy,
             String having, String orderBy, String limit) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         String sql = SQLiteQueryBuilder.buildQueryString(
                 distinct, table, columns, selection, groupBy, having, orderBy, limit);
 
@@ -1289,6 +1335,9 @@
     public Cursor rawQueryWithFactory(
             CursorFactory cursorFactory, String sql, String[] selectionArgs,
             String editTable) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         long timeStart = 0;
 
         if (Config.LOGV || mSlowQueryThreshold != -1) {
@@ -1675,8 +1724,12 @@
      * @throws SQLException If the SQL string is invalid for some reason
      */
     public void execSQL(String sql) throws SQLException {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         long timeStart = SystemClock.uptimeMillis();
         lock();
+        logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX);
         try {
             native_execSQL(sql);
         } catch (SQLiteDatabaseCorruptException e) {
@@ -1690,9 +1743,9 @@
         // SQL statement for disambiguation.  Note that instance
         // equality to COMMIT_SQL is safe here.
         if (sql == COMMIT_SQL) {
-            logTimeStat(sql + mLastSqlStatement, timeStart);
+            logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL);
         } else {
-            logTimeStat(sql, timeStart);
+            logTimeStat(sql, timeStart, null);
         }
     }
 
@@ -1706,6 +1759,9 @@
      * @throws SQLException If the SQL string is invalid for some reason
      */
     public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
         if (bindArgs == null) {
             throw new IllegalArgumentException("Empty bindArgs");
         }
@@ -1809,9 +1865,11 @@
         return mPath;
     }
 
-
-
     /* package */ void logTimeStat(String sql, long beginMillis) {
+        logTimeStat(sql, beginMillis, null);
+    }
+
+    /* package */ void logTimeStat(String sql, long beginMillis, String prefix) {
         // Keep track of the last statement executed here, as this is
         // the common funnel through which all methods of hitting
         // libsqlite eventually flow.
@@ -1823,13 +1881,27 @@
 
         int samplePercent;
         long durationMillis = SystemClock.uptimeMillis() - beginMillis;
-        if (durationMillis >= QUERY_LOG_TIME_IN_MILLIS) {
+        if (durationMillis == 0 && prefix == GET_LOCK_LOG_PREFIX) {
+            // The common case is locks being uncontended.  Don't log those,
+            // even at 1%, which is our default below.
+            return;
+        }
+        if (sQueryLogTimeInMillis == 0) {
+            sQueryLogTimeInMillis = SystemProperties.getInt("db.db_operation.threshold_ms", 500);
+        }
+        if (durationMillis >= sQueryLogTimeInMillis) {
             samplePercent = 100;
-        } else {
-            samplePercent = (int) (100 * durationMillis / QUERY_LOG_TIME_IN_MILLIS) + 1;
+        } else {;
+            samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
             if (mRandom.nextInt(100) >= samplePercent) return;
         }
 
+        // Note: the prefix will be "COMMIT;" or "GETLOCK:" when non-null.  We wait to do
+        // it here so we avoid allocating in the common case.
+        if (prefix != null) {
+            sql = prefix + sql;
+        }
+
         if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
 
         // ActivityThread.currentPackageName() only returns non-null if the
@@ -2059,6 +2131,10 @@
         static ActiveDatabases getInstance() {return activeDatabases;}
     }
 
+    /**
+     * this method is used to collect data about ALL open databases in the current process.
+     * bugreport is a user of this data. 
+     */
     /* package */ static ArrayList<DbStats> getDbStats() {
         ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
         for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) {
@@ -2076,6 +2152,9 @@
 
             // get list of attached dbs and for each db, get its size and pagesize
             ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
+            if (attachedDbs == null) {
+                continue;
+            }
             for (int i = 0; i < attachedDbs.size(); i++) {
                 Pair<String, String> p = attachedDbs.get(i);
                 long pageCount = getPragmaVal(db, p.first + ".page_count;");
@@ -2095,7 +2174,10 @@
                         dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
                     }
                 }
-                dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), lookasideUsed));
+                if (pageCount > 0) {
+                    dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
+                            lookasideUsed));
+                }
             }
         }
         return dbStatsList;
@@ -2108,6 +2190,9 @@
      * TODO: use this to do all pragma's in this class
      */
     private static long getPragmaVal(SQLiteDatabase db, String pragma) {
+        if (!db.isOpen()) {
+            return 0;
+        }
         SQLiteStatement prog = null;
         try {
             prog = new SQLiteStatement(db, "PRAGMA " + pragma);
@@ -2124,6 +2209,9 @@
      * TODO: move this to {@link DatabaseUtils}
      */
     private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
+        if (!dbObj.isOpen()) {
+            return null;
+        }
         ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
         Cursor c = dbObj.rawQuery("pragma database_list;", null);
         while (c.moveToNext()) {
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 7cd9561..43d2fac 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -51,20 +51,20 @@
 
     /**
      * Reads rows into a buffer. This method acquires the database lock.
-     * 
+     *
      * @param window The window to fill into
      * @return number of total rows in the query
      */
-    /* package */ int fillWindow(CursorWindow window,  
+    /* package */ int fillWindow(CursorWindow window,
             int maxRead, int lastPos) {
         long timeStart = SystemClock.uptimeMillis();
         mDatabase.lock();
-
+        mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
         try {
             acquireReference();
             try {
                 window.acquireReference();
-                // if the start pos is not equal to 0, then most likely window is 
+                // if the start pos is not equal to 0, then most likely window is
                 // too small for the data set, loading by another thread
                 // is not safe in this situation. the native code will ignore maxRead
                 int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
@@ -83,7 +83,7 @@
                 mDatabase.onCorruption();
                 throw e;
             } finally {
-                window.releaseReference();                
+                window.releaseReference();
             }
         } finally {
             releaseReference();
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index b6f3997..b41bad0 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -164,8 +164,11 @@
 using namespace android;
 
 class NinePatchPeeker : public SkImageDecoder::Peeker {
+    SkImageDecoder* fHost;
 public:
-    NinePatchPeeker() {
+    NinePatchPeeker(SkImageDecoder* host) {
+        // the host lives longer than we do, so a raw ptr is safe
+        fHost = host;
         fPatchIsValid = false;
     }
 
@@ -197,6 +200,19 @@
             //       fPatch.sizeLeft, fPatch.sizeTop,
             //       fPatch.sizeRight, fPatch.sizeBottom);
             fPatchIsValid = true;
+
+            // now update our host to force index or 32bit config
+            // 'cause we don't want 565 predithered, since as a 9patch, we know
+            // we will be stretched, and therefore we want to dither afterwards.
+            static const SkBitmap::Config gNo565Pref[] = {
+                SkBitmap::kIndex8_Config,
+                SkBitmap::kIndex8_Config,
+                SkBitmap::kARGB_8888_Config,
+                SkBitmap::kARGB_8888_Config,
+                SkBitmap::kARGB_8888_Config,
+                SkBitmap::kARGB_8888_Config,
+            };
+            fHost->setPrefConfigTable(gNo565Pref);
         } else {
             fPatch = NULL;
         }
@@ -364,7 +380,7 @@
     decoder->setSampleSize(sampleSize);
     decoder->setDitherImage(doDither);
 
-    NinePatchPeeker     peeker;
+    NinePatchPeeker     peeker(decoder);
     JavaPixelAllocator  javaAllocator(env, reportSizeToVM);
     SkBitmap*           bitmap = new SkBitmap;
     Res_png_9patch      dummy9Patch;
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 7028d1a..48fe765 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -214,7 +214,6 @@
         MockContentResolver mockResolver = new MockContentResolver();
 
         final TestContext testContext = new TestContext(mockResolver, getContext());
-        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
         byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<accounts>\n"
@@ -230,7 +229,7 @@
         fos.write(accountsFileData);
         accountInfoFile.finishWrite(fos);
 
-        engine.clearAndReadState();
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
         List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority1);
         assertEquals(1, syncs.size());
@@ -245,7 +244,7 @@
         assertEquals(sync3, syncs.get(0));
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<accounts version=\"1\">\n"
+                + "<accounts version=\"2\">\n"
                 + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
                 + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
                 + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
@@ -268,7 +267,7 @@
         assertEquals(0, syncs.size());
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<accounts version=\"1\">\n"
+                + "<accounts version=\"2\">\n"
                 + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
                 + "<periodicSync period=\"1000\" />\n"
                 + "</authority>"
@@ -299,6 +298,89 @@
         assertEquals(1, syncs.size());
         assertEquals(sync3s, syncs.get(0));
     }
+
+    @SmallTest
+    public void testAuthorityRenaming() throws Exception {
+        final Account account1 = new Account("acc1", "type1");
+        final Account account2 = new Account("acc2", "type2");
+        final String authorityContacts = "contacts";
+        final String authorityCalendar = "calendar";
+        final String authorityOther = "other";
+        final String authorityContactsNew = "com.android.contacts";
+        final String authorityCalendarNew = "com.android.calendar";
+
+        MockContentResolver mockResolver = new MockContentResolver();
+
+        final TestContext testContext = new TestContext(mockResolver, getContext());
+
+        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<accounts>\n"
+                + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
+                + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
+                + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
+                + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
+                + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
+                + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
+                + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+                + " authority=\"com.android.calendar\" />\n"
+                + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+                + " authority=\"com.android.contacts\" />\n"
+                + "</accounts>\n").getBytes();
+
+        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+        syncDir.mkdirs();
+        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        FileOutputStream fos = accountInfoFile.startWrite();
+        fos.write(accountsFileData);
+        accountInfoFile.finishWrite(fos);
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+        assertEquals(false, engine.getSyncAutomatically(account1, authorityContacts));
+        assertEquals(false, engine.getSyncAutomatically(account1, authorityCalendar));
+        assertEquals(true, engine.getSyncAutomatically(account1, authorityOther));
+        assertEquals(true, engine.getSyncAutomatically(account1, authorityContactsNew));
+        assertEquals(true, engine.getSyncAutomatically(account1, authorityCalendarNew));
+
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityContacts));
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendar));
+        assertEquals(true, engine.getSyncAutomatically(account2, authorityOther));
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityContactsNew));
+        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendarNew));
+    }
+
+    @SmallTest
+    public void testSyncableMigration() throws Exception {
+        final Account account = new Account("acc", "type");
+
+        MockContentResolver mockResolver = new MockContentResolver();
+
+        final TestContext testContext = new TestContext(mockResolver, getContext());
+
+        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<accounts>\n"
+                + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
+                + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
+                + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
+                + " authority=\"other3\" />\n"
+                + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
+                + " authority=\"other4\" />\n"
+                + "</accounts>\n").getBytes();
+
+        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+        syncDir.mkdirs();
+        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        FileOutputStream fos = accountInfoFile.startWrite();
+        fos.write(accountsFileData);
+        accountInfoFile.finishWrite(fos);
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+        assertEquals(-1, engine.getIsSyncable(account, "other1"));
+        assertEquals(1, engine.getIsSyncable(account, "other2"));
+        assertEquals(0, engine.getIsSyncable(account, "other3"));
+        assertEquals(1, engine.getIsSyncable(account, "other4"));
+    }
 }
 
 class TestContext extends ContextWrapper {
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java
index 8dc206c..15d692c 100755
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/location/java/com/android/internal/location/GpsLocationProvider.java
@@ -63,13 +63,14 @@
 import java.util.Date;
 import java.util.Properties;
 import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * A GPS implementation of LocationProvider used by LocationManager.
  *
  * {@hide}
  */
-public class GpsLocationProvider implements LocationProviderInterface, Runnable {
+public class GpsLocationProvider implements LocationProviderInterface {
 
     private static final String TAG = "GpsLocationProvider";
 
@@ -190,7 +191,7 @@
     private static final int NO_FIX_TIMEOUT = 60;
 
     // true if we are enabled
-    private boolean mEnabled;
+    private volatile boolean mEnabled;
     
     // true if we have network connectivity
     private boolean mNetworkAvailable;
@@ -236,7 +237,13 @@
     private Bundle mLocationExtras = new Bundle();
     private ArrayList<Listener> mListeners = new ArrayList<Listener>();
 
+    // GpsLocationProvider's handler thread
+    private final Thread mThread;
+    // Handler for processing events in mThread.
     private Handler mHandler;
+    // Used to signal when our main thread has initialized everything
+    private final CountDownLatch mInitializedLatch = new CountDownLatch(1);
+    // Thread for receiving events from the native code
     private Thread mEventThread;
 
     private String mAGpsApn;
@@ -389,8 +396,17 @@
             Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
         }
 
-        Thread thread = new Thread(null, this, "GpsLocationProvider");
-        thread.start();
+        // wait until we are fully initialized before returning
+        mThread = new GpsLocationProviderThread();
+        mThread.start();
+        while (true) {
+            try {
+                mInitializedLatch.await();
+                break;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
     }
 
     private void initialize() {
@@ -673,7 +689,7 @@
     }
 
     private void handleDisable() {
-        if (DEBUG) Log.d(TAG, "handleEnable");
+        if (DEBUG) Log.d(TAG, "handleDisable");
         if (!mEnabled) return;
 
         mEnabled = false;
@@ -1327,7 +1343,7 @@
     // native_wait_for_event() will callback to us via reportLocation(), reportStatus(), etc.
     // this is necessary because native code cannot call Java on a thread that the JVM does
     // not know about.
-    private class GpsEventThread extends Thread {
+    private final class GpsEventThread extends Thread {
 
         public GpsEventThread() {
             super("GpsEventThread");
@@ -1384,13 +1400,21 @@
         }
     };
 
-    public void run()
-    {
-        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-        initialize();
-        Looper.prepare();
-        mHandler = new ProviderHandler();
-        Looper.loop();
+    private final class GpsLocationProviderThread extends Thread {
+
+        public GpsLocationProviderThread() {
+            super("GpsLocationProvider");
+        }
+
+        public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            initialize();
+            Looper.prepare();
+            mHandler = new ProviderHandler();
+            // signal when we are initialized and ready to go
+            mInitializedLatch.countDown();
+            Looper.loop();
+        }
     }
 
     // for GPS SV statistics
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 4d7b393..b3705c3 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -5214,8 +5214,19 @@
                 }
             }
             if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId);
-                return false;
+                Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId +
+                        " which might be stale. Will try to clean up.");
+                // Clean up the stale container and proceed to recreate.
+                if (!PackageHelper.destroySdDir(newCacheId)) {
+                    Slog.e(TAG, "Very strange. Cannot clean up stale container " + newCacheId);
+                    return false;
+                }
+                // Successfully cleaned up stale container. Try to rename again.
+                if (!PackageHelper.renameSdDir(cid, newCacheId)) {
+                    Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId
+                            + " inspite of cleaning it up.");
+                    return false;
+                }
             }
             if (!PackageHelper.isContainerMounted(newCacheId)) {
                 Slog.w(TAG, "Mounting container " + newCacheId);
diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
index 6975c70..449661c 100755
--- a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
@@ -520,24 +520,6 @@
             }
         }
     }
-    
-    public void clearSecureContainersForPkg(String pkgName) {
-        IMountService ms = getMs();
-        try {
-            String list[] = ms.getSecureContainerList();
-            if (list != null) {
-                for (String cid : list) {
-                    boolean delete = false;
-                    // STOPSHIP issues with rename should be fixed.
-                    if (cid.contains(pkgName) ||
-                            cid.contains("smdltmp")) {
-                        Log.i(TAG, "Destroying container " + cid);
-                        ms.destroySecureContainer(cid, true);
-                    }
-                }
-            }
-        } catch (RemoteException e) {}
-    }
 
     /*
      * Utility function that reads a apk bundled as a raw resource
@@ -792,7 +774,7 @@
                         waitTime += WAIT_TIME_INCR;
                     }
                     if(!receiver.isDone()) {
-                        throw new Exception("Timed out waiting for PACKAGE_ADDED notification");
+                        throw new Exception("Timed out waiting for PACKAGE_REMOVED notification");
                     }
                     return receiver.received;
                 }
@@ -2186,6 +2168,49 @@
             }
         }
     }
+
+    /* This test creates a stale container via MountService and then installs
+     * a package and verifies that the stale container is cleaned up and install
+     * is successful.
+     * Please note that this test is very closely tied to the framework's
+     * naming convention for secure containers.
+     */
+    public void testInstallSdcardStaleContainer() {
+        boolean origMediaState = getMediaState();
+        try {
+            String outFileName = "install.apk";
+            int rawResId = R.raw.install;
+            PackageManager pm = mContext.getPackageManager();
+            File filesDir = mContext.getFilesDir();
+            File outFile = new File(filesDir, outFileName);
+            Uri packageURI = getInstallablePackage(rawResId, outFile);
+            PackageParser.Package pkg = parsePackage(packageURI);
+            assertNotNull(pkg);
+            // Install an app on sdcard.
+            installFromRawResource(outFileName, rawResId,
+                    PackageManager.INSTALL_EXTERNAL, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            // Unmount sdcard
+            unmountMedia();
+            // Delete the app on sdcard to leave a stale container on sdcard.
+            GenericReceiver receiver = new DeleteReceiver(pkg.packageName);
+            assertTrue(invokeDeletePackage(packageURI, 0, pkg.packageName, receiver));
+            mountMedia();
+            // Reinstall the app and make sure it gets installed.
+            installFromRawResource(outFileName, rawResId,
+                    PackageManager.INSTALL_EXTERNAL, true,
+                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        } catch (Exception e) { 
+            failStr(e.getMessage());
+        } finally {
+            if (origMediaState) {
+                mountMedia();
+            } else {
+                unmountMedia();
+            }
+
+        }
+    }
     /*---------- Recommended install location tests ----*/
     /*
      * TODO's