merge in ub-now-queens-release history after reset to 7e585dc71f9aec8a232a79105653f315af8007b2
diff --git a/protos/backup.proto b/protos/backup.proto
index 7ba2937..8ae1752 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -37,12 +37,36 @@
   required int64 checksum = 2;
 }
 
+message DeviceProfieData {
+  required float desktop_rows = 1;
+  required float desktop_cols = 2;
+  required float hotseat_count = 3;
+  required int32 allapps_rank = 4;
+}
+
 message Journal {
   required int32 app_version = 1;
+
+  // Time when the backup was created
   required int64 t = 2;
+
+  // Total bytes written during the last backup
+  // OBSOLETE: A state may contain entries which are already present in the backup
+  // and were not written in the last backup
   optional int64 bytes = 3;
+
+  // Total entries written during the last backup
+  // OBSOLETE: A state may contain entries which are already present in the backup
+  // and were not written in the last backup
   optional int32 rows = 4;
+
+  // Valid keys for this state
   repeated Key key = 5;
+
+  // Backup format version.
+  optional int32 backup_version = 6 [default = 1];
+
+  optional DeviceProfieData profile = 7;
 }
 
 message Favorite {
diff --git a/res/drawable-hdpi/ic_pageindicator_current.png b/res/drawable-hdpi/ic_pageindicator_current.png
index 2e841f5..283f44d 100644
--- a/res/drawable-hdpi/ic_pageindicator_current.png
+++ b/res/drawable-hdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default.png b/res/drawable-hdpi/ic_pageindicator_default.png
index 07ab948..e63ee50 100644
--- a/res/drawable-hdpi/ic_pageindicator_default.png
+++ b/res/drawable-hdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current.png b/res/drawable-mdpi/ic_pageindicator_current.png
index 08f43b4..b41e1bb 100644
--- a/res/drawable-mdpi/ic_pageindicator_current.png
+++ b/res/drawable-mdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default.png b/res/drawable-mdpi/ic_pageindicator_default.png
index 635be4a..7584a9e 100644
--- a/res/drawable-mdpi/ic_pageindicator_default.png
+++ b/res/drawable-mdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current.png b/res/drawable-xhdpi/ic_pageindicator_current.png
index 0e9a52f..8fa774d 100644
--- a/res/drawable-xhdpi/ic_pageindicator_current.png
+++ b/res/drawable-xhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default.png b/res/drawable-xhdpi/ic_pageindicator_default.png
index d0f14cd..f9c71b1 100644
--- a/res/drawable-xhdpi/ic_pageindicator_default.png
+++ b/res/drawable-xhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current.png b/res/drawable-xxhdpi/ic_pageindicator_current.png
index b74e92e..22b290e 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_current.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default.png b/res/drawable-xxhdpi/ic_pageindicator_default.png
index e362ece..b9b5066 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_default.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/layout-land/longpress_cling.xml b/res/layout-land/longpress_cling.xml
index 93bbc07..9672dd8 100644
--- a/res/layout-land/longpress_cling.xml
+++ b/res/layout-land/longpress_cling.xml
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/longpress_cling"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    launcher:layout_ignoreInsets="true"
     android:background="@color/cling_scrim_background"
     android:orientation="vertical" >
 
diff --git a/res/layout-land/migration_cling.xml b/res/layout-land/migration_cling.xml
index 307cba8..db93da8 100644
--- a/res/layout-land/migration_cling.xml
+++ b/res/layout-land/migration_cling.xml
@@ -15,9 +15,11 @@
      limitations under the License.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/migration_cling"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    launcher:layout_ignoreInsets="true"
     android:background="#FF009688"
     android:baselineAligned="false"
     android:gravity="center_vertical" >
diff --git a/res/layout-port/longpress_cling.xml b/res/layout-port/longpress_cling.xml
index 8e35f5c..c0b5267 100644
--- a/res/layout-port/longpress_cling.xml
+++ b/res/layout-port/longpress_cling.xml
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/longpress_cling"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    launcher:layout_ignoreInsets="true"
     android:background="@color/cling_scrim_background" >
 
     <FrameLayout
diff --git a/res/layout-port/migration_cling.xml b/res/layout-port/migration_cling.xml
index dde8dbc..81689d3 100644
--- a/res/layout-port/migration_cling.xml
+++ b/res/layout-port/migration_cling.xml
@@ -15,9 +15,11 @@
      limitations under the License.
 -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/migration_cling"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    launcher:layout_ignoreInsets="true"
     android:background="#FF009688" >
 
     <RelativeLayout
diff --git a/res/layout-sw600dp-port/longpress_cling.xml b/res/layout-sw600dp-port/longpress_cling.xml
index b42d697..c4573d5 100644
--- a/res/layout-sw600dp-port/longpress_cling.xml
+++ b/res/layout-sw600dp-port/longpress_cling.xml
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/longpress_cling"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    launcher:layout_ignoreInsets="true"
     android:background="@color/cling_scrim_background"
     android:orientation="vertical" >
 
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 4e5303a..31e9da0 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -45,6 +45,7 @@
         android:hint="@string/folder_hint_text"
         android:textSize="14sp"
         android:textColor="#ff777777"
+        android:textColorHint="#ff808080"
         android:textColorHighlight="#ffCCCCCC"
         android:textCursorDrawable="@null"
         android:gravity="center_horizontal"
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 7f3b7fb..0648858 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -1476,7 +1476,9 @@
         }
     }
 
+    @Override
     public void reset() {
+        super.reset();
         // If we have reset, then we should not continue to restore the previous state
         mSaveInstanceStateItemIndex = -1;
 
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
index 9a516fd..df65cba 100644
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -141,14 +141,6 @@
             mPagedView.loadAssociatedPages(mPagedView.getCurrentPage());
         }
     }
-
-    public void onTrimMemory() {
-        mContent.setVisibility(GONE);
-        // Clear the widget pages of all their subviews - this will trigger the widget previews
-        // to delete their bitmaps
-        mPagedView.clearAllWidgetPages();
-    }
-
     @Override
     public ViewGroup getContent() {
         return mPagedView;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b357e7a..a2de314 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -50,10 +50,10 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -71,7 +71,6 @@
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
@@ -418,22 +417,9 @@
         LauncherAppState.setApplicationContext(getApplicationContext());
         LauncherAppState app = LauncherAppState.getInstance();
         LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
-        // Determine the dynamic grid properties
-        Point smallestSize = new Point();
-        Point largestSize = new Point();
-        Point realSize = new Point();
-        Display display = getWindowManager().getDefaultDisplay();
-        display.getCurrentSizeRange(smallestSize, largestSize);
-        display.getRealSize(realSize);
-        DisplayMetrics dm = new DisplayMetrics();
-        display.getMetrics(dm);
 
         // Lazy-initialize the dynamic grid
-        DeviceProfile grid = app.initDynamicGrid(this,
-                Math.min(smallestSize.x, smallestSize.y),
-                Math.min(largestSize.x, largestSize.y),
-                realSize.x, realSize.y,
-                dm.widthPixels, dm.heightPixels);
+        DeviceProfile grid = app.initDynamicGrid(this);
 
         // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
         mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
@@ -503,13 +489,6 @@
         // On large interfaces, we want the screen to auto-rotate based on the current orientation
         unlockScreenOrientation(true);
 
-        if (shouldShowIntroScreen()) {
-            showIntroScreen();
-        } else {
-            showFirstRunActivity();
-            showFirstRunClings();
-        }
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
             if (mLauncherCallbacks.hasLauncherOverlay()) {
@@ -520,6 +499,13 @@
                 mWorkspace.setLauncherOverlay(mLauncherOverlay);
             }
         }
+
+        if (shouldShowIntroScreen()) {
+            showIntroScreen();
+        } else {
+            showFirstRunActivity();
+            showFirstRunClings();
+        }
     }
 
     private LauncherCallbacks mLauncherCallbacks;
@@ -3583,6 +3569,9 @@
                     if (mSearchDropTargetBar != null) {
                         mSearchDropTargetBar.hideSearchBar(false);
                     }
+
+                    // This can hold unnecessary references to views.
+                    mStateAnimation = null;
                 }
 
             });
@@ -3868,6 +3857,9 @@
                     content.setCurrentPage(content.getNextPage());
 
                     mAppsCustomizeContent.updateCurrentPageScroll();
+
+                    // This can hold unnecessary references to views.
+                    mStateAnimation = null;
                 }
             });
 
@@ -3911,8 +3903,16 @@
     @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
-        if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
-            mAppsCustomizeTabHost.onTrimMemory();
+        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
+            // The widget preview db can result in holding onto over
+            // 3MB of memory for caching which isn't necessary.
+            SQLiteDatabase.releaseMemory();
+
+            // We reset the apps customize tray in order to
+            // to free all the memory associated with widget previews
+            if (mAppsCustomizeTabHost != null) {
+                mAppsCustomizeTabHost.reset();
+            }
         }
     }
 
@@ -5260,6 +5260,9 @@
         if (introScreen != null) {
             mDragLayer.showOverlayView(introScreen);
         }
+        if (mLauncherOverlayContainer != null) {
+            mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
+        }
     }
 
     public void dismissIntroScreen() {
@@ -5271,11 +5274,17 @@
                 @Override
                 public void run() {
                     mDragLayer.dismissOverlayView();
+                    if (mLauncherOverlayContainer != null) {
+                        mLauncherOverlayContainer.setVisibility(View.VISIBLE);
+                    }
                     showFirstRunClings();
                 }
             }, ACTIVITY_START_DELAY);
         } else {
             mDragLayer.dismissOverlayView();
+            if (mLauncherOverlayContainer != null) {
+                mLauncherOverlayContainer.setVisibility(View.VISIBLE);
+            }
             showFirstRunClings();
         }
         changeWallpaperVisiblity(true);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 03ab94b..48af216 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -25,8 +25,12 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Point;
 import android.os.Handler;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
@@ -190,21 +194,35 @@
         return LauncherFiles.SHARED_PREFERENCES_KEY;
     }
 
-    DeviceProfile initDynamicGrid(Context context, int minWidth, int minHeight,
-                                  int width, int height,
-                                  int availableWidth, int availableHeight) {
+    DeviceProfile initDynamicGrid(Context context) {
+        // Determine the dynamic grid properties
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+
+        Point realSize = new Point();
+        display.getRealSize(realSize);
+        DisplayMetrics dm = new DisplayMetrics();
+        display.getMetrics(dm);
+
         if (mDynamicGrid == null) {
+            Point smallestSize = new Point();
+            Point largestSize = new Point();
+            display.getCurrentSizeRange(smallestSize, largestSize);
+
             mDynamicGrid = new DynamicGrid(context,
                     context.getResources(),
-                    minWidth, minHeight, width, height,
-                    availableWidth, availableHeight);
+                    Math.min(smallestSize.x, smallestSize.y),
+                    Math.min(largestSize.x, largestSize.y),
+                    realSize.x, realSize.y,
+                    dm.widthPixels, dm.heightPixels);
             mDynamicGrid.getDeviceProfile().addCallback(this);
         }
 
         // Update the icon size
         DeviceProfile grid = mDynamicGrid.getDeviceProfile();
-        grid.updateFromConfiguration(context, context.getResources(), width, height,
-                availableWidth, availableHeight);
+        grid.updateFromConfiguration(context, context.getResources(),
+                realSize.x, realSize.y,
+                dm.widthPixels, dm.heightPixels);
         return grid;
     }
     public DynamicGrid getDynamicGrid() {
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index 09ad22b..b03b13c 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.os.ParcelFileDescriptor;
-import android.provider.Settings;
 import android.util.Log;
 
 import java.io.IOException;
@@ -30,13 +29,14 @@
 public class LauncherBackupAgentHelper extends BackupAgentHelper {
 
     private static final String TAG = "LauncherBackupAgentHelper";
+
+    private static final String LAUNCHER_DATA_PREFIX = "L";
+
     static final boolean VERBOSE = true;
     static final boolean DEBUG = false;
 
     private static BackupManager sBackupManager;
 
-    protected static final String SETTING_RESTORE_ENABLED = "launcher_restore_enabled";
-
     /**
      * Notify the backup manager that out database is dirty.
      *
@@ -51,28 +51,13 @@
         sBackupManager.dataChanged();
     }
 
-    @Override
-    public void onDestroy() {
-        // There is only one process accessing this preference file, but the restore
-        // modifies the file outside the normal codepaths, so it looks like another
-        // process.  This forces a reload of the file, in case this process persists.
-        String spKey = LauncherAppState.getSharedPreferencesKey();
-        getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
-        super.onDestroy();
-    }
+    private LauncherBackupHelper mHelper;
 
     @Override
     public void onCreate() {
-        boolean restoreEnabled = 0 != Settings.Secure.getInt(
-                getContentResolver(), SETTING_RESTORE_ENABLED, 1);
-        if (VERBOSE) Log.v(TAG, "restore is " + (restoreEnabled ? "enabled" : "disabled"));
-
-        addHelper(LauncherBackupHelper.LAUNCHER_PREFS_PREFIX,
-                new LauncherPreferencesBackupHelper(this,
-                        LauncherAppState.getSharedPreferencesKey(),
-                        restoreEnabled));
-        addHelper(LauncherBackupHelper.LAUNCHER_PREFIX,
-                new LauncherBackupHelper(this, restoreEnabled));
+        super.onCreate();
+        mHelper = new LauncherBackupHelper(this);
+        addHelper(LAUNCHER_DATA_PREFIX, mHelper);
     }
 
     @Override
@@ -92,7 +77,10 @@
         boolean hasData = c.moveToNext();
         c.close();
 
-        if (!hasData) {
+        if (hasData && mHelper.restoreSuccessful) {
+            LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
+            LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
+        } else {
             if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
             LauncherAppState.getLauncherProvider().createEmptyDB();
         }
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index b2ac577..c260fbc 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.LauncherSettings.WorkspaceScreens;
 import com.android.launcher3.backup.BackupProtos;
 import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.DeviceProfieData;
 import com.android.launcher3.backup.BackupProtos.Favorite;
 import com.android.launcher3.backup.BackupProtos.Journal;
 import com.android.launcher3.backup.BackupProtos.Key;
@@ -66,24 +67,24 @@
  * Persist the launcher home state across calamities.
  */
 public class LauncherBackupHelper implements BackupHelper {
-
     private static final String TAG = "LauncherBackupHelper";
     private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
     private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
 
+    private static final int BACKUP_VERSION = 2;
     private static final int MAX_JOURNAL_SIZE = 1000000;
 
+    // Journal key is such that it is always smaller than any dynamically generated
+    // key (any Base64 encoded string).
+    private static final String JOURNAL_KEY = "#";
+
     /** icons are large, dribble them out */
     private static final int MAX_ICONS_PER_PASS = 10;
 
     /** widgets contain previews, which are very large, dribble them out */
     private static final int MAX_WIDGETS_PER_PASS = 5;
 
-    public static final int IMAGE_COMPRESSION_QUALITY = 75;
-
-    public static final String LAUNCHER_PREFIX = "L";
-
-    public static final String LAUNCHER_PREFS_PREFIX = "LP";
+    private static final int IMAGE_COMPRESSION_QUALITY = 75;
 
     private static final Bitmap.CompressFormat IMAGE_FORMAT =
             android.graphics.Bitmap.CompressFormat.PNG;
@@ -145,10 +146,14 @@
     private byte[] mBuffer = new byte[512];
     private long mLastBackupRestoreTime;
 
-    public LauncherBackupHelper(Context context, boolean restoreEnabled) {
+    private DeviceProfieData mCurrentProfile;
+    boolean restoreSuccessful;
+
+    public LauncherBackupHelper(Context context) {
         mContext = context;
         mExistingKeys = new HashSet<String>();
         mKeys = new ArrayList<Key>();
+        restoreSuccessful = true;
     }
 
     private void dataChanged() {
@@ -178,7 +183,6 @@
      * @param oldState notes from the last backup
      * @param data incremental key/value pairs to persist off-device
      * @param newState notes for the next backup
-     * @throws IOException
      */
     @Override
     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
@@ -220,6 +224,12 @@
 
             mExistingKeys.clear();
             mLastBackupRestoreTime = newBackupTime;
+
+            // We store the journal at two places.
+            //   1) Storing it in newState allows us to do partial backups by comparing old state
+            //   2) Storing it in backup data allows us to validate keys during restore
+            Journal state = getCurrentStateJournal();
+            writeRowToBackup(JOURNAL_KEY, state, data);
         } catch (IOException e) {
             Log.e(TAG, "launcher backup has failed", e);
         }
@@ -228,14 +238,45 @@
     }
 
     /**
+     * @return true if the backup corresponding to oldstate can be successfully applied
+     * to this device.
+     */
+    private boolean isBackupCompatible(Journal oldState) {
+        DeviceProfieData currentProfile = getDeviceProfieData();
+
+        DeviceProfieData oldProfile = oldState.profile;
+
+        if (oldProfile == null || oldProfile.desktopCols == 0) {
+            // Profile info is not valid, ignore the check.
+            return true;
+        }
+
+        boolean isHotsetCompatible = false;
+        if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
+            isHotsetCompatible = true;
+        }
+        if ((currentProfile.hotseatCount >= oldProfile.hotseatCount) &&
+                (currentProfile.allappsRank == oldProfile.allappsRank)) {
+            isHotsetCompatible = true;
+        }
+
+        return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
+                && (currentProfile.desktopRows >= oldProfile.desktopRows);
+    }
+
+    /**
      * Restore launcher configuration from the restored data stream.
-     *
-     * <P>Keys may arrive in any order.
+     * It assumes that the keys will arrive in lexical order. So if the journal was present in the
+     * backup, it should arrive first.
      *
      * @param data the key/value pair from the server
      */
     @Override
     public void restoreEntity(BackupDataInputStream data) {
+        if (!restoreSuccessful) {
+            return;
+        }
+
         int dataSize = data.size();
         if (mBuffer.length < dataSize) {
             mBuffer = new byte[dataSize];
@@ -244,6 +285,27 @@
             int bytesRead = data.read(mBuffer, 0, dataSize);
             if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
             String backupKey = data.getKey();
+
+            if (JOURNAL_KEY.equals(backupKey)) {
+                if (VERBOSE) Log.v(TAG, "Journal entry restored");
+                if (!mKeys.isEmpty()) {
+                    // We received the journal key after a restore key.
+                    Log.wtf(TAG, keyToBackupKey(mKeys.get(0)) + " received after " + JOURNAL_KEY);
+                    restoreSuccessful = false;
+                    return;
+                }
+
+                Journal journal = new Journal();
+                MessageNano.mergeFrom(journal, readCheckedBytes(mBuffer, dataSize));
+                applyJournal(journal);
+                restoreSuccessful = isBackupCompatible(journal);
+                return;
+            }
+
+            if (!mExistingKeys.isEmpty() && !mExistingKeys.contains(backupKey)) {
+                if (DEBUG) Log.e(TAG, "Ignoring key not present in the backup state " + backupKey);
+                return;
+            }
             Key key = backupKeyToKey(backupKey);
             mKeys.add(key);
             switch (key.type) {
@@ -288,6 +350,8 @@
         journal.t = mLastBackupRestoreTime;
         journal.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
         journal.appVersion = getAppVersion();
+        journal.backupVersion = BACKUP_VERSION;
+        journal.profile = getDeviceProfieData();
         return journal;
     }
 
@@ -301,6 +365,32 @@
     }
 
     /**
+     * @return the current device profile information.
+     */
+    private DeviceProfieData getDeviceProfieData() {
+        if (mCurrentProfile != null) {
+            return mCurrentProfile;
+        }
+        LauncherAppState.setApplicationContext(mContext.getApplicationContext());
+        LauncherAppState app = LauncherAppState.getInstance();
+
+        DeviceProfile profile;
+        if (app.getDynamicGrid() == null) {
+            // Initialize the grid
+            profile = app.initDynamicGrid(mContext);
+        } else {
+            profile = app.getDynamicGrid().getDeviceProfile();
+        }
+
+        mCurrentProfile = new DeviceProfieData();
+        mCurrentProfile.desktopRows = profile.numRows;
+        mCurrentProfile.desktopCols = profile.numColumns;
+        mCurrentProfile.hotseatCount = profile.numHotseatIcons;
+        mCurrentProfile.allappsRank = profile.hotseatAllAppsRank;
+        return mCurrentProfile;
+    }
+
+    /**
      * Write all modified favorites to the data stream.
      *
      * @param data output stream for key/value pairs
@@ -628,18 +718,18 @@
     }
 
     /** keys need to be strings, decode and parse. */
-    private Key backupKeyToKey(String backupKey) throws KeyParsingException {
+    private Key backupKeyToKey(String backupKey) throws InvalidBackupException {
         try {
             Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
             if (key.checksum != checkKey(key)) {
                 key = null;
-                throw new KeyParsingException("invalid key read from stream" + backupKey);
+                throw new InvalidBackupException("invalid key read from stream" + backupKey);
             }
             return key;
         } catch (InvalidProtocolBufferNanoException e) {
-            throw new KeyParsingException(e);
+            throw new InvalidBackupException(e);
         } catch (IllegalArgumentException e) {
-            throw new KeyParsingException(e);
+            throw new InvalidBackupException(e);
         }
     }
 
@@ -710,7 +800,7 @@
 
     /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
     private ContentValues unpackFavorite(byte[] buffer, int dataSize)
-            throws InvalidProtocolBufferNanoException {
+            throws IOException {
         Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
         ContentValues values = new ContentValues();
         values.put(Favorites._ID, favorite.id);
@@ -743,6 +833,8 @@
                 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
         values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
 
+        DeviceProfieData currentProfile = getDeviceProfieData();
+
         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
             if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
                 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
@@ -752,9 +844,31 @@
                     LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
                     LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
                     LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
+
+            // Verify placement
+            if (((favorite.cellX + favorite.spanX) > currentProfile.desktopCols)
+                    || ((favorite.cellY + favorite.spanY) > currentProfile.desktopRows)) {
+                restoreSuccessful = false;
+                throw new InvalidBackupException("Widget not in screen bounds, aborting restore");
+            }
         } else {
             // Let LauncherModel know we've been here.
             values.put(LauncherSettings.Favorites.RESTORED, 1);
+
+            // Verify placement
+            if (favorite.container == Favorites.CONTAINER_HOTSEAT) {
+                if ((favorite.screen >= currentProfile.hotseatCount)
+                        || (favorite.screen == currentProfile.allappsRank)) {
+                    restoreSuccessful = false;
+                    throw new InvalidBackupException("Item not in hotseat bounds, aborting restore");
+                }
+            } else {
+                if ((favorite.cellX >= currentProfile.desktopCols)
+                        || (favorite.cellY >= currentProfile.desktopRows)) {
+                    restoreSuccessful = false;
+                    throw new InvalidBackupException("Item not in desktop bounds, aborting restore");
+                }
+            }
         }
 
         return values;
@@ -902,7 +1016,6 @@
         return journal;
     }
 
-
     private void writeRowToBackup(Key key, MessageNano proto, BackupDataOutput data)
             throws IOException {
         writeRowToBackup(keyToBackupKey(key), proto, data);
@@ -1017,12 +1130,12 @@
                 .getSerialNumberForUser(UserHandleCompat.myUserHandle());
     }
 
-    private class KeyParsingException extends IOException {
-        private KeyParsingException(Throwable cause) {
+    private class InvalidBackupException extends IOException {
+        private InvalidBackupException(Throwable cause) {
             super(cause);
         }
 
-        public KeyParsingException(String reason) {
+        public InvalidBackupException(String reason) {
             super(reason);
         }
     }
diff --git a/src/com/android/launcher3/LauncherPreferencesBackupHelper.java b/src/com/android/launcher3/LauncherPreferencesBackupHelper.java
deleted file mode 100644
index 6f9c05c..0000000
--- a/src/com/android/launcher3/LauncherPreferencesBackupHelper.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.app.backup.BackupDataInputStream;
-import android.app.backup.SharedPreferencesBackupHelper;
-import android.content.Context;
-import android.util.Log;
-
-public class LauncherPreferencesBackupHelper extends SharedPreferencesBackupHelper {
-
-    private static final String TAG = "LauncherPreferencesBackupHelper";
-    private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
-
-    private final boolean mRestoreEnabled;
-
-    public LauncherPreferencesBackupHelper(Context context,  String sharedPreferencesKey,
-            boolean restoreEnabled) {
-        super(context, sharedPreferencesKey);
-        mRestoreEnabled = restoreEnabled;
-    }
-
-    @Override
-    public void restoreEntity(BackupDataInputStream data) {
-        if (mRestoreEnabled) {
-            if (VERBOSE) Log.v(TAG, "restoring preferences");
-            super.restoreEntity(data);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fe9bc17..365d989 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -293,6 +293,14 @@
         mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
     }
 
+    public void clearFlagEmptyDbCreated() {
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE)
+            .edit()
+            .remove(EMPTY_DATABASE_CREATED)
+            .commit();
+    }
+
     /**
      * Loads the default workspace based on the following priority scheme:
      *   1) From a package provided by play store
@@ -334,7 +342,7 @@
                 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                         getDefaultLayoutParser());
             }
-            sp.edit().remove(EMPTY_DATABASE_CREATED).commit();
+            clearFlagEmptyDbCreated();
         }
     }
 
diff --git a/src/com/android/launcher3/PagedViewWithDraggableItems.java b/src/com/android/launcher3/PagedViewWithDraggableItems.java
index 2a29c33..7f4a3a0 100644
--- a/src/com/android/launcher3/PagedViewWithDraggableItems.java
+++ b/src/com/android/launcher3/PagedViewWithDraggableItems.java
@@ -91,6 +91,10 @@
         return super.onTouchEvent(ev);
     }
 
+    public void reset() {
+        mLastTouchedItem = null;
+    }
+
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         mLastTouchedItem = v;
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index a84bf02..601f04c 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Handler;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -29,15 +30,18 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 
-public class PackageInstallerCompatVL extends PackageInstallerCompat {
+public class PackageInstallerCompatVL extends PackageInstallerCompat implements Runnable {
 
     private static final String TAG = "PackageInstallerCompatVL";
     private static final boolean DEBUG = false;
 
+    // All updates to these sets must happen on the {@link #mWorker} thread.
     private final SparseArray<SessionInfo> mPendingReplays = new SparseArray<SessionInfo>();
     private final HashSet<String> mPendingBadgeUpdates = new HashSet<String>();
+
     private final PackageInstaller mInstaller;
     private final IconCache mCache;
+    private final Handler mWorker;
 
     private boolean mResumed;
     private boolean mBound;
@@ -46,16 +50,23 @@
         mInstaller = context.getPackageManager().getPackageInstaller();
         LauncherAppState.setApplicationContext(context.getApplicationContext());
         mCache = LauncherAppState.getInstance().getIconCache();
+        mWorker = new Handler();
 
         mResumed = false;
         mBound = false;
 
-        mInstaller.registerSessionCallback(mCallback);
+        mInstaller.registerSessionCallback(mCallback, mWorker);
 
         // On start, send updates for all active sessions
-        for (SessionInfo info : mInstaller.getAllSessions()) {
-            mPendingReplays.append(info.getSessionId(), info);
-        }
+        mWorker.post(new Runnable() {
+
+            @Override
+            public void run() {
+                for (SessionInfo info : mInstaller.getAllSessions()) {
+                    mPendingReplays.append(info.getSessionId(), info);
+                }
+            }
+        });
     }
 
     @Override
@@ -87,7 +98,7 @@
     @Override
     public void onFinishBind() {
         mBound = true;
-        replayUpdates(null);
+        mWorker.post(this);
     }
 
     @Override
@@ -98,7 +109,7 @@
     @Override
     public void onResume() {
         mResumed = true;
-        replayUpdates(null);
+        mWorker.post(this);
     }
 
     @Override
@@ -106,6 +117,12 @@
         // No op
     }
 
+    @Override
+    public void run() {
+        // Called on mWorker thread.
+        replayUpdates(null);
+    }
+
     private void replayUpdates(PackageInstallInfo newInfo) {
         if (DEBUG) Log.d(TAG, "updates resumed");
         if (!mResumed || !mBound) {