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