Merge "Removing old new-app animation code, and refactoring InstallShortcutReceiver to use new add/bind logic." into jb-ub-gel-agar
diff --git a/res/drawable-hdpi/now_page.png b/res/drawable-hdpi/now_page.png
new file mode 100644
index 0000000..9eef50c
--- /dev/null
+++ b/res/drawable-hdpi/now_page.png
Binary files differ
diff --git a/res/drawable-mdpi/now_page.png b/res/drawable-mdpi/now_page.png
new file mode 100644
index 0000000..cc4005d
--- /dev/null
+++ b/res/drawable-mdpi/now_page.png
Binary files differ
diff --git a/res/drawable-xhdpi/now_page.png b/res/drawable-xhdpi/now_page.png
new file mode 100644
index 0000000..e1da91c
--- /dev/null
+++ b/res/drawable-xhdpi/now_page.png
Binary files differ
diff --git a/res/layout/now_page_indicator_marker.xml b/res/layout/now_page_indicator_marker.xml
new file mode 100644
index 0000000..7d05627
--- /dev/null
+++ b/res/layout/now_page_indicator_marker.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<com.android.launcher3.PageIndicatorMarker
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:id="@+id/inactive"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/now_page"
+        />
+    <ImageView
+        android:id="@+id/active"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/now_page"
+        android:alpha="0"
+        />
+</com.android.launcher3.PageIndicatorMarker>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index ac41a2b..86bc1b0 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -3231,6 +3231,11 @@
         public boolean isLockedToGrid = true;
 
         /**
+         * Indicates that this item should use the full extents of its parent.
+         */
+        public boolean isFullscreen = false;
+
+        /**
          * Indicates whether this item can be reordered. Always true except in the case of the
          * the AllApps button.
          */
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 763ec3f..c98f761 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -55,6 +55,8 @@
     private TransitionDrawable mRemoveDrawable;
     private TransitionDrawable mCurrentDrawable;
 
+    private boolean mWaitingForUninstall = false;
+
     public DeleteDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -251,7 +253,6 @@
          return AppsCustomizePagedView.DISABLE_ALL_APPS && isWorkspaceOrFolderApplication(d);
     }
 
-    private boolean mWaitingForUninstall = false;
     private void completeDrop(final DragObject d) {
         ItemInfo item = (ItemInfo) d.dragInfo;
         boolean wasWaitingForUninstall = mWaitingForUninstall;
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 3c064ba..514dca2 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -327,84 +327,15 @@
      */
     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
             boolean includeRootScroll) {
-        return DragLayer.getDescendantCoordRelativeToParent(descendant, this,
+        return Utilities.getDescendantCoordRelativeToParent(descendant, this,
                 coord, includeRootScroll);
     }
 
-    public static float getDescendantCoordRelativeToParent(View descendant, View root,
-            int[] coord, boolean includeRootScroll) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
-        View v = descendant;
-        while(v != root && v != null) {
-            ancestorChain.add(v);
-            v = (View) v.getParent();
-        }
-        ancestorChain.add(root);
-
-        float scale = 1.0f;
-        int count = ancestorChain.size();
-        for (int i = 0; i < count; i++) {
-            View v0 = ancestorChain.get(i);
-            View v1 = i < count -1 ? ancestorChain.get(i + 1) : null;
-
-            // For TextViews, scroll has a meaning which relates to the text position
-            // which is very strange... ignore the scroll.
-            if (v0 != descendant || includeRootScroll) {
-                pt[0] -= v0.getScrollX();
-                pt[1] -= v0.getScrollY();
-            }
-
-            v0.getMatrix().mapPoints(pt);
-            pt[0] += v0.getLeft();
-            pt[1] += v0.getTop();
-            scale *= v0.getScaleX();
-        }
-
-        coord[0] = (int) Math.round(pt[0]);
-        coord[1] = (int) Math.round(pt[1]);
-        return scale;
-    }
-
     /**
      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
      */
     public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
