diff options
23 files changed, 913 insertions, 319 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e7b703cab..9192764b5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -53,8 +53,9 @@ <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" /> <application - android:allowBackup="@bool/enable_backup" - android:backupAgent="com.android.launcher3.LauncherBackupAgentHelper" + android:backupAgent="com.android.launcher3.LauncherBackupAgent" + android:fullBackupOnly="true" + android:fullBackupContent="@xml/backupscheme" android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher_home" android:label="@string/app_name" diff --git a/build.gradle b/build.gradle index 4df406363..d777e95c8 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.0' } } diff --git a/res/xml/backupscheme.xml b/res/xml/backupscheme.xml new file mode 100644 index 000000000..7e833a0ca --- /dev/null +++ b/res/xml/backupscheme.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<full-backup-content xmlns:android="http://schemas.android.com/apk/res/android"> + + <include domain="database" path="launcher.db" /> + <include domain="sharedpref" path="com.android.launcher3.prefs.xml" /> + +</full-backup-content>
\ No newline at end of file diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 0d71a0c6c..a04c5570a 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -630,7 +630,7 @@ public class AutoInstallsLayout { copyInteger(myValues, childValues, Favorites.CELLY); addedId = folderItems.get(0); - mDb.update(LauncherProvider.TABLE_FAVORITES, childValues, + mDb.update(Favorites.TABLE_NAME, childValues, Favorites._ID + "=" + addedId, null); } } diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index bb70be697..7e1ecf5af 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -16,8 +16,12 @@ package com.android.launcher3; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -27,12 +31,13 @@ import android.view.ViewDebug; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; public class Hotseat extends FrameLayout - implements UserEventDispatcher.LaunchSourceProvider{ + implements UserEventDispatcher.LaunchSourceProvider, Insettable { private CellLayout mContent; @@ -44,6 +49,14 @@ public class Hotseat extends FrameLayout @ViewDebug.ExportedProperty(category = "launcher") private final boolean mHasVerticalHotseat; + @ViewDebug.ExportedProperty(category = "launcher") + private Rect mInsets = new Rect(); + + @ViewDebug.ExportedProperty(category = "launcher") + private int mBackgroundColor; + @ViewDebug.ExportedProperty(category = "launcher") + private ColorDrawable mBackground; + public Hotseat(Context context) { this(context, null); } @@ -56,6 +69,8 @@ public class Hotseat extends FrameLayout super(context, attrs, defStyle); mLauncher = (Launcher) context; mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout(); + mBackground = new ColorDrawable(); + setBackground(mBackground); } public CellLayout getLayout() { @@ -166,4 +181,46 @@ public class Hotseat extends FrameLayout target.gridY = info.cellY; targetParent.containerType = LauncherLogProto.HOTSEAT; } + + //Overridden so that the background color extends behind the navigation buttons. + @Override + public void setInsets(Rect insets) { + int rightInset = insets.right - mInsets.right; + int bottomInset = insets.bottom - mInsets.bottom; + mInsets.set(insets); + LayoutParams lp = (LayoutParams) getLayoutParams(); + if (mHasVerticalHotseat) { + setPadding(getPaddingLeft(), getPaddingTop(), + getPaddingRight() + rightInset, getPaddingBottom()); + if (lp.width != LayoutParams.MATCH_PARENT && lp.width != LayoutParams.WRAP_CONTENT) { + lp.width += rightInset; + } + } else { + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), + getPaddingBottom() + bottomInset); + if (lp.height != LayoutParams.MATCH_PARENT && lp.height != LayoutParams.WRAP_CONTENT) { + lp.height += bottomInset; + } + } + } + + public void updateColor(ExtractedColors extractedColors, boolean animate) { + if (!mHasVerticalHotseat) { + int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT); + if (!animate) { + setBackgroundColor(color); + } else { + ValueAnimator animator = ValueAnimator.ofInt(mBackgroundColor, color); + animator.setEvaluator(new ArgbEvaluator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mBackground.setColor((Integer) animation.getAnimatedValue()); + } + }); + animator.start(); + } + mBackgroundColor = color; + } + } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b2f96d099..eacf72aa1 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -114,6 +114,7 @@ import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewOnDrawExecutor; @@ -123,11 +124,9 @@ import com.android.launcher3.widget.WidgetsContainerView; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -307,11 +306,6 @@ public class Launcher extends Activity private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>(); private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false; - private static final ArrayList<String> sDumpLogs = new ArrayList<String>(); - private static final Date sDateStamp = new Date(); - private static final DateFormat sDateFormat = - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); - // We only want to get the SharedPreferences once since it does an FS stat each time we get // it from the context. private SharedPreferences mSharedPrefs; @@ -502,9 +496,10 @@ public class Launcher extends Activity } private void loadExtractedColorsAndColorItems() { - if (mExtractedColors != null) { + // TODO: do this in pre-N as well, once the extraction part is complete. + if (mExtractedColors != null && Utilities.isNycOrAbove()) { mExtractedColors.load(this); - // TODO: pass mExtractedColors to interested items such as hotseat. + mHotseat.updateColor(mExtractedColors, !mPaused); } } @@ -3976,7 +3971,7 @@ public class Launcher extends Activity // Verify that we own the widget if (appWidgetInfo == null) { - Log.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); + FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); deleteWidgetInfo(item); return; } @@ -4649,12 +4644,10 @@ public class Launcher extends Activity } } - synchronized (sDumpLogs) { - writer.println(); - writer.println(prefix + "Debug logs"); - for (String log : sDumpLogs) { - writer.println(prefix + " " + log); - } + try { + FileLog.flushAll(writer); + } catch (Exception e) { + // Ignore } if (mLauncherCallbacks != null) { @@ -4662,14 +4655,6 @@ public class Launcher extends Activity } } - public static void addDumpLog(String tag, String log) { - Log.d(tag, log); - synchronized(sDumpLogs) { - sDateStamp.setTime(System.currentTimeMillis()); - sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log); - } - } - public static CustomAppWidget getCustomAppWidget(String name) { return sCustomAppWidgets.get(name); } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index f84e4b5b4..9d889e09a 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -27,9 +27,9 @@ import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.util.ConfigMonitor; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; @@ -79,6 +79,7 @@ public class LauncherAppState { // is the first component to get created. Initializing application context here ensures // that LauncherAppState always exists in the main process. sContext = provider.getContext().getApplicationContext(); + FileLog.setDir(sContext.getFilesDir()); } private LauncherAppState() { @@ -184,8 +185,4 @@ public class LauncherAppState { public InvariantDeviceProfile getInvariantDeviceProfile() { return mInvariantDeviceProfile; } - - public static boolean isDogfoodBuild() { - return FeatureFlags.IS_ALPHA_BUILD || FeatureFlags.IS_DEV_BUILD; - } } diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index 570607ec7..28557d0a5 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -256,7 +256,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc @Override public void requestChildFocus(View child, View focused) { super.requestChildFocus(child, focused); - dispatchChildFocus(focused != null); + dispatchChildFocus(mChildrenFocused && focused != null); if (focused != null) { focused.setFocusableInTouchMode(false); } diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java new file mode 100644 index 000000000..b2f5c5717 --- /dev/null +++ b/src/com/android/launcher3/LauncherBackupAgent.java @@ -0,0 +1,133 @@ +package com.android.launcher3; + +import android.app.backup.BackupAgent; +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.ParcelFileDescriptor; + +import com.android.launcher3.LauncherProvider.DatabaseHelper; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.logging.FileLog; + +import java.io.InvalidObjectException; + +public class LauncherBackupAgent extends BackupAgent { + + private static final String TAG = "LauncherBackupAgent"; + + private static final String INFO_COLUMN_NAME = "name"; + private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value"; + + @Override + public void onRestore( + BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) { + // Doesn't do incremental backup/restore + } + + @Override + public void onBackup( + ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { + // Doesn't do incremental backup/restore + } + + @Override + public void onRestoreFinished() { + DatabaseHelper helper = new DatabaseHelper(this, null, LauncherFiles.LAUNCHER_DB); + + if (!sanitizeDBSafely(helper)) { + helper.createEmptyDB(helper.getWritableDatabase()); + } + + try { + // Flush all logs before the process is killed. + FileLog.flushAll(null); + } catch (Exception e) { } + } + + private boolean sanitizeDBSafely(DatabaseHelper helper) { + SQLiteDatabase db = helper.getWritableDatabase(); + db.beginTransaction(); + try { + sanitizeDB(helper, db); + db.setTransactionSuccessful(); + return true; + } catch (Exception e) { + FileLog.e(TAG, "Failed to verify db", e); + return false; + } finally { + db.endTransaction(); + } + } + + /** + * Makes the following changes in the provider DB. + * 1. Removes all entries belonging to a managed profile as managed profiles + * cannot be restored. + * 2. Marks all entries as restored. The flags are updated during first load or as + * the restored apps get installed. + * 3. If the user serial for primary profile is different than that of the previous device, + * update the entries to the new profile id. + */ + private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception { + long oldProfileId = getDefaultProfileId(db); + // Delete all entries which do not belong to the main user + int itemsDeleted = db.delete( + Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)}); + if (itemsDeleted > 0) { + FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted"); + } + + // Mark all items as restored. + ContentValues values = new ContentValues(); + values.put(Favorites.RESTORED, 1); + db.update(Favorites.TABLE_NAME, values, null, null); + + // Mark widgets with appropriate restore flag + values.put(Favorites.RESTORED, + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | + LauncherAppWidgetInfo.FLAG_UI_NOT_READY); + db.update(Favorites.TABLE_NAME, values, "itemType = ?", + new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)}); + + long myProfileId = helper.getDefaultUserSerial(); + if (Utilities.longCompare(oldProfileId, myProfileId) != 0) { + FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId); + migrateProfileId(db, myProfileId); + } + } + + /** + * Updates profile id of all entries and changes the default value for the column. + */ + protected void migrateProfileId(SQLiteDatabase db, long newProfileId) { + // Update existing entries. + ContentValues values = new ContentValues(); + values.put(Favorites.PROFILE_ID, newProfileId); + db.update(Favorites.TABLE_NAME, values, null, null); + + // Change default value of the column. + db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;"); + Favorites.addTableToDb(db, newProfileId, false); + db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;"); + db.execSQL("DROP TABLE favorites_old;"); + } + + /** + * Returns the profile id for used in the favorites table of the provided db. + */ + protected long getDefaultProfileId(SQLiteDatabase db) throws Exception { + try (Cursor c = db.rawQuery("PRAGMA table_info (favorites)", null)){ + int nameIndex = c.getColumnIndex(INFO_COLUMN_NAME); + while (c.moveToNext()) { + if (Favorites.PROFILE_ID.equals(c.getString(nameIndex))) { + return c.getLong(c.getColumnIndex(INFO_COLUMN_DEFAULT_VALUE)); + } + } + throw new InvalidObjectException("Table does not have a profile id column"); + } + } +} diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 884685c8a..2fd12fd57 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -60,6 +60,7 @@ import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.CursorIconInfo; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; @@ -1335,7 +1336,7 @@ public class LauncherModel extends BroadcastReceiver try { screenIds.add(sc.getLong(idIndex)); } catch (Exception e) { - addDumpLog("Invalid screen id: " + e); + FileLog.d(TAG, "Invalid screen id", e); } } } finally { @@ -1813,7 +1814,7 @@ public class LauncherModel extends BroadcastReceiver if (intent == null) { // The app is installed but the component is no // longer available. - addDumpLog("Invalid component removed: " + cn); + FileLog.d(TAG, "Invalid component removed: " + cn); itemsToRemove.add(id); continue; } else { @@ -1824,7 +1825,7 @@ public class LauncherModel extends BroadcastReceiver } else if (restored) { // Package is not yet available but might be // installed later. - addDumpLog("package not yet restored: " + cn); + FileLog.d(TAG, "package not yet restored: " + cn); if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) { // Restore has started once. @@ -1850,12 +1851,12 @@ public class LauncherModel extends BroadcastReceiver itemReplaced = true; } else if (REMOVE_UNRESTORED_ICONS) { - addDumpLog("Unrestored package removed: " + cn); + FileLog.d(TAG, "Unrestored package removed: " + cn); itemsToRemove.add(id); continue; } } else if (REMOVE_UNRESTORED_ICONS) { - addDumpLog("Unrestored package removed: " + cn); + FileLog.d(TAG, "Unrestored package removed: " + cn); itemsToRemove.add(id); continue; } @@ -1880,7 +1881,7 @@ public class LauncherModel extends BroadcastReceiver } else { // Do not wait for external media load anymore. // Log the invalid package, and remove it - addDumpLog("Invalid package removed: " + cn); + FileLog.d(TAG, "Invalid package removed: " + cn); itemsToRemove.add(id); continue; } @@ -1890,7 +1891,7 @@ public class LauncherModel extends BroadcastReceiver restored = false; } } catch (URISyntaxException e) { - addDumpLog("Invalid uri: " + intentDescription); + FileLog.d(TAG, "Invalid uri: " + intentDescription); itemsToRemove.add(id); continue; } @@ -2073,7 +2074,7 @@ public class LauncherModel extends BroadcastReceiver final boolean isProviderReady = isValidProvider(provider); if (!isSafeMode && !customWidget && wasProviderReady && !isProviderReady) { - addDumpLog("Deleting widget that isn't installed anymore: " + FileLog.d(TAG, "Deleting widget that isn't installed anymore: " + provider); itemsToRemove.add(id); } else { @@ -2115,7 +2116,7 @@ public class LauncherModel extends BroadcastReceiver appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) { - addDumpLog("Unrestored widget removed: " + component); + FileLog.d(TAG, "Unrestored widget removed: " + component); itemsToRemove.add(id); continue; } @@ -2171,9 +2172,7 @@ public class LauncherModel extends BroadcastReceiver } } } finally { - if (c != null) { - c.close(); - } + Utilities.closeSilently(c); } // Break early if we've stopped loading @@ -3541,8 +3540,4 @@ public class LauncherModel extends BroadcastReceiver public static Looper getWorkerLooper() { return sWorkerThread.getLooper(); } - - @Thunk static final void addDumpLog(String log) { - Launcher.addDumpLog(TAG, log); - } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 53026ac5a..f10099e0c 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -42,20 +42,25 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.Process; import android.os.UserManager; +import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.LauncherSettings.WorkspaceScreens; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.NoLocaleSqliteContext; +import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; import java.net.URISyntaxException; @@ -71,18 +76,19 @@ public class LauncherProvider extends ContentProvider { public static final String AUTHORITY = ProviderConfig.AUTHORITY; - static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME; - static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME; static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED"; private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name"; - private static final Object LISTENER_LOCK = new Object(); - @Thunk LauncherProviderChangeListener mListener; + private final ChangeListenerWrapper mListenerWrapper = new ChangeListenerWrapper(); + private Handler mListenerHandler; + protected DatabaseHelper mOpenHelper; @Override public boolean onCreate() { + mListenerHandler = new Handler(mListenerWrapper); + LauncherAppState.setLauncherProvider(this); return true; } @@ -91,9 +97,8 @@ public class LauncherProvider extends ContentProvider { * Sets a provider listener. */ public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) { - synchronized (LISTENER_LOCK) { - mListener = listener; - } + Preconditions.assertUIThread(); + mListenerWrapper.mListener = listener; } @Override @@ -111,7 +116,7 @@ public class LauncherProvider extends ContentProvider { */ protected synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - mOpenHelper = new DatabaseHelper(getContext(), this); + mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler); } } @@ -159,7 +164,7 @@ public class LauncherProvider extends ContentProvider { // In very limited cases, we support system|signature permission apps to modify the db. if (Binder.getCallingPid() != Process.myPid()) { - if (!mOpenHelper.initializeExternalAdd(initialValues)) { + if (!initializeExternalAdd(initialValues)) { return null; } } @@ -189,6 +194,59 @@ public class LauncherProvider extends ContentProvider { return uri; } + private boolean initializeExternalAdd(ContentValues values) { + // 1. Ensure that externally added items have a valid item id + long id = mOpenHelper.generateNewItemId(); + values.put(LauncherSettings.Favorites._ID, id); + + // 2. In the case of an app widget, and if no app widget id is specified, we + // attempt allocate and bind the widget. + Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); + if (itemType != null && + itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && + !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { + + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext()); + ComponentName cn = ComponentName.unflattenFromString( + values.getAsString(Favorites.APPWIDGET_PROVIDER)); + + if (cn != null) { + try { + int appWidgetId = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID) + .allocateAppWidgetId(); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); + if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { + return false; + } + } catch (RuntimeException e) { + Log.e(TAG, "Failed to initialize external widget", e); + return false; + } + } else { + return false; + } + } + + // Add screen id if not present + long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); + SQLiteStatement stmp = null; + try { + stmp = mOpenHelper.getWritableDatabase().compileStatement( + "INSERT OR IGNORE INTO workspaceScreens (_id, screenRank) " + + "select ?, (ifnull(MAX(screenRank), -1)+1) from workspaceScreens"); + stmp.bindLong(1, screenId); + + ContentValues valuesInserted = new ContentValues(); + valuesInserted.put(LauncherSettings.BaseLauncherColumns._ID, stmp.executeInsert()); + mOpenHelper.checkId(WorkspaceScreens.TABLE_NAME, valuesInserted); + return true; + } catch (Exception e) { + return false; + } finally { + Utilities.closeSilently(stmp); + } + } + @Override public int bulkInsert(Uri uri, ContentValues[] values) { createDbIfNotExists(); @@ -295,17 +353,7 @@ public class LauncherProvider extends ContentProvider { .putString(ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, extractedColors) .putInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, wallpaperId) .apply(); - new MainThreadExecutor().execute(new Runnable() { - @Override - public void run() { - synchronized (LISTENER_LOCK) { - if (mListener != null) { - mListener.onExtractedColorsChanged(); - } - } - - } - }); + mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_EXTRACTED_COLORS_CHANGED); Bundle result = new Bundle(); result.putString(LauncherSettings.Settings.EXTRA_VALUE, extractedColors); return result; @@ -340,6 +388,8 @@ public class LauncherProvider extends ContentProvider { case LauncherSettings.Settings.METHOD_MIGRATE_LAUNCHER2_SHORTCUTS: { mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), Uri.parse(getContext().getString(R.string.old_launcher_provider_uri))); + Utilities.getPrefs(getContext()).edit().putBoolean(EMPTY_DATABASE_CREATED, false) + .commit(); return null; } case LauncherSettings.Settings.METHOD_UPDATE_FOLDER_ITEMS_RANK: { @@ -373,8 +423,8 @@ public class LauncherProvider extends ContentProvider { + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + " AND " + LauncherSettings.Favorites._ID + " NOT IN (SELECT " + LauncherSettings.Favorites.CONTAINER + " FROM " - + TABLE_FAVORITES + ")"; - Cursor c = db.query(TABLE_FAVORITES, + + Favorites.TABLE_NAME + ")"; + Cursor c = db.query(Favorites.TABLE_NAME, new String[] {LauncherSettings.Favorites._ID}, selection, null, null, null, null); while (c.moveToNext()) { @@ -382,7 +432,7 @@ public class LauncherProvider extends ContentProvider { } c.close(); if (!folderIds.isEmpty()) { - db.delete(TABLE_FAVORITES, Utilities.createDbSelectionQuery( + db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery( LauncherSettings.Favorites._ID, folderIds), null); } db.setTransactionSuccessful(); @@ -401,11 +451,7 @@ public class LauncherProvider extends ContentProvider { protected void notifyListeners() { // always notify the backup agent LauncherBackupAgentHelper.dataChanged(getContext()); - synchronized (LISTENER_LOCK) { - if (mListener != null) { - mListener.onLauncherProviderChange(); - } - } + mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_LAUNCHER_PROVIDER_CHANGED); } @Thunk static void addModifiedTime(ContentValues values) { @@ -436,10 +482,10 @@ public class LauncherProvider extends ContentProvider { if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { Log.d(TAG, "loading default workspace"); - AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(); + AppWidgetHost widgetHost = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID); + AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost); if (loader == null) { - loader = AutoInstallsLayout.get(getContext(), - mOpenHelper.mAppWidgetHost, mOpenHelper); + loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper); } if (loader == null) { final Partner partner = Partner.get(getContext().getPackageManager()); @@ -448,7 +494,7 @@ public class LauncherProvider extends ContentProvider { int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, "xml", partner.getPackageName()); if (workspaceResId != 0) { - loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, + loader = new DefaultLayoutParser(getContext(), widgetHost, mOpenHelper, partnerRes, workspaceResId); } } @@ -456,7 +502,7 @@ public class LauncherProvider extends ContentProvider { final boolean usingExternallyProvidedLayout = loader != null; if (loader == null) { - loader = getDefaultLayoutParser(); + loader = getDefaultLayoutParser(widgetHost); } // There might be some partially restored DB items, due to buggy restore logic in @@ -468,7 +514,7 @@ public class LauncherProvider extends ContentProvider { // Unable to load external layout. Cleanup and load the internal layout. createEmptyDB(); mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), - getDefaultLayoutParser()); + getDefaultLayoutParser(widgetHost)); } clearFlagEmptyDbCreated(); } @@ -480,7 +526,7 @@ public class LauncherProvider extends ContentProvider { * @return the loader if the restrictions are set and the resource exists; null otherwise. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() { + private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) { // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18 if (!Utilities.ATLEAST_JB_MR2) { return null; @@ -499,7 +545,7 @@ public class LauncherProvider extends ContentProvider { Resources targetResources = ctx.getPackageManager() .getResourcesForApplication(packageName); return AutoInstallsLayout.get(ctx, packageName, targetResources, - mOpenHelper.mAppWidgetHost, mOpenHelper); + widgetHost, mOpenHelper); } catch (NameNotFoundException e) { Log.e(TAG, "Target package for restricted profile not found", e); return null; @@ -508,50 +554,28 @@ public class LauncherProvider extends ContentProvider { return null; } - private DefaultLayoutParser getDefaultLayoutParser() { + private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) { int defaultLayout = LauncherAppState.getInstance() .getInvariantDeviceProfile().defaultLayoutId; - return new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, + return new DefaultLayoutParser(getContext(), widgetHost, mOpenHelper, getContext().getResources(), defaultLayout); } /** - * Send notification that we've deleted the {@link AppWidgetHost}, - * probably as part of the initial database creation. The receiver may - * want to re-call {@link AppWidgetHost#startListening()} to ensure - * callbacks are correctly set. - */ - @Thunk void notifyAppHostReset() { - new MainThreadExecutor().execute(new Runnable() { - - @Override - public void run() { - synchronized (LISTENER_LOCK) { - if (mListener != null) { - mListener.onAppWidgetHostReset(); - } - } - } - }); - } - - /** * The class is subclassed in tests to create an in-memory db. */ - protected static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { - private final LauncherProvider mProvider; + public static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { + private final Handler mWidgetHostResetHandler; private final Context mContext; - @Thunk final AppWidgetHost mAppWidgetHost; private long mMaxItemId = -1; private long mMaxScreenId = -1; - DatabaseHelper(Context context, LauncherProvider provider) { - this(context, provider, LauncherFiles.LAUNCHER_DB, - new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID)); + DatabaseHelper(Context context, Handler widgetHostResetHandler) { + this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB); // Table creation sometimes fails silently, which leads to a crash loop. // This way, we will try to create a table every time after crash, so the device // would eventually be able to recover. - if (!tableExists(TABLE_FAVORITES) || !tableExists(TABLE_WORKSPACE_SCREENS)) { + if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) { Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate"); // This operation is a no-op if the table already exists. addFavoritesTable(getWritableDatabase(), true); @@ -562,14 +586,13 @@ public class LauncherProvider extends ContentProvider { } /** - * Constructor used only in tests. + * Constructor used in tests and for restore. */ public DatabaseHelper( - Context context, LauncherProvider provider, String tableName, AppWidgetHost host) { + Context context, Handler widgetHostResetHandler, String tableName) { super(new NoLocaleSqliteContext(context), tableName, null, DATABASE_VERSION); mContext = context; - mProvider = provider; - mAppWidgetHost = host; + mWidgetHostResetHandler = widgetHostResetHandler; } protected void initIds() { @@ -605,12 +628,6 @@ public class LauncherProvider extends ContentProvider { addFavoritesTable(db, false); addWorkspacesTable(db, false); - // Database was just created, so wipe any previous widgets - if (mAppWidgetHost != null) { - mAppWidgetHost.deleteHost(); - mProvider.notifyAppHostReset(); - } - // Fresh and clean launcher DB. mMaxItemId = initializeMaxItemId(db); onEmptyDbCreated(); @@ -620,6 +637,13 @@ public class LauncherProvider extends ContentProvider { * Overriden in tests. */ protected void onEmptyDbCreated() { + // Database was just created, so wipe any previous widgets + if (mWidgetHostResetHandler != null) { + new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID).deleteHost(); + mWidgetHostResetHandler.sendEmptyMessage( + ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET); + } + // Set the flag for empty DB Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit(); @@ -634,35 +658,12 @@ public class LauncherProvider extends ContentProvider { } private void addFavoritesTable(SQLiteDatabase db, boolean optional) { - String ifNotExists = optional ? " IF NOT EXISTS " : ""; - db.execSQL("CREATE TABLE " + ifNotExists + TABLE_FAVORITES + " (" + - "_id INTEGER PRIMARY KEY," + - "title TEXT," + - "intent TEXT," + - "container INTEGER," + - "screen INTEGER," + - "cellX INTEGER," + - "cellY INTEGER," + - "spanX INTEGER," + - "spanY INTEGER," + - "itemType INTEGER," + - "appWidgetId INTEGER NOT NULL DEFAULT -1," + - "iconType INTEGER," + - "iconPackage TEXT," + - "iconResource TEXT," + - "icon BLOB," + - "appWidgetProvider TEXT," + - "modified INTEGER NOT NULL DEFAULT 0," + - "restored INTEGER NOT NULL DEFAULT 0," + - "profileId INTEGER DEFAULT " + getDefaultUserSerial() + "," + - "rank INTEGER NOT NULL DEFAULT 0," + - "options INTEGER NOT NULL DEFAULT 0" + - ");"); + Favorites.addTableToDb(db, getDefaultUserSerial(), optional); } private void addWorkspacesTable(SQLiteDatabase db, boolean optional) { String ifNotExists = optional ? " IF NOT EXISTS " : ""; - db.execSQL("CREATE TABLE " + ifNotExists + TABLE_WORKSPACE_SCREENS + " (" + + db.execSQL("CREATE TABLE " + ifNotExists + WorkspaceScreens.TABLE_NAME + " (" + LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," + LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," + LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" + @@ -673,10 +674,10 @@ public class LauncherProvider extends ContentProvider { // Delete items directly on the workspace who's screen id doesn't exist // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens) // AND container = -100" - String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES + + String removeOrphanedDesktopItems = "DELETE FROM " + Favorites.TABLE_NAME + " WHERE " + LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " + - LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" + + LauncherSettings.WorkspaceScreens._ID + " FROM " + WorkspaceScreens.TABLE_NAME + ")" + " AND " + LauncherSettings.Favorites.CONTAINER + " = " + LauncherSettings.Favorites.CONTAINER_DESKTOP; @@ -685,7 +686,7 @@ public class LauncherProvider extends ContentProvider { // Delete items contained in folders which no longer exist (after above statement) // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)" - String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES + + String removeOrphanedFolderItems = "DELETE FROM " + Favorites.TABLE_NAME + " WHERE " + LauncherSettings.Favorites.CONTAINER + " <> " + LauncherSettings.Favorites.CONTAINER_DESKTOP + @@ -694,16 +695,12 @@ public class LauncherProvider extends ContentProvider { LauncherSettings.Favorites.CONTAINER_HOTSEAT + " AND " + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " + - LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES + + LauncherSettings.Favorites._ID + " FROM " + Favorites.TABLE_NAME + " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")"; db.execSQL(removeOrphanedFolderItems); } - private void setFlagJustLoadedOldDb() { - Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, false).commit(); - } - @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion); @@ -823,8 +820,8 @@ public class LauncherProvider extends ContentProvider { * Clears all the data for a fresh start. */ public void createEmptyDB(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); + db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME); onCreate(db); } @@ -839,9 +836,8 @@ public class LauncherProvider extends ContentProvider { try { // Only consider the primary user as other users can't have a shortcut. - long userSerial = UserManagerCompat.getInstance(mContext) - .getSerialNumberForUser(UserHandleCompat.myUserHandle()); - c = db.query(TABLE_FAVORITES, new String[] { + long userSerial = getDefaultUserSerial(); + c = db.query(Favorites.TABLE_NAME, new String[] { Favorites._ID, Favorites.INTENT, }, "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + " AND profileId=" + userSerial, @@ -891,7 +887,7 @@ public class LauncherProvider extends ContentProvider { public boolean recreateWorkspaceTable(SQLiteDatabase db) { db.beginTransaction(); try { - Cursor c = db.query(TABLE_WORKSPACE_SCREENS, + Cursor c = db.query(WorkspaceScreens.TABLE_NAME, new String[] {LauncherSettings.WorkspaceScreens._ID}, null, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK); @@ -909,7 +905,7 @@ public class LauncherProvider extends ContentProvider { c.close(); } - db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); + db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME); addWorkspacesTable(db, false); // Add all screen ids back @@ -919,7 +915,7 @@ public class LauncherProvider extends ContentProvider { values.put(LauncherSettings.WorkspaceScreens._ID, sortedIDs.get(i)); values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); addModifiedTime(values); - db.insertOrThrow(TABLE_WORKSPACE_SCREENS, null, values); + db.insertOrThrow(WorkspaceScreens.TABLE_NAME, null, values); } db.setTransactionSuccessful(); mMaxScreenId = maxId; @@ -966,12 +962,7 @@ public class LauncherProvider extends ContentProvider { } private boolean addProfileColumn(SQLiteDatabase db) { - UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); - // Default to the serial number of this user, for older - // shortcuts. - long userSerialNumber = userManager.getSerialNumberForUser( - UserHandleCompat.myUserHandle()); - return addIntegerColumn(db, Favorites.PROFILE_ID, userSerialNumber); + return addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial()); } private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) { @@ -1005,12 +996,12 @@ public class LauncherProvider extends ContentProvider { @Override public long insertAndCheck(SQLiteDatabase db, ContentValues values) { - return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); + return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values); } public void checkId(String table, ContentValues values) { long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID); - if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) { + if (table == WorkspaceScreens.TABLE_NAME) { mMaxScreenId = Math.max(id, mMaxScreenId); } else { mMaxItemId = Math.max(id, mMaxItemId); @@ -1018,7 +1009,7 @@ public class LauncherProvider extends ContentProvider { } private long initializeMaxItemId(SQLiteDatabase db) { - return getMaxId(db, TABLE_FAVORITES); + return getMaxId(db, Favorites.TABLE_NAME); } // Generates a new ID to use for an workspace screen in your database. This method @@ -1035,94 +1026,7 @@ public class LauncherProvider extends ContentProvider { } private long initializeMaxScreenId(SQLiteDatabase db) { - return getMaxId(db, TABLE_WORKSPACE_SCREENS); - } - - @Thunk boolean initializeExternalAdd(ContentValues values) { - // 1. Ensure that externally added items have a valid item id - long id = generateNewItemId(); - values.put(LauncherSettings.Favorites._ID, id); - - // 2. In the case of an app widget, and if no app widget id is specified, we - // attempt allocate and bind the widget. - Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); - if (itemType != null && - itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && - !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { - - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); - ComponentName cn = ComponentName.unflattenFromString( - values.getAsString(Favorites.APPWIDGET_PROVIDER)); - - if (cn != null) { - try { - int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); - values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); - if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { - return false; - } - } catch (RuntimeException e) { - Log.e(TAG, "Failed to initialize external widget", e); - return false; - } - } else { - return false; - } - } - - // Add screen id if not present - long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); - if (!addScreenIdIfNecessary(screenId)) { - return false; - } - return true; - } - - // Returns true of screen id exists, or if successfully added - private boolean addScreenIdIfNecessary(long screenId) { - if (!hasScreenId(screenId)) { - int rank = getMaxScreenRank() + 1; - - ContentValues v = new ContentValues(); - v.put(LauncherSettings.WorkspaceScreens._ID, screenId); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); - if (dbInsertAndCheck(this, getWritableDatabase(), - TABLE_WORKSPACE_SCREENS, null, v) < 0) { - return false; - } - } - return true; - } - - private boolean hasScreenId(long screenId) { - SQLiteDatabase db = getWritableDatabase(); - Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE " - + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null); - if (c != null) { - int count = c.getCount(); - c.close(); - return count > 0; - } else { - return false; - } - } - - private int getMaxScreenRank() { - SQLiteDatabase db = getWritableDatabase(); - Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK - + ") FROM " + TABLE_WORKSPACE_SCREENS, null); - - // get the result - final int maxRankIndex = 0; - int rank = -1; - if (c != null && c.moveToNext()) { - rank = c.getInt(maxRankIndex); - } - if (c != null) { - c.close(); - } - - return rank; + return getMaxId(db, WorkspaceScreens.TABLE_NAME); } @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) { @@ -1138,7 +1042,7 @@ public class LauncherProvider extends ContentProvider { values.clear(); values.put(LauncherSettings.WorkspaceScreens._ID, id); values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); - if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) { + if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) { throw new RuntimeException("Failed initialize screen table" + "from default layout"); } @@ -1383,7 +1287,7 @@ public class LauncherProvider extends ContentProvider { try { for (ContentValues row: allItems) { if (row == null) continue; - if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row) + if (dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, row) < 0) { return; } else { @@ -1402,7 +1306,7 @@ public class LauncherProvider extends ContentProvider { final ContentValues values = new ContentValues(); values.put(LauncherSettings.WorkspaceScreens._ID, i); values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) + if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) { return; } @@ -1422,9 +1326,6 @@ public class LauncherProvider extends ContentProvider { Log.d(TAG, "migrated " + count + " icons from Launcher2 into " + (curScreen+1) + " screens"); - // ensure that new screens are created to hold these icons - setFlagJustLoadedOldDb(); - // Update max IDs; very important since we just grabbed IDs from another database mMaxItemId = initializeMaxItemId(db); mMaxScreenId = initializeMaxScreenId(db); @@ -1484,4 +1385,31 @@ public class LauncherProvider extends ContentProvider { } } } + + private static class ChangeListenerWrapper implements Handler.Callback { + + private static final int MSG_LAUNCHER_PROVIDER_CHANGED = 1; + private static final int MSG_EXTRACTED_COLORS_CHANGED = 2; + private static final int MSG_APP_WIDGET_HOST_RESET = 3; + + private LauncherProviderChangeListener mListener; + + @Override + public boolean handleMessage(Message msg) { + if (mListener != null) { + switch (msg.what) { + case MSG_LAUNCHER_PROVIDER_CHANGED: + mListener.onLauncherProviderChange(); + break; + case MSG_EXTRACTED_COLORS_CHANGED: + mListener.onExtractedColorsChanged(); + break; + case MSG_APP_WIDGET_HOST_RESET: + mListener.onAppWidgetHostReset(); + break; + } + } + return true; + } + } } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 0e559a544..13e45476a 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -17,6 +17,7 @@ package com.android.launcher3; import android.content.ContentResolver; +import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Bundle; import android.provider.BaseColumns; @@ -256,6 +257,33 @@ public class LauncherSettings { * <p>Type: INTEGER</p> */ public static final String OPTIONS = "options"; + + public static void addTableToDb(SQLiteDatabase db, long myProfileId, boolean optional) { + String ifNotExists = optional ? " IF NOT EXISTS " : ""; + db.execSQL("CREATE TABLE " + ifNotExists + TABLE_NAME + " (" + + "_id INTEGER PRIMARY KEY," + + "title TEXT," + + "intent TEXT," + + "container INTEGER," + + "screen INTEGER," + + "cellX INTEGER," + + "cellY INTEGER," + + "spanX INTEGER," + + "spanY INTEGER," + + "itemType INTEGER," + + "appWidgetId INTEGER NOT NULL DEFAULT -1," + + "iconType INTEGER," + + "iconPackage TEXT," + + "iconResource TEXT," + + "icon BLOB," + + "appWidgetProvider TEXT," + + "modified INTEGER NOT NULL DEFAULT 0," + + "restored INTEGER NOT NULL DEFAULT 0," + + "profileId INTEGER DEFAULT " + myProfileId + "," + + "rank INTEGER NOT NULL DEFAULT 0," + + "options INTEGER NOT NULL DEFAULT 0" + + ");"); + } } /** diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 1acbfc12b..c5f601dcd 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -46,7 +46,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; import android.os.Build; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.PowerManager; import android.text.Spannable; @@ -63,9 +62,11 @@ import android.widget.Toast; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.util.IconNormalizer; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -845,6 +846,18 @@ public final class Utilities { return true; } + public static void closeSilently(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { + Log.d(TAG, "Error closing", e); + } + } + } + } + /** * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. * This allows the badging to be done based on the action bitmap size rather than diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 5711a3db7..34c6663e9 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -18,27 +18,15 @@ package com.android.launcher3.config; /** * Defines a set of flags used to control various launcher behaviors - * All the flags must be defined as - * public static boolean LAUNCHER3_FLAG_NAME = true/false; - * - * Use LAUNCHER3_ prefix for prevent namespace conflicts. */ public final class FeatureFlags { private FeatureFlags() {} - public static boolean IS_DEV_BUILD = false; - public static boolean IS_ALPHA_BUILD = false; - public static boolean IS_RELEASE_BUILD = true; - // Custom flags go below this public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false; // As opposed to the new spring-loaded workspace. public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false; public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false; - public static boolean LAUNCHER3_LEGACY_LOGGING = false; public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = false; public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false; - - // This flags is only defined to resolve some build issues. - public static boolean LAUNCHER3_ICON_NORMALIZATION = false; } diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java index 825b43422..1d964b1b2 100644 --- a/src/com/android/launcher3/config/ProviderConfig.java +++ b/src/com/android/launcher3/config/ProviderConfig.java @@ -20,5 +20,5 @@ public class ProviderConfig { public static final String AUTHORITY = "com.android.launcher3.settings".intern(); - public static boolean IS_DOGFOOD_BUILD = false; + public static boolean IS_DOGFOOD_BUILD = true; } diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java index 95a62b957..89594f407 100644 --- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java +++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java @@ -32,6 +32,9 @@ import com.android.launcher3.LauncherSettings; */ public class ColorExtractionService extends IntentService { + /** The fraction of the wallpaper to extract colors for use on the hotseat. */ + private static final float HOTSEAT_FRACTION = 1f / 4; + public ColorExtractionService() { super("ColorExtractionService"); } @@ -44,10 +47,21 @@ public class ColorExtractionService extends IntentService { if (wallpaperManager.getWallpaperInfo() != null) { // We can't extract colors from live wallpapers, so just use the default color always. extractedColors.updatePalette(null); + extractedColors.updateHotseatPalette(null); } else { Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); Palette palette = Palette.from(wallpaper).generate(); extractedColors.updatePalette(palette); + // We extract colors for the hotseat separately, + // since it only considers the lower part of the wallpaper. + // TODO(twickham): update Palette library to 23.3.1 or higher, + // which fixes a bug with using regions (b/28349435). + Palette hotseatPalette = Palette.from(wallpaper) + .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)), + wallpaper.getWidth(), wallpaper.getHeight()) + .clearFilters() + .generate(); + extractedColors.updateHotseatPalette(hotseatPalette); } // Save the extracted colors and wallpaper id to LauncherProvider. diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java index 4d17ff764..e545288a0 100644 --- a/src/com/android/launcher3/dynamicui/ExtractedColors.java +++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java @@ -18,6 +18,7 @@ package com.android.launcher3.dynamicui; import android.content.Context; import android.graphics.Color; +import android.support.v4.graphics.ColorUtils; import android.support.v7.graphics.Palette; import android.util.Log; @@ -35,26 +36,30 @@ public class ExtractedColors { // These color profile indices should NOT be changed, since they are used when saving and // loading extracted colors. New colors should always be added at the end. - public static final int DEFAULT_INDEX = 0; - public static final int VIBRANT_INDEX = 1; - public static final int VIBRANT_DARK_INDEX = 2; - public static final int VIBRANT_LIGHT_INDEX = 3; - public static final int MUTED_INDEX = 4; - public static final int MUTED_DARK_INDEX = 5; - public static final int MUTED_LIGHT_INDEX = 6; - - public static final int NUM_COLOR_PROFILES = 7; + public static final int VERSION_INDEX = 0; + public static final int HOTSEAT_INDEX = 1; + // public static final int VIBRANT_INDEX = 2; + // public static final int VIBRANT_DARK_INDEX = 3; + // public static final int VIBRANT_LIGHT_INDEX = 4; + // public static final int MUTED_INDEX = 5; + // public static final int MUTED_DARK_INDEX = 6; + // public static final int MUTED_LIGHT_INDEX = 7; + + public static final int NUM_COLOR_PROFILES = 1; + private static final int VERSION = 1; private static final String COLOR_SEPARATOR = ","; private int[] mColors; public ExtractedColors() { - mColors = new int[NUM_COLOR_PROFILES]; + // The first entry is reserved for the version number. + mColors = new int[NUM_COLOR_PROFILES + 1]; + mColors[VERSION_INDEX] = VERSION; } public void setColorAtIndex(int index, int color) { - if (index >= 0 && index < mColors.length) { + if (index > VERSION_INDEX && index < mColors.length) { mColors[index] = color; } else { Log.e(TAG, "Attempted to set a color at an invalid index " + index); @@ -89,17 +94,21 @@ public class ExtractedColors { */ public void load(Context context) { String encodedString = Utilities.getPrefs(context).getString( - ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, DEFAULT_COLOR + ""); + ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, VERSION + ""); decodeFromString(encodedString); + + if (mColors[VERSION_INDEX] != VERSION) { + ExtractionUtils.startColorExtractionService(context); + } } /** @param index must be one of the index values defined at the top of this class. */ - public int getColor(int index) { - if (index >= 0 && index < mColors.length) { + public int getColor(int index, int defaultColor) { + if (index > VERSION_INDEX && index < mColors.length) { return mColors[index]; } - return DEFAULT_COLOR; + return defaultColor; } /** @@ -112,20 +121,39 @@ public class ExtractedColors { setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR); } } else { - setColorAtIndex(ExtractedColors.DEFAULT_INDEX, - ExtractedColors.DEFAULT_COLOR); - setColorAtIndex(ExtractedColors.VIBRANT_INDEX, - palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR)); - setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX, - palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK)); - setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX, - palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); - setColorAtIndex(ExtractedColors.MUTED_INDEX, - palette.getMutedColor(ExtractedColors.DEFAULT_COLOR)); - setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX, - palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK)); - setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX, - palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); + // We currently don't use any of the colors defined by the Palette API, + // but this is how we would add them if we ever need them. + + // setColorAtIndex(ExtractedColors.VIBRANT_INDEX, + // palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR)); + // setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX, + // palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK)); + // setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX, + // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); + // setColorAtIndex(ExtractedColors.MUTED_INDEX, + // palette.getMutedColor(DEFAULT_COLOR)); + // setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX, + // palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK)); + // setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX, + // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); + } + } + + /** + * The hotseat's color is defined as follows: + * - 12% black for super light wallpaper + * - 18% white for super dark + * - 25% white otherwise + */ + public void updateHotseatPalette(Palette hotseatPalette) { + int hotseatColor; + if (hotseatPalette != null && ExtractionUtils.isSuperLight(hotseatPalette)) { + hotseatColor = ColorUtils.setAlphaComponent(Color.BLACK, (int) (0.12f * 255)); + } else if (hotseatPalette != null && ExtractionUtils.isSuperDark(hotseatPalette)) { + hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.18f * 255)); + } else { + hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.25f * 255)); } + setColorAtIndex(HOTSEAT_INDEX, hotseatColor); } } diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java index 0b28ba6f9..6dc0035ee 100644 --- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java +++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java @@ -20,11 +20,15 @@ import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Color; +import android.support.v4.graphics.ColorUtils; +import android.support.v7.graphics.Palette; import com.android.launcher3.Utilities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.List; /** * Contains helper fields and methods related to extracting colors from the wallpaper. @@ -34,6 +38,7 @@ public class ExtractionUtils { public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId"; private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM + private static final float MIN_CONTRAST_RATIO = 2f; /** * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed. @@ -46,12 +51,17 @@ public class ExtractionUtils { @Override public void run() { if (hasWallpaperIdChanged(context)) { - context.startService(new Intent(context, ColorExtractionService.class)); + startColorExtractionService(context); } } }); } + /** Starts the {@link ColorExtractionService} without checking the wallpaper id */ + public static void startColorExtractionService(Context context) { + context.startService(new Intent(context, ColorExtractionService.class)); + } + private static boolean hasWallpaperIdChanged(Context context) { if (!Utilities.isNycOrAbove()) { // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here. @@ -72,4 +82,36 @@ public class ExtractionUtils { return -1; } } + + public static boolean isSuperLight(Palette p) { + return !isLegibleOnWallpaper(Color.WHITE, p.getSwatches()); + } + + public static boolean isSuperDark(Palette p) { + return !isLegibleOnWallpaper(Color.BLACK, p.getSwatches()); + } + + /** + * Given a color, returns true if that color is legible on + * the given wallpaper color swatches, else returns false. + */ + private static boolean isLegibleOnWallpaper(int color, List<Palette.Swatch> wallpaperSwatches) { + int legiblePopulation = 0; + int illegiblePopulation = 0; + for (Palette.Swatch swatch : wallpaperSwatches) { + if (isLegible(color, swatch.getRgb())) { + legiblePopulation += swatch.getPopulation(); + } else { + illegiblePopulation += swatch.getPopulation(); + } + } + return legiblePopulation > illegiblePopulation; + } + + /** @return Whether the foreground color is legible on the background color. */ + private static boolean isLegible(int foreground, int background) { + background = ColorUtils.setAlphaComponent(background, 255); + return ColorUtils.calculateContrast(foreground, background) >= MIN_CONTRAST_RATIO; + } + } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 7dc815529..1ebe8fdfb 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -330,8 +330,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } public void startEditingFolderName() { - mFolderName.setHint(""); - mIsEditingName = true; + post(new Runnable() { + @Override + public void run() { + mFolderName.setHint(""); + mIsEditingName = true; + } + }); } public void dismissEditingName() { diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java new file mode 100644 index 000000000..68d9b8c92 --- /dev/null +++ b/src/com/android/launcher3/logging/FileLog.java @@ -0,0 +1,216 @@ +package com.android.launcher3.logging; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; +import android.util.Pair; + +import com.android.launcher3.LauncherModel; +import com.android.launcher3.Utilities; +import com.android.launcher3.config.ProviderConfig; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Wrapper around {@link Log} to allow writing to a file. + * This class can safely be called from main thread. + * + * Note: This should only be used for logging errors which have a persistent effect on user's data, + * but whose effect may not be visible immediately. + */ +public final class FileLog { + + private static final String FILE_NAME_PREFIX = "log-"; + private static final DateFormat DATE_FORMAT = + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + + private static final long MAX_LOG_FILE_SIZE = 4 << 20; // 4 mb + + private static Handler sHandler = null; + private static File sLogsDirectory = null; + + public static void setDir(File logsDir) { + sLogsDirectory = logsDir; + } + + public static void d(String tag, String msg, Exception e) { + Log.d(tag, msg, e); + print(tag, msg, e); + } + + public static void d(String tag, String msg) { + Log.d(tag, msg); + print(tag, msg); + } + + public static void e(String tag, String msg, Exception e) { + Log.e(tag, msg, e); + print(tag, msg, e); + } + + public static void e(String tag, String msg) { + Log.e(tag, msg); + print(tag, msg); + } + + public static void print(String tag, String msg) { + print(tag, msg, null); + } + + public static void print(String tag, String msg, Exception e) { + if (!ProviderConfig.IS_DOGFOOD_BUILD) { + return; + } + String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg); + if (e != null) { + out += "\n" + Log.getStackTraceString(e); + } + Message.obtain(getHandler(), LogWriterCallback.MSG_WRITE, out).sendToTarget(); + } + + private static Handler getHandler() { + synchronized (DATE_FORMAT) { + if (sHandler == null) { + HandlerThread thread = new HandlerThread("file-logger"); + thread.start(); + sHandler = new Handler(thread.getLooper(), new LogWriterCallback()); + } + } + return sHandler; + } + + /** + * Blocks until all the pending logs are written to the disk + * @param out if not null, all the persisted logs are copied to the writer. + */ + public static void flushAll(PrintWriter out) throws InterruptedException { + if (!ProviderConfig.IS_DOGFOOD_BUILD) { + return; + } + CountDownLatch latch = new CountDownLatch(1); + Message.obtain(getHandler(), LogWriterCallback.MSG_FLUSH, + Pair.create(out, latch)).sendToTarget(); + + latch.await(2, TimeUnit.SECONDS); + } + + /** + * Writes logs to the file. + * Log files are named log-0 for even days of the year and log-1 for odd days of the year. + * Logs older than 36 hours are purged. + */ + private static class LogWriterCallback implements Handler.Callback { + + private static final long CLOSE_DELAY = 5000; // 5 seconds + + private static final int MSG_WRITE = 1; + private static final int MSG_CLOSE = 2; + private static final int MSG_FLUSH = 3; + + private String mCurrentFileName = null; + private PrintWriter mCurrentWriter = null; + + private void closeWriter() { + Utilities.closeSilently(mCurrentWriter); + mCurrentWriter = null; + } + + @Override + public boolean handleMessage(Message msg) { + if (sLogsDirectory == null || !ProviderConfig.IS_DOGFOOD_BUILD) { + return true; + } + switch (msg.what) { + case MSG_WRITE: { + Calendar cal = Calendar.getInstance(); + // suffix with 0 or 1 based on the day of the year. + String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) & 1); + + if (!fileName.equals(mCurrentFileName)) { + closeWriter(); + } + + try { + if (mCurrentWriter == null) { + mCurrentFileName = fileName; + + boolean append = false; + File logFile = new File(sLogsDirectory, fileName); + if (logFile.exists()) { + Calendar modifiedTime = Calendar.getInstance(); + modifiedTime.setTimeInMillis(logFile.lastModified()); + + // If the file was modified more that 36 hours ago, purge the file. + // We use instead of 24 to account for day-365 followed by day-1 + modifiedTime.add(Calendar.HOUR, 36); + append = cal.before(modifiedTime) + && logFile.length() < MAX_LOG_FILE_SIZE; + } + mCurrentWriter = new PrintWriter(new FileWriter(logFile, append)); + } + + mCurrentWriter.println((String) msg.obj); + mCurrentWriter.flush(); + + // Auto close file stream after some time. + sHandler.removeMessages(MSG_CLOSE); + sHandler.sendEmptyMessageDelayed(MSG_CLOSE, CLOSE_DELAY); + } catch (Exception e) { + Log.e("FileLog", "Error writing logs to file", e); + // Close stream, will try reopening during next log + closeWriter(); + } + return true; + } + case MSG_CLOSE: { + closeWriter(); + return true; + } + case MSG_FLUSH: { + closeWriter(); + Pair<PrintWriter, CountDownLatch> p = + (Pair<PrintWriter, CountDownLatch>) msg.obj; + + if (p.first != null) { + dumpFile(p.first, FILE_NAME_PREFIX + 0); + dumpFile(p.first, FILE_NAME_PREFIX + 1); + } + p.second.countDown(); + return true; + } + } + return true; + } + } + + private static void dumpFile(PrintWriter out, String fileName) { + File logFile = new File(sLogsDirectory, fileName); + if (logFile.exists()) { + + BufferedReader in = null; + try { + in = new BufferedReader(new FileReader(logFile)); + out.println(); + out.println("--- logfile: " + fileName + " ---"); + String line; + while ((line = in.readLine()) != null) { + out.println(line); + } + } catch (Exception e) { + // ignore + } finally { + Utilities.closeSilently(in); + } + } + } +} diff --git a/tests/src/com/android/launcher3/LauncherBackupAgentTest.java b/tests/src/com/android/launcher3/LauncherBackupAgentTest.java new file mode 100644 index 000000000..020a557c7 --- /dev/null +++ b/tests/src/com/android/launcher3/LauncherBackupAgentTest.java @@ -0,0 +1,74 @@ +package com.android.launcher3; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.launcher3.LauncherProvider.DatabaseHelper; +import com.android.launcher3.LauncherSettings.Favorites; + +/** + * Tests for {@link LauncherBackupAgent} + */ +@MediumTest +public class LauncherBackupAgentTest extends AndroidTestCase { + + public void testGetProfileId() throws Exception { + SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase(); + assertEquals(23, new LauncherBackupAgent().getDefaultProfileId(db)); + } + + public void testMigrateProfileId() throws Exception { + SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase(); + // Add some dummy data + for (int i = 0; i < 5; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.TITLE, "item " + i); + db.insert(Favorites.TABLE_NAME, null, values); + } + // Verify item add + assertEquals(5, getCount(db, "select * from favorites where profileId = 42")); + + new LauncherBackupAgent().migrateProfileId(db, 33); + + // verify data migrated + assertEquals(0, getCount(db, "select * from favorites where profileId = 42")); + assertEquals(5, getCount(db, "select * from favorites where profileId = 33")); + + // Verify default value changed + ContentValues values = new ContentValues(); + values.put(Favorites._ID, 100); + values.put(Favorites.TITLE, "item 100"); + db.insert(Favorites.TABLE_NAME, null, values); + assertEquals(6, getCount(db, "select * from favorites where profileId = 33")); + } + + private int getCount(SQLiteDatabase db, String sql) { + Cursor c = db.rawQuery(sql, null); + try { + return c.getCount(); + } finally { + c.getCount(); + } + } + + private class MyDatabaseHelper extends DatabaseHelper { + + private final long mProfileId; + + public MyDatabaseHelper(long profileId) { + super(getContext(), null, null); + mProfileId = profileId; + } + + @Override + public long getDefaultUserSerial() { + return mProfileId; + } + + protected void onEmptyDbCreated() { } + } +} diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/src/com/android/launcher3/logging/FileLogTest.java new file mode 100644 index 000000000..c24cc3fb7 --- /dev/null +++ b/tests/src/com/android/launcher3/logging/FileLogTest.java @@ -0,0 +1,77 @@ +package com.android.launcher3.logging; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Calendar; + +/** + * Tests for {@link FileLog} + */ +@SmallTest +public class FileLogTest extends AndroidTestCase { + + private File mTempDir; + + @Override + protected void setUp() throws Exception { + super.setUp(); + int count = 0; + do { + mTempDir = new File(getContext().getCacheDir(), "log-test-" + (count++)); + } while(!mTempDir.mkdir()); + + FileLog.setDir(mTempDir); + } + + @Override + protected void tearDown() throws Exception { + // Clear existing logs + new File(mTempDir, "log-0").delete(); + new File(mTempDir, "log-1").delete(); + mTempDir.delete(); + super.tearDown(); + } + + public void testPrintLog() throws Exception { + FileLog.print("Testing", "hoolalala"); + StringWriter writer = new StringWriter(); + FileLog.flushAll(new PrintWriter(writer)); + assertTrue(writer.toString().contains("hoolalala")); + + FileLog.print("Testing", "abracadabra", new Exception("cat! cat!")); + writer = new StringWriter(); + FileLog.flushAll(new PrintWriter(writer)); + assertTrue(writer.toString().contains("abracadabra")); + // Exception is also printed + assertTrue(writer.toString().contains("cat! cat!")); + + // Old logs still present after flush + assertTrue(writer.toString().contains("hoolalala")); + } + + public void testOldFileTruncated() throws Exception { + FileLog.print("Testing", "hoolalala"); + StringWriter writer = new StringWriter(); + FileLog.flushAll(new PrintWriter(writer)); + assertTrue(writer.toString().contains("hoolalala")); + + Calendar threeDaysAgo = Calendar.getInstance(); + threeDaysAgo.add(Calendar.HOUR, -72); + new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis()); + new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis()); + + FileLog.print("Testing", "abracadabra", new Exception("cat! cat!")); + writer = new StringWriter(); + FileLog.flushAll(new PrintWriter(writer)); + assertTrue(writer.toString().contains("abracadabra")); + // Exception is also printed + assertTrue(writer.toString().contains("cat! cat!")); + + // Old logs have been truncated + assertFalse(writer.toString().contains("hoolalala")); + } +} diff --git a/tests/src/com/android/launcher3/util/TestLauncherProvider.java b/tests/src/com/android/launcher3/util/TestLauncherProvider.java index a11013ffe..bd3e86c8a 100644 --- a/tests/src/com/android/launcher3/util/TestLauncherProvider.java +++ b/tests/src/com/android/launcher3/util/TestLauncherProvider.java @@ -1,6 +1,7 @@ package com.android.launcher3.util; import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; import com.android.launcher3.LauncherProvider; @@ -17,16 +18,21 @@ public class TestLauncherProvider extends LauncherProvider { @Override protected synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - mOpenHelper = new MyDatabaseHelper(getContext(), this); + mOpenHelper = new MyDatabaseHelper(getContext()); } } + public SQLiteOpenHelper getHelper() { + createDbIfNotExists(); + return mOpenHelper; + } + @Override protected void notifyListeners() { } private static class MyDatabaseHelper extends DatabaseHelper { - public MyDatabaseHelper(Context context, LauncherProvider provider) { - super(context, provider, null, null); + public MyDatabaseHelper(Context context) { + super(context, null, null); initIds(); } |