Merge "Preventing launcher crashes due to low disk space." into ub-launcher3-burnaby-polish

This commit is contained in:
Sunny Goyal
2015-12-18 22:29:47 +00:00
committed by Android (Google) Code Review
3 changed files with 218 additions and 129 deletions
+71 -80
View File
@@ -28,7 +28,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -48,6 +48,7 @@ import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
import java.util.Collections;
@@ -230,9 +231,9 @@ public class IconCache {
public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
removeFromMemCacheLocked(packageName, user);
long userSerial = mUserManager.getSerialNumberForUser(user);
mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
mIconDb.delete(
IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
new String[] {packageName + "/%", Long.toString(userSerial)});
new String[]{packageName + "/%", Long.toString(userSerial)});
}
public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
@@ -275,58 +276,65 @@ public class IconCache {
componentMap.put(app.getComponentName(), app);
}
Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
new String[] {IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
IconDB.COLUMN_SYSTEM_STATE},
IconDB.COLUMN_USER + " = ? ",
new String[] {Long.toString(userSerial)},
null, null, null);
final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
HashSet<Integer> itemsToRemove = new HashSet<Integer>();
Stack<LauncherActivityInfoCompat> appsToUpdate = new Stack<>();
while (c.moveToNext()) {
String cn = c.getString(indexComponent);
ComponentName component = ComponentName.unflattenFromString(cn);
PackageInfo info = pkgInfoMap.get(component.getPackageName());
if (info == null) {
if (!ignorePackages.contains(component.getPackageName())) {
Cursor c = null;
try {
c = mIconDb.query(
new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
IconDB.COLUMN_SYSTEM_STATE},
IconDB.COLUMN_USER + " = ? ",
new String[]{Long.toString(userSerial)});
final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
while (c.moveToNext()) {
String cn = c.getString(indexComponent);
ComponentName component = ComponentName.unflattenFromString(cn);
PackageInfo info = pkgInfoMap.get(component.getPackageName());
if (info == null) {
if (!ignorePackages.contains(component.getPackageName())) {
remove(component, user);
itemsToRemove.add(c.getInt(rowIndex));
}
continue;
}
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
// Application is not present
continue;
}
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
LauncherActivityInfoCompat app = componentMap.remove(component);
if (version == info.versionCode && updateTime == info.lastUpdateTime &&
TextUtils.equals(mSystemState, c.getString(systemStateIndex))) {
continue;
}
if (app == null) {
remove(component, user);
itemsToRemove.add(c.getInt(rowIndex));
} else {
appsToUpdate.add(app);
}
continue;
}
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
// Application is not present
continue;
}
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
LauncherActivityInfoCompat app = componentMap.remove(component);
if (version == info.versionCode && updateTime == info.lastUpdateTime &&
TextUtils.equals(mSystemState, c.getString(systemStateIndex))) {
continue;
}
if (app == null) {
remove(component, user);
itemsToRemove.add(c.getInt(rowIndex));
} else {
appsToUpdate.add(app);
} catch (SQLiteException e) {
Log.d(TAG, "Error reading icon cache", e);
// Continue updating whatever we have read so far
} finally {
if (c != null) {
c.close();
}
}
c.close();
if (!itemsToRemove.isEmpty()) {
mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove),
null);
mIconDb.delete(
Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
}
// Insert remaining apps.
@@ -356,8 +364,7 @@ public class IconCache {
values.put(IconDB.COLUMN_USER, userSerial);
values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
values.put(IconDB.COLUMN_VERSION, info.versionCode);
mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
mIconDb.insertOrReplace(values);
}
@Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
@@ -672,19 +679,18 @@ public class IconCache {
label, Color.TRANSPARENT);
values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
values.put(IconDB.COLUMN_USER, userSerial);
mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
mIconDb.insertOrReplace(values);
}
private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
Cursor c = null;
try {
c = mIconDb.query(
new String[]{lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
IconDB.COLUMN_LABEL},
IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
new String[] {cacheKey.componentName.flattenToString(),
Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))},
null, null, null);
try {
new String[]{cacheKey.componentName.flattenToString(),
Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
if (c.moveToNext()) {
entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : null);
entry.isLowResIcon = lowRes;
@@ -698,8 +704,12 @@ public class IconCache {
}
return true;
}
} catch (SQLiteException e) {
Log.d(TAG, "Error reading icon cache", e);
} finally {
c.close();
if (c != null) {
c.close();
}
}
return false;
}
@@ -745,9 +755,9 @@ public class IconCache {
LauncherActivityInfoCompat app = mAppsToUpdate.pop();
String cn = app.getComponentName().flattenToString();
ContentValues values = updateCacheAndGetContentValues(app, true);
mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
mIconDb.update(values,
IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
new String[] {cn, Long.toString(mUserSerial)});
new String[]{cn, Long.toString(mUserSerial)});
mUpdatedPackages.add(app.getComponentName().getPackageName());
if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
@@ -782,7 +792,7 @@ public class IconCache {
mSystemState = Locale.getDefault().toString();
}
private static final class IconDB extends SQLiteOpenHelper {
private static final class IconDB extends SQLiteCacheHelper {
private final static int DB_VERSION = 7;
private final static int RELEASE_VERSION = DB_VERSION +
@@ -800,11 +810,11 @@ public class IconCache {
private final static String COLUMN_SYSTEM_STATE = "system_state";
public IconDB(Context context) {
super(context, LauncherFiles.APP_ICONS_DB, null, RELEASE_VERSION);
super(context, LauncherFiles.APP_ICONS_DB, RELEASE_VERSION, TABLE_NAME);
}
@Override
public void onCreate(SQLiteDatabase db) {
protected void onCreateTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
COLUMN_COMPONENT + " TEXT NOT NULL, " +
COLUMN_USER + " INTEGER NOT NULL, " +
@@ -817,25 +827,6 @@ public class IconCache {
"PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
clearDB(db);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
clearDB(db);
}
}
private void clearDB(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) {
@@ -10,7 +10,6 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
@@ -32,6 +31,7 @@ import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.WidgetCell;
@@ -104,7 +104,7 @@ public class WidgetPreviewLoader {
* The DB holds the generated previews for various components. Previews can also have different
* sizes (landscape vs portrait).
*/
private static class CacheDb extends SQLiteOpenHelper {
private static class CacheDb extends SQLiteCacheHelper {
private static final int DB_VERSION = 4;
private static final String TABLE_NAME = "shortcut_and_widget_previews";
@@ -117,11 +117,11 @@ public class WidgetPreviewLoader {
private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
public CacheDb(Context context) {
super(context, LauncherFiles.WIDGET_PREVIEWS_DB, null, DB_VERSION);
super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME);
}
@Override
public void onCreate(SQLiteDatabase database) {
public void onCreateTable(SQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
COLUMN_COMPONENT + " TEXT NOT NULL, " +
COLUMN_USER + " INTEGER NOT NULL, " +
@@ -133,25 +133,6 @@ public class WidgetPreviewLoader {
"PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " +
");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
clearDB(db);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
clearDB(db);
}
}
private void clearDB(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
private WidgetCacheKey getObjectKey(Object o, String size) {
@@ -176,13 +157,7 @@ public class WidgetPreviewLoader {
values.put(CacheDb.COLUMN_VERSION, versions[0]);
values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
values.put(CacheDb.COLUMN_PREVIEW_BITMAP, Utilities.flattenBitmap(preview));
try {
mDb.getWritableDatabase().insertWithOnConflict(CacheDb.TABLE_NAME, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
} catch (SQLException e) {
Log.e(TAG, "Error saving image to DB", e);
}
mDb.insertOrReplace(values);
}
public void removePackage(String packageName, UserHandleCompat user) {
@@ -194,13 +169,9 @@ public class WidgetPreviewLoader {
mPackageVersions.remove(packageName);
}
try {
mDb.getWritableDatabase().delete(CacheDb.TABLE_NAME,
CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
new String[] {packageName, Long.toString(userSerial)});
} catch (SQLException e) {
Log.e(TAG, "Unable to delete items from DB", e);
}
mDb.delete(
CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
new String[]{packageName, Long.toString(userSerial)});
}
/**
@@ -238,10 +209,10 @@ public class WidgetPreviewLoader {
LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
Cursor c = null;
try {
c = mDb.getReadableDatabase().query(CacheDb.TABLE_NAME,
new String[] {CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
null, null, null, null, null);
c = mDb.query(
new String[]{CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
null, null);
while (c.moveToNext()) {
long userId = c.getLong(0);
String pkg = c.getString(1);
@@ -274,7 +245,7 @@ public class WidgetPreviewLoader {
}
}
} catch (SQLException e) {
Log.e(TAG, "Error updatating widget previews", e);
Log.e(TAG, "Error updating widget previews", e);
} finally {
if (c != null) {
c.close();
@@ -288,16 +259,15 @@ public class WidgetPreviewLoader {
@Thunk Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle, PreviewLoadTask loadTask) {
Cursor cursor = null;
try {
cursor = mDb.getReadableDatabase().query(
CacheDb.TABLE_NAME,
new String[] { CacheDb.COLUMN_PREVIEW_BITMAP },
CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND " + CacheDb.COLUMN_SIZE + " = ?",
new String[] {
cursor = mDb.query(
new String[]{CacheDb.COLUMN_PREVIEW_BITMAP},
CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
+ CacheDb.COLUMN_SIZE + " = ?",
new String[]{
key.componentName.flattenToString(),
Long.toString(mUserManager.getSerialNumberForUser(key.user)),
key.size
},
null, null, null);
});
// If cancelled, skip getting the blob and decoding it into a bitmap
if (loadTask.isCancelled()) {
return null;
@@ -0,0 +1,128 @@
package com.android.launcher3.util;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
/**
* An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
* Any exception during write operations are ignored, and any version change causes a DB reset.
*/
public abstract class SQLiteCacheHelper {
private static final String TAG = "SQLiteCacheHelper";
private final String mTableName;
private final MySQLiteOpenHelper mOpenHelper;
private boolean mIgnoreWrites;
public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
mTableName = tableName;
mOpenHelper = new MySQLiteOpenHelper(context, name, version);
mIgnoreWrites = false;
}
/**
* @see SQLiteDatabase#update(String, ContentValues, String, String[])
*/
public void update(ContentValues values, String whereClause, String[] whereArgs) {
if (mIgnoreWrites) {
return;
}
try {
mOpenHelper.getWritableDatabase().update(mTableName, values, whereClause, whereArgs);
} catch (SQLiteFullException e) {
onDiskFull(e);
} catch (SQLiteException e) {
Log.d(TAG, "Ignoring sqlite exception", e);
}
}
/**
* @see SQLiteDatabase#delete(String, String, String[])
*/
public void delete(String whereClause, String[] whereArgs) {
if (mIgnoreWrites) {
return;
}
try {
mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
} catch (SQLiteFullException e) {
onDiskFull(e);
} catch (SQLiteException e) {
Log.d(TAG, "Ignoring sqlite exception", e);
}
}
/**
* @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
*/
public void insertOrReplace(ContentValues values) {
if (mIgnoreWrites) {
return;
}
try {
mOpenHelper.getWritableDatabase().insertWithOnConflict(
mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
} catch (SQLiteFullException e) {
onDiskFull(e);
} catch (SQLiteException e) {
Log.d(TAG, "Ignoring sqlite exception", e);
}
}
private void onDiskFull(SQLiteFullException e) {
Log.e(TAG, "Disk full, all write operations will be ignored", e);
mIgnoreWrites = true;
}
/**
* @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
*/
public Cursor query(String[] columns, String selection, String[] selectionArgs) {
return mOpenHelper.getReadableDatabase().query(
mTableName, columns, selection, selectionArgs, null, null, null);
}
protected abstract void onCreateTable(SQLiteDatabase db);
/**
* A private inner class to prevent direct DB access.
*/
private class MySQLiteOpenHelper extends SQLiteOpenHelper {
public MySQLiteOpenHelper(Context context, String name, int version) {
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
onCreateTable(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
clearDB(db);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
clearDB(db);
}
}
private void clearDB(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + mTableName);
onCreate(db);
}
}
}