-        View v = descendant;
-        while(v != this) {
-            ancestorChain.add(v);
-            v = (View) v.getParent();
-        }
-        ancestorChain.add(this);
-
-        float scale = 1.0f;
-        Matrix inverse = new Matrix();
-        int count = ancestorChain.size();
-        for (int i = count - 1; i >= 0; i--) {
-            View ancestor = ancestorChain.get(i);
-            View next = i > 0 ? ancestorChain.get(i-1) : null;
-
-            pt[0] += ancestor.getScrollX();
-            pt[1] += ancestor.getScrollY();
-
-            if (next != null) {
-                pt[0] -= next.getLeft();
-                pt[1] -= next.getTop();
-                next.getMatrix().invert(inverse);
-                inverse.mapPoints(pt);
-                scale *= next.getScaleX();
-            }
-        }
-
-        coord[0] = (int) Math.round(pt[0]);
-        coord[1] = (int) Math.round(pt[1]);
-        return scale;
+        return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
     }
 
     public void getViewRectRelativeToSelf(View v, Rect r) {
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index a591433..1acaf3e 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -21,6 +21,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.widget.ScrollView;
 import android.widget.TabHost;
 import android.widget.TabWidget;
 
@@ -817,7 +818,8 @@
     static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
         final CellLayout layout = (CellLayout) parent.getParent();
-        final Folder folder = (Folder) layout.getParent();
+        final ScrollView scrollView = (ScrollView) layout.getParent();
+        final Folder folder = (Folder) scrollView.getParent();
         View title = folder.mFolderName;
 
         final int action = e.getAction();
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index bb3993e..c70cbe0 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -775,7 +775,7 @@
         if (target != this) {
             if (mOnExitAlarm.alarmPending()) {
                 mOnExitAlarm.cancelAlarm();
-                if (successfulDrop) {
+                if (!successfulDrop) {
                     mSuppressFolderDeletion = true;
                 }
                 completeDragExit();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c32de33..f990d25 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -442,6 +442,11 @@
         sPausedFromUserAction = true;
     }
 
+    /** To be overriden by subclasses to hint to Launcher that we have custom content */
+    protected boolean hasCustomContentToLeft() {
+        return false;
+    }
+
     private void updateGlobalIcons() {
         boolean searchVisible = false;
         boolean voiceVisible = false;
@@ -795,6 +800,12 @@
                     (System.currentTimeMillis() - startTimeCallbacks));
             }
         }
+        if (mOnResumeCallbacks.size() > 0) {
+            for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
+                mOnResumeCallbacks.get(i).run();
+            }
+            mOnResumeCallbacks.clear();
+        }
 
         // Reset the pressed state of icons that were locked in the press state while activities
         // were launching
@@ -869,13 +880,13 @@
     }
 
     // Add a fullscreen unpadded view to the workspace to the left all other screens.
-    public QSBScroller addCustomContentToLeft(View customContent) {
-        return addCustomContentToLeft(customContent, null);
+    public QSBScroller addToCustomContentPage(View customContent) {
+        return addToCustomContentPage(customContent, null);
     }
 
-    public QSBScroller addCustomContentToLeft(View customContent,
+    public QSBScroller addToCustomContentPage(View customContent,
             CustomContentCallbacks callbacks) {
-        mWorkspace.addCustomContentToLeft(customContent, callbacks);
+        mWorkspace.addToCustomContentPage(customContent, callbacks);
         return mQsbScroller;
     }
 
@@ -991,7 +1002,7 @@
 
         int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
         if (currentScreen > -1) {
-            mWorkspace.setCurrentPage(currentScreen);
+            mWorkspace.setRestorePage(currentScreen);
         }
 
         final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
@@ -3434,11 +3445,11 @@
     }
 
     public void addOnResumeCallback(Runnable run) {
-        mBindOnResumeCallbacks.add(run);
+        mOnResumeCallbacks.add(run);
     }
 
     public void removeOnResumeCallback(Runnable run) {
-        mBindOnResumeCallbacks.remove(run);
+        mOnResumeCallbacks.remove(run);
     }
 
     /**
@@ -3603,6 +3614,8 @@
                     workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
                             item.cellY, 1, 1);
                     break;
+                default:
+                    throw new RuntimeException("Invalid Item Type");
             }
         }
 
@@ -3710,6 +3723,11 @@
             mSavedState = null;
         }
 
+        // Create the custom content page here before onLayout to prevent flashing
+        if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
+            mWorkspace.createCustomContentPage();
+        }
+
         mWorkspace.restoreInstanceStateForRemainingPages();
 
         // If we received the result of any pending adds while the loader was running (e.g. the
@@ -3725,7 +3743,6 @@
 
         mWorkspaceLoading = false;
         if (upgradePath) {
-            mWorkspace.saveWorkspaceToDb();
             mWorkspace.stripDuplicateApps();
             mIntentsOnWorkspaceFromUpgradePath = mWorkspace.stripDuplicateApps();
         }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f79e081..6308987 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -300,17 +300,24 @@
                         Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
                                 name, launchIntent, startSearchPageIndex);
                         if (coords == null) {
-                            // If we can't find a valid position, then just add a new screen.
-                            // This takes time so we need to re-queue the add until the new
-                            // page is added.
                             LauncherAppState appState = LauncherAppState.getInstance();
                             LauncherProvider lp = appState.getLauncherProvider();
-                            long screenId = lp.generateNewScreenId();
-                            // Update the model
-                            sBgWorkspaceScreens.add(screenId);
-                            updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
-                            // Save the screen id for binding in the workspace
-                            addedWorkspaceScreensFinal.add(screenId);
+
+                            // If we can't find a valid position, then just add a new screen.
+                            // This takes time so we need to re-queue the add until the new
+                            // page is added.  Create as many screens as necessary to satisfy
+                            // the startSearchPageIndex.
+                            int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
+                                    sBgWorkspaceScreens.size());
+                            while (numPagesToAdd > 0) {
+                                long screenId = lp.generateNewScreenId();
+                                // Update the model
+                                sBgWorkspaceScreens.add(screenId);
+                                updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+                                // Save the screen id for binding in the workspace
+                                addedWorkspaceScreensFinal.add(screenId);
+                                numPagesToAdd--;
+                            }
                             // Find the coordinate again
                             coords = LauncherModel.findNextAvailableIconSpace(context,
                                     name, launchIntent, startSearchPageIndex);
@@ -1254,7 +1261,6 @@
         private boolean mIsLoadingAndBindingWorkspace;
         private boolean mStopped;
         private boolean mLoadAndBindStepFinished;
-        private boolean mIsUpgradePath;
 
         private HashMap<Object, CharSequence> mLabelCache;
 
@@ -1272,7 +1278,8 @@
             return mIsLoadingAndBindingWorkspace;
         }
 
-        private void loadAndBindWorkspace() {
+        /** Returns whether this is an upgrade path */
+        private boolean loadAndBindWorkspace() {
             mIsLoadingAndBindingWorkspace = true;
 
             // Load the workspace
@@ -1280,18 +1287,20 @@
                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
             }
 
+            boolean isUpgradePath = false;
             if (!mWorkspaceLoaded) {
-                loadWorkspace();
+                isUpgradePath = loadWorkspace();
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
-                        return;
+                        return isUpgradePath;
                     }
                     mWorkspaceLoaded = true;
                 }
             }
 
             // Bind the workspace
-            bindWorkspace(-1);
+            bindWorkspace(-1, isUpgradePath);
+            return isUpgradePath;
         }
 
         private void waitForIdle() {
@@ -1360,13 +1369,15 @@
 
             // Divide the set of loaded items into those that we are binding synchronously, and
             // everything else that is to be bound normally (asynchronously).
-            bindWorkspace(synchronousBindPage);
+            bindWorkspace(synchronousBindPage, false);
             // XXX: For now, continue posting the binding of AllApps as there are other issues that
             //      arise from that.
             onlyBindAllApps();
         }
 
         public void run() {
+            boolean isUpgrade = false;
+
             synchronized (mLock) {
                 mIsLoaderTaskRunning = true;
             }
@@ -1384,7 +1395,7 @@
                             ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
                 }
                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
-                loadAndBindWorkspace();
+                isUpgrade = loadAndBindWorkspace();
 
                 if (mStopped) {
                     break keep_running;
@@ -1419,6 +1430,12 @@
                 sBgDbIconCache.clear();
             }
 
+            // Ensure that all the applications that are in the system are represented on the home
+            // screen.
+            if (!isUpgrade) {
+                verifyApplications();
+            }
+
             // Clear out this reference, otherwise we end up holding it until all of the
             // callback runnables are done.
             mContext = null;
@@ -1469,6 +1486,29 @@
             }
         }
 
+        private void verifyApplications() {
+            final Context context = mApp.getContext();
+
+            // Cross reference all the applications in our apps list with items in the workspace
+            ArrayList<ItemInfo> tmpInfos;
+            ArrayList<ApplicationInfo> added = new ArrayList<ApplicationInfo>();
+            synchronized (sBgLock) {
+                for (ApplicationInfo app : mBgAllAppsList.data) {
+                    tmpInfos = getItemInfoForComponentName(app.componentName);
+                    if (tmpInfos.isEmpty()) {
+                        // We are missing an application icon, so add this to the workspace
+                        added.add(app);
+                        // This is a rare event, so lets log it
+                        Log.e(TAG, "Missing Application on load: " + app);
+                    }
+                }
+            }
+            if (!added.isEmpty()) {
+                Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                addAndBindAddedApps(context, added, cb);
+            }
+        }
+
         // check & update map of what's occupied; used to discard overlapping/invalid items
         private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
             long containerIndex = item.screenId;
@@ -1522,7 +1562,8 @@
             return true;
         }
 
-        private void loadWorkspace() {
+        /** Returns whether this is an upgradge path */
+        private boolean loadWorkspace() {
             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
             final Context context = mContext;
@@ -1532,11 +1573,10 @@
             final boolean isSafeMode = manager.isSafeMode();
 
             // Make sure the default workspace is loaded, if needed
-            boolean loadOldDb = mApp.getLauncherProvider().shouldLoadOldDb();
-            Uri contentUri = loadOldDb ? LauncherSettings.Favorites.OLD_CONTENT_URI :
-                    LauncherSettings.Favorites.CONTENT_URI;
+            mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
 
-            mIsUpgradePath = loadOldDb;
+            // Check if we need to do any upgrade-path logic
+            boolean loadedOldDb = mApp.getLauncherProvider().justLoadedOldDb();
 
             synchronized (sBgLock) {
                 sBgWorkspaceItems.clear();
@@ -1547,7 +1587,7 @@
                 sBgWorkspaceScreens.clear();
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
-
+                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
 
                 // +1 for the hotseat (it can be larger than the workspace)
@@ -1655,10 +1695,6 @@
                                         folderInfo.add(info);
                                         break;
                                     }
-                                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
-                                            loadOldDb) {
-                                        info.screenId = permuteScreens(info.screenId);
-                                    }
                                     sBgItemsIdMap.put(info.id, info);
 
                                     // now that we've loaded everthing re-save it with the
@@ -1698,10 +1734,6 @@
                                         sBgWorkspaceItems.add(folderInfo);
                                         break;
                                 }
-                                if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
-                                        loadOldDb) {
-                                    folderInfo.screenId = permuteScreens(folderInfo.screenId);
-                                }
 
                                 sBgItemsIdMap.put(folderInfo.id, folderInfo);
                                 sBgFolders.put(folderInfo.id, folderInfo);
@@ -1742,11 +1774,6 @@
                                             "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
                                         continue;
                                     }
-                                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
-                                            loadOldDb) {
-                                        appWidgetInfo.screenId =
-                                                permuteScreens(appWidgetInfo.screenId);
-                                    }
 
                                     appWidgetInfo.container = c.getInt(containerIndex);
                                     // check & update map of what's occupied
@@ -1763,7 +1790,9 @@
                         }
                     }
                 } finally {
-                    c.close();
+                    if (c != null) {
+                        c.close();
+                    }
                 }
 
                 if (itemsToRemove.size() > 0) {
@@ -1784,7 +1813,7 @@
                     }
                 }
 
-                if (loadOldDb) {
+                if (loadedOldDb) {
                     long maxScreenId = 0;
                     // If we're importing we use the old screen order.
                     for (ItemInfo item: sBgItemsIdMap.values()) {
@@ -1800,6 +1829,15 @@
                     Collections.sort(sBgWorkspaceScreens);
                     mApp.getLauncherProvider().updateMaxScreenId(maxScreenId);
                     updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+
+                    // Update the max item id after we load an old db
+                    long maxItemId = 0;
+                    // If we're importing we use the old screen order.
+                    for (ItemInfo item: sBgItemsIdMap.values()) {
+                        maxItemId = Math.max(maxItemId, item.id);
+                    }
+                    LauncherAppState app = LauncherAppState.getInstance();
+                    app.getLauncherProvider().updateMaxItemId(maxItemId);
                 } else {
                     Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
                     final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
@@ -1870,16 +1908,7 @@
                     }
                 }
             }
-        }
-
-        // We rearrange the screens from the old launcher
-        // 12345 -> 34512
-        private long permuteScreens(long screen) {
-            if (screen >= 2) {
-                return screen - 2;
-            } else {
-                return screen + 3;
-            }
+            return loadedOldDb;
         }
 
         /** Filters the set of items who are directly or indirectly (via another container) on the
@@ -2087,7 +2116,7 @@
         /**
          * Binds all loaded data to actual views on the main thread.
          */
-        private void bindWorkspace(int synchronizeBindPage) {
+        private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
             final long t = SystemClock.uptimeMillis();
             Runnable r;
 
@@ -2179,7 +2208,7 @@
                 public void run() {
                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                     if (callbacks != null) {
-                        callbacks.finishBindingItems(mIsUpgradePath);
+                        callbacks.finishBindingItems(isUpgradePath);
                     }
 
                     // If we're profiling, ensure this is the last thing in the queue.
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index ae227a9..7d090e1 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -73,8 +73,10 @@
     static final String TABLE_FAVORITES = "favorites";
     static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
     static final String PARAMETER_NOTIFY = "notify";
-    static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED =
-            "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED";
+    static final String UPGRADED_FROM_OLD_DATABASE =
+            "UPGRADED_FROM_OLD_DATABASE";
+    static final String EMPTY_DATABASE_CREATED =
+            "EMPTY_DATABASE_CREATED";
     static final String DEFAULT_WORKSPACE_RESOURCE_ID =
             "DEFAULT_WORKSPACE_RESOURCE_ID";
 
@@ -90,7 +92,7 @@
             Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
 
     private DatabaseHelper mOpenHelper;
-    private static boolean sLoadOldDb;
+    private static boolean sJustLoadedFromOldDb;
 
     @Override
     public boolean onCreate() {
@@ -208,6 +210,10 @@
         return mOpenHelper.generateNewItemId();
     }
 
+    public void updateMaxItemId(long id) {
+        mOpenHelper.updateMaxItemId(id);
+    }
+
     public long generateNewScreenId() {
         return mOpenHelper.generateNewScreenId();
     }
@@ -221,21 +227,21 @@
     /**
      * @param Should we load the old db for upgrade? first run only.
      */
-    synchronized public boolean shouldLoadOldDb() {
+    synchronized public boolean justLoadedOldDb() {
         String spKey = LauncherAppState.getSharedPreferencesKey();
         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
 
-        boolean loadOldDb = false || sLoadOldDb;
+        boolean loadedOldDb = false || sJustLoadedFromOldDb;
 
-        sLoadOldDb = false;
-        if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
+        sJustLoadedFromOldDb = false;
+        if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
 
             SharedPreferences.Editor editor = sp.edit();
-            editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
+            editor.remove(UPGRADED_FROM_OLD_DATABASE);
             editor.commit();
-            loadOldDb = true;
+            loadedOldDb = true;
         }
-        return loadOldDb;
+        return loadedOldDb;
     }
 
     /**
@@ -245,7 +251,7 @@
         String spKey = LauncherAppState.getSharedPreferencesKey();
         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
 
-        if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
+        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
             int workspaceResId = origWorkspaceResId;
 
             // Use default workspace resource if none provided
@@ -255,16 +261,21 @@
 
             // Populate favorites table with initial favorites
             SharedPreferences.Editor editor = sp.edit();
-            editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
+            editor.remove(EMPTY_DATABASE_CREATED);
             if (origWorkspaceResId != 0) {
                 editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
             }
 
             mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
+            mOpenHelper.setFlagJustLoadedOldDb();
             editor.commit();
         }
     }
 
+    private static interface ContentValuesCallback {
+        public void onRow(ContentValues values);
+    }
+
     private static class DatabaseHelper extends SQLiteOpenHelper {
         private static final String TAG_FAVORITES = "favorites";
         private static final String TAG_FAVORITE = "favorite";
@@ -341,10 +352,32 @@
                 sendAppWidgetResetNotify();
             }
 
-            if (!convertDatabase(db)) {
-                // Set a shared pref so that we know we need to load the default workspace later
-                setFlagToLoadDefaultWorkspaceLater();
+            // Try converting the old database
+            ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
+                public void onRow(ContentValues values) {
+                    int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
+                    if (container == Favorites.CONTAINER_DESKTOP) {
+                        int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
+                        screen = (int) upgradeLauncherDb_permuteScreens(screen);
+                        values.put(LauncherSettings.Favorites.SCREEN, screen);
+                    }
+                }
+            };
+            Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
+                    "/old_favorites?notify=true");
+            if (!convertDatabase(db, uri, permuteScreensCb, true)) {
+                // Try and upgrade from the Launcher2 db
+                uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
+                if (!convertDatabase(db, uri, permuteScreensCb, false)) {
+                    // If we fail, then set a flag to load the default workspace
+                    setFlagEmptyDbCreated();
+                    return;
+                }
             }
+            // Right now, in non-default workspace cases, we want to run the final
+            // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
+            // set that flag too.
+            setFlagJustLoadedOldDb();
         }
 
         private void addWorkspacesTable(SQLiteDatabase db) {
@@ -354,20 +387,39 @@
                     ");");
         }
 
-        private void setFlagToLoadDefaultWorkspaceLater() {
+        private void setFlagJustLoadedOldDb() {
             String spKey = LauncherAppState.getSharedPreferencesKey();
             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
             SharedPreferences.Editor editor = sp.edit();
-            editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
+            editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
+            editor.putBoolean(EMPTY_DATABASE_CREATED, false);
             editor.commit();
         }
 
-        private boolean convertDatabase(SQLiteDatabase db) {
+        private void setFlagEmptyDbCreated() {
+            String spKey = LauncherAppState.getSharedPreferencesKey();
+            SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putBoolean(EMPTY_DATABASE_CREATED, true);
+            editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
+            editor.commit();
+        }
+
+        // We rearrange the screens from the old launcher
+        // 12345 -> 34512
+        private long upgradeLauncherDb_permuteScreens(long screen) {
+            if (screen >= 2) {
+                return screen - 2;
+            } else {
+                return screen + 3;
+            }
+        }
+
+        private boolean convertDatabase(SQLiteDatabase db, Uri uri,
+                                        ContentValuesCallback cb, boolean deleteRows) {
             if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
             boolean converted = false;
 
-            final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
-                    "/old_favorites?notify=true");
             final ContentResolver resolver = mContext.getContentResolver();
             Cursor cursor = null;
 
@@ -378,28 +430,33 @@
             }
 
             // We already have a favorites database in the old provider
-            if (cursor != null && cursor.getCount() > 0) {
+            if (cursor != null) {
                 try {
-                    converted = copyFromCursor(db, cursor) > 0;
+                     if (cursor.getCount() > 0) {
+                        converted = copyFromCursor(db, cursor, cb) > 0;
+                        if (converted && deleteRows) {
+                            resolver.delete(uri, null, null);
+                        }
+                    }
                 } finally {
                     cursor.close();
                 }
-
-                if (converted) {
-                    resolver.delete(uri, null, null);
-                }
             }
 
             if (converted) {
                 // Convert widgets from this import into widgets
                 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
                 convertWidgets(db);
+
+                // Update max item id
+                mMaxItemId = initializeMaxItemId(db);
+                if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
             }
 
             return converted;
         }
 
-        private int copyFromCursor(SQLiteDatabase db, Cursor c) {
+        private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
@@ -434,23 +491,28 @@
                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
+                if (cb != null) {
+                    cb.onRow(values);
+                }
                 rows[i++] = values;
             }
 
-            db.beginTransaction();
             int total = 0;
-            try {
-                int numValues = rows.length;
-                for (i = 0; i < numValues; i++) {
-                    if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
-                        return 0;
-                    } else {
-                        total++;
+            if (i > 0) {
+                db.beginTransaction();
+                try {
+                    int numValues = rows.length;
+                    for (i = 0; i < numValues; i++) {
+                        if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
+                            return 0;
+                        } else {
+                            total++;
+                        }
                     }
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
                 }
-                db.setTransactionSuccessful();
-            } finally {
-                db.endTransaction();
             }
 
             return total;
@@ -458,7 +520,7 @@
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            if (LOGD) Log.d(TAG, "onUpgrade triggered");
+            if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
 
             int version = oldVersion;
             if (version < 3) {
@@ -558,7 +620,7 @@
 
                 // This will never happen in the wild, but when we switch to using workspace
                 // screen ids, redo the import from old launcher.
-                sLoadOldDb = true;
+                sJustLoadedFromOldDb = true;
 
                 addWorkspacesTable(db);
                 version = 13;
@@ -722,6 +784,10 @@
             return mMaxItemId;
         }
 
+        public void updateMaxItemId(long id) {
+            mMaxItemId = id + 1;
+        }
+
         private long initializeMaxItemId(SQLiteDatabase db) {
             Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
 
@@ -861,6 +927,10 @@
                     c.close();
                 }
             }
+
+            // Update max item id
+            mMaxItemId = initializeMaxItemId(db);
+            if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
         }
 
         private static final void beginDocument(XmlPullParser parser, String firstElementName)
@@ -1018,6 +1088,11 @@
                 Log.w(TAG, "Got exception parsing favorites.", e);
             }
 
+            // Update the max item id after we have loaded the database
+            if (mMaxItemId == -1) {
+                mMaxItemId = initializeMaxItemId(db);
+            }
+
             return i;
         }
 
diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/PageIndicator.java
index d7778fb..ce98145 100644
--- a/src/com/android/launcher3/PageIndicator.java
+++ b/src/com/android/launcher3/PageIndicator.java
@@ -161,18 +161,17 @@
         mWindowRange[1] = windowEnd;
     }
 
-    void addMarker(int index) {
+    void addMarker(int index, int layoutId) {
         index = Math.max(0, Math.min(index, mMarkers.size()));
 
-        int mLayoutId = R.layout.page_indicator_marker;
         PageIndicatorMarker marker =
-            (PageIndicatorMarker) mLayoutInflater.inflate(mLayoutId, this, false);
+            (PageIndicatorMarker) mLayoutInflater.inflate(layoutId, this, false);
         mMarkers.add(index, marker);
         offsetWindowCenterTo(mActiveMarkerIndex, true);
     }
-    void addMarkers(int count) {
-        for (int i = 0; i < count; ++i) {
-            addMarker(Integer.MAX_VALUE);
+    void addMarkers(ArrayList<Integer> layoutIds) {
+        for (int i = 0; i < layoutIds.size(); ++i) {
+            addMarker(Integer.MAX_VALUE, layoutIds.get(i));
         }
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index aaff588..bb596a7 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -104,6 +104,7 @@
     protected boolean mFirstLayout = true;
 
     protected int mCurrentPage;
+    protected int mRestorePage = -1;
     protected int mChildCountOnLastLayout;
 
     protected int mNextPage = INVALID_PAGE;
@@ -326,7 +327,12 @@
         if (mPageIndicator == null && mPageIndicatorViewId > -1) {
             mPageIndicator = (PageIndicator) parent.findViewById(mPageIndicatorViewId);
             mPageIndicator.removeAllMarkers();
-            mPageIndicator.addMarkers(getChildCount());
+
+            ArrayList<Integer> markers = new ArrayList<Integer>();
+            for (int i = 0; i < getChildCount(); ++i) {
+                markers.add(getPageIndicatorMarker(i));
+            }
+            mPageIndicator.addMarkers(markers);
         }
     }
 
@@ -403,6 +409,9 @@
     PageIndicator getPageIndicator() {
         return mPageIndicator;
     }
+    protected int getPageIndicatorMarker(int pageIndex) {
+        return R.layout.page_indicator_marker;
+    }
 
     public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
         mPageSwitchListener = pageSwitchListener;
@@ -506,6 +515,14 @@
         invalidate();
     }
 
+    /**
+     * The restore page will be set in place of the current page at the next (likely first)
+     * layout.
+     */
+    void setRestorePage(int restorePage) {
+        mRestorePage = restorePage;
+    }
+
     protected void notifyPageSwitchListener() {
         if (mPageSwitchListener != null) {
             mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
@@ -824,6 +841,7 @@
         }
 
         for (int i = startIndex; i != endIndex; i += delta) {
+
             final View child = getPageAt(i);
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
             int childTop;
@@ -847,10 +865,13 @@
 
                 // We assume the left and right padding are equal, and hence center the pages
                 // horizontally
-                int scrollOffset = false ? 0 : (getViewportWidth() - childWidth) / 2;
+                int scrollOffset = (getViewportWidth() - childWidth) / 2;
                 mPageScrolls[i] = childLeft - scrollOffset - offsetX;
 
-                childLeft += childWidth + mPageSpacing;
+                if (i != endIndex - delta) {
+                    int nextScrollOffset = (getViewportWidth() - getChildWidth(i + delta)) / 2;
+                    childLeft += childWidth + nextScrollOffset;
+                }
             }
         }
 
@@ -870,7 +891,12 @@
 
         if (mScroller.isFinished() && mChildCountOnLastLayout != getChildCount() &&
                 !mDeferringForDelete) {
-            setCurrentPage(getNextPage());
+            if (mRestorePage > -1) {
+                setCurrentPage(mRestorePage);
+                mRestorePage = -1;
+            } else {
+                setCurrentPage(getNextPage());
+            }
         }
         mChildCountOnLastLayout = getChildCount();
     }
@@ -896,7 +922,8 @@
         // Update the page indicator, we don't update the page indicator as we
         // add/remove pages
         if (mPageIndicator != null && !isReordering(false)) {
-            mPageIndicator.addMarker(indexOfChild(child));
+            int pageIndex = indexOfChild(child);
+            mPageIndicator.addMarker(pageIndex, getPageIndicatorMarker(pageIndex));
         }
 
         // This ensures that when children are added, they get the correct transforms / alphas
@@ -983,7 +1010,7 @@
 
                 // Check if the right edge of the page is in the viewport
                 mTmpIntPoint[0] = currPage.getMeasuredWidth();
-                DragLayer.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+                Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
                 if (mTmpIntPoint[0] < 0) {
                     break;
                 }
@@ -993,7 +1020,7 @@
 
                 // Check if the left edge of the page is in the viewport
                 mTmpIntPoint[0] = 0;
-                DragLayer.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+                Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
                 if (mTmpIntPoint[0] >= viewportWidth) {
                     break;
                 }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 18b9399..64a87ef 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -92,13 +92,15 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int count = getChildCount();
+
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
             measureChild(child);
         }
-        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
-        setMeasuredDimension(widthSpecSize, heightSpecSize);
     }
 
     public void setupLp(CellLayout.LayoutParams lp) {
@@ -115,8 +117,15 @@
         final int cellWidth = mCellWidth;
         final int cellHeight = mCellHeight;
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-
-        lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(), mCountX);
+        if (!lp.isFullscreen) {
+            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
+                    mCountX);
+        } else {
+            lp.x = 0;
+            lp.y = 0;
+            lp.width = getMeasuredWidth();
+            lp.height = getMeasuredHeight();
+        }
         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
         int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
                 MeasureSpec.EXACTLY);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fac96d2..b4e5642 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -83,7 +83,8 @@
         }
         mIcon = info.mIcon; // TODO: should make a copy here.  maybe we don't need this ctor at all
         customIcon = info.customIcon;
-        initFlagsAndFirstInstallTime(getPackageInfo(context, intent.getComponent().getPackageName()));
+        initFlagsAndFirstInstallTime(
+                getPackageInfo(context, intent.getComponent().getPackageName()));
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -141,7 +142,8 @@
         intent.setComponent(className);
         intent.setFlags(launchFlags);
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
-        initFlagsAndFirstInstallTime(getPackageInfo(context, intent.getComponent().getPackageName()));
+        initFlagsAndFirstInstallTime(
+                getPackageInfo(context, intent.getComponent().getPackageName()));
     }
 
     @Override
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 0cc29fa..cc22bb5 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -25,6 +25,7 @@
 import android.graphics.Canvas;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.PorterDuff;
@@ -33,6 +34,10 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
 
 import com.android.launcher3.R;
 
@@ -232,6 +237,96 @@
         }
     }
 
+    /**
+     * Given a coordinate relative to the descendant, find the coordinate in a parent view's
+     * coordinates.
+     *
+     * @param descendant The descendant to which the passed coordinate is relative.
+     * @param root The root view to make the coordinates relative to.
+     * @param coord The coordinate that we want mapped.
+     * @param includeRootScroll Whether or not to account for the scroll of the descendant:
+     *          sometimes this is relevant as in a child's coordinates within the descendant.
+     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
+     *         assumption fails, we will need to return a pair of scale factors.
+     */
+    public static float getDescendantCoordRelativeToParent(View descendant, View root,
+                                                           int[] coord, boolean includeRootScroll) {
+        ArrayList<View> ancestorChain = new ArrayList<View>();
+
+        float[] pt = {coord[0], coord[1]};
+
+        View v = descendant;
+        while(v != root && v != null) {
+            ancestorChain.add(v);
+            v = (View) v.getParent();
+        }
+        ancestorChain.add(root);
+
+        float scale = 1.0f;
+        int count = ancestorChain.size();
+        for (int i = 0; i < count; i++) {
+            View v0 = ancestorChain.get(i);
+            View v1 = i < count -1 ? ancestorChain.get(i + 1) : null;
+
+            // For TextViews, scroll has a meaning which relates to the text position
+            // which is very strange... ignore the scroll.
+            if (v0 != descendant || includeRootScroll) {
+                pt[0] -= v0.getScrollX();
+                pt[1] -= v0.getScrollY();
+            }
+
+            v0.getMatrix().mapPoints(pt);
+            pt[0] += v0.getLeft();
+            pt[1] += v0.getTop();
+            scale *= v0.getScaleX();
+        }
+
+        coord[0] = (int) Math.round(pt[0]);
+        coord[1] = (int) Math.round(pt[1]);
+        return scale;
+    }
+
+    /**
+     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+     */
+    public static float mapCoordInSelfToDescendent(View descendant, View root,
+                                                   int[] coord) {
+        ArrayList<View> ancestorChain = new ArrayList<View>();
+
+        float[] pt = {coord[0], coord[1]};
+
+        View v = descendant;
+        while(v != root) {
+            ancestorChain.add(v);
+            v = (View) v.getParent();
+        }
+        ancestorChain.add(root);
+
+        float scale = 1.0f;
+        Matrix inverse = new Matrix();
+        int count = ancestorChain.size();
+        for (int i = count - 1; i >= 0; i--) {
+            View ancestor = ancestorChain.get(i);
+            View next = i > 0 ? ancestorChain.get(i-1) : null;
+
+            pt[0] += ancestor.getScrollX();
+            pt[1] += ancestor.getScrollY();
+
+            if (next != null) {
+                pt[0] -= next.getLeft();
+                pt[1] -= next.getTop();
+                next.getMatrix().invert(inverse);
+                inverse.mapPoints(pt);
+                scale *= next.getScaleX();
+            }
+        }
+
+        coord[0] = (int) Math.round(pt[0]);
+        coord[1] = (int) Math.round(pt[1]);
+        return scale;
+    }
+
     private static void initStatics(Context context) {
         final Resources resources = context.getResources();
         final DisplayMetrics metrics = resources.getDisplayMetrics();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 90b54c6..6989c9a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -528,18 +528,10 @@
         return screenId;
     }
 
-    public void addCustomContentToLeft(View customContent, CustomContentCallbacks callbacks) {
+    public void createCustomContentPage() {
         CellLayout customScreen = (CellLayout)
                 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
 
-        int spanX = customScreen.getCountX();
-        int spanY = customScreen.getCountY();
-
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
-        lp.canReorder  = false;
-
-        customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
-
         Rect p = new Rect();
         AppWidgetHostView.getDefaultPaddingForWidget(mLauncher, mLauncher.getComponentName(), p);
 
@@ -548,13 +540,28 @@
 
         addFullScreenPage(customScreen);
 
-        mCustomContentCallbacks = callbacks;
-
         // Ensure that the current page and default page are maintained.
         mDefaultPage++;
         setCurrentPage(getCurrentPage() + 1);
     }
 
+    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks) {
+        if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
+            throw new RuntimeException("Expected custom content screen to exist");
+        }
+
+        // Add the custom content to the full screen custom page
+        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+        int spanX = customScreen.getCountX();
+        int spanY = customScreen.getCountY();
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
+        lp.canReorder  = false;
+        lp.isFullscreen = true;
+        customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
+
+        mCustomContentCallbacks = callbacks;
+    }
+
     public long commitExtraEmptyScreen() {
         CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
         mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
@@ -769,6 +776,13 @@
 
     protected void onWindowVisibilityChanged (int visibility) {
         mLauncher.onWindowVisibilityChanged(visibility);
+        if (mCustomContentShowing && mCustomContentCallbacks != null) {
+            if (visibility == View.VISIBLE) {
+                mCustomContentCallbacks.onShow();
+            } else if (visibility == View.GONE) {
+                mCustomContentCallbacks.onHide();
+            }
+        }
     }
 
     @Override
@@ -929,25 +943,25 @@
             stripEmptyScreens();
             mStripScreensOnPageStopMoving = false;
         }
-    }
 
-    @Override
-    protected void notifyPageSwitchListener() {
-        super.notifyPageSwitchListener();
-        Launcher.setScreen(mCurrentPage);
-
-        if (hasCustomContent() && mCurrentPage == 0) {
+        if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
             mCustomContentShowing = true;
             if (mCustomContentCallbacks != null) {
                 mCustomContentCallbacks.onShow();
             }
-        } else if (hasCustomContent() && mCustomContentShowing) {
+        } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
             mCustomContentShowing = false;
             if (mCustomContentCallbacks != null) {
                 mCustomContentCallbacks.onHide();
                 mLauncher.resetQSBScroll();
             }
         }
+    }
+
+    @Override
+    protected void notifyPageSwitchListener() {
+        super.notifyPageSwitchListener();
+        Launcher.setScreen(mCurrentPage);
     };
 
     // As a ratio of screen height, the total distance we want the parallax effect to span
@@ -1292,7 +1306,7 @@
         }
     }
 
-    private boolean hasCustomContent() {
+    public boolean hasCustomContent() {
         return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
     }
 
@@ -1302,21 +1316,26 @@
 
     private void updateStateForCustomContent(int screenCenter) {
         if (hasCustomContent()) {
-            CellLayout customContent = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
             int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
 
             int scrollDelta = getScrollForPage(index + 1) - getScrollX();
-            float translationX = Math.max(scrollDelta, 0);
 
             float progress = (1.0f * scrollDelta) /
                     (getScrollForPage(index + 1) - getScrollForPage(index));
             progress = Math.max(0, progress);
 
             setBackgroundAlpha(progress * 0.8f);
-            float transY = progress * (getViewportHeight() - getPageIndicator().getTop());
+            float height = getViewportHeight();
+            if (getPageIndicator() != null) {
+                height -= getPageIndicator().getTop();
+            } else if (mLauncher.getHotseat() != null) {
+                height -= mLauncher.getHotseat().getTop();
+            }
+            float transY = progress * height;
 
             if (mLauncher.getHotseat() != null) {
                 mLauncher.getHotseat().setTranslationY(transY);
+                mLauncher.getHotseat().setAlpha(1 - progress);
             }
             if (getPageIndicator() != null) {
                 getPageIndicator().setAlpha(1 - progress);
@@ -3702,6 +3721,11 @@
             setCurrentDropLayout(null);
 
             if (0 <= page && page < getChildCount()) {
+                // Ensure that we are not dragging over to the custom content screen
+                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
+                    return false;
+                }
+
                 CellLayout layout = (CellLayout) getChildAt(page);
                 setCurrentDragOverlappingLayout(layout);
 
@@ -3990,6 +4014,14 @@
     }
 
     @Override
+    protected int getPageIndicatorMarker(int pageIndex) {
+        if (getScreenIdForPageIndex(pageIndex) == CUSTOM_CONTENT_SCREEN_ID) {
+            return R.layout.now_page_indicator_marker;
+        }
+        return super.getPageIndicatorMarker(pageIndex);
+    }
+
+    @Override
     public void syncPages() {
     }