Improving transition between Apps/Widgets panes (5145408)

Change-Id: Id4f73a89b646bbf168ee49e8bdeb34b85a52d9d4
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
index 05e7fc1..95b7dc2 100644
--- a/res/layout/apps_customize_pane.xml
+++ b/res/layout/apps_customize_pane.xml
@@ -63,6 +63,11 @@
                 launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
                 launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
                 launcher:maxGap="@dimen/workspace_max_gap" />
+            <ImageView
+                android:id="@+id/animation_buffer"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="gone" />
 
             <include
                 android:id="@+id/paged_view_indicator"
diff --git a/res/values/config.xml b/res/values/config.xml
index 7eebc4a..37710b6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -31,7 +31,7 @@
     <integer name="config_appsCustomizeWorkspaceShrinkTime">1000</integer>
 
     <!-- Tab transition animation duration -->
-    <integer name="config_tabTransitionDuration">100</integer>
+    <integer name="config_tabTransitionDuration">200</integer>
 
     <!-- The slope, in percent, of the drag movement needed to drag an item out of
          AppsCustomize (y / x * 100%)  -->
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 9d03995..1da6fe0 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -188,6 +188,7 @@
     // Previews & outlines
     ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
     private HolographicOutlineHelper mHolographicOutlineHelper;
+    private static final int sPageSleepDelay = 200;
 
     public AppsCustomizePagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -489,7 +490,7 @@
 
     public void setContentType(ContentType type) {
         mContentType = type;
-        invalidatePageData(0);
+        invalidatePageData(0, true);
     }
 
     public boolean isContentType(ContentType type) {
@@ -536,7 +537,7 @@
             addView(layout);
         }
     }
-    public void syncAppsPageItems(int page) {
+    public void syncAppsPageItems(int page, boolean immediate) {
         // ensure that we have the right number of items on the pages
         int numPages = getPageCount();
         int numCells = mCellCountX * mCellCountY;
@@ -592,6 +593,10 @@
             return Process.THREAD_PRIORITY_DEFAULT;
         }
     }
+    private int getSleepForPage(int page) {
+        int pageDiff = Math.abs(page - mCurrentPage) - 1;
+        return Math.max(0, pageDiff * sPageSleepDelay);
+    }
     /**
      * Creates and executes a new AsyncTask to load a page of widget previews.
      */
@@ -612,37 +617,16 @@
             }
         }
 
+        // We introduce a slight delay to order the loading of side pages so that we don't thrash
+        final int sleepMs = getSleepForPage(page);
         AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
             cellCountX, new AsyncTaskCallback() {
                 @Override
                 public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
-                    // Ensure that this task starts running at the correct priority
-                    task.syncThreadPriority();
-
-                    // Load each of the widget/shortcut previews
-                    ArrayList<Object> items = data.items;
-                    ArrayList<Bitmap> images = data.generatedImages;
-                    int count = items.size();
-                    int cellWidth = data.cellWidth;
-                    int cellHeight = data.cellHeight;
-                    for (int i = 0; i < count && !task.isCancelled(); ++i) {
-                        // Before work on each item, ensure that this task is running at the correct
-                        // priority
-                        task.syncThreadPriority();
-
-                        Object rawInfo = items.get(i);
-                        if (rawInfo instanceof AppWidgetProviderInfo) {
-                            AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
-                            int[] cellSpans = CellLayout.rectToCell(getResources(),
-                                    info.minWidth, info.minHeight, null);
-                            images.add(getWidgetPreview(info, cellSpans[0],cellSpans[1],
-                                    cellWidth, cellHeight));
-                        } else if (rawInfo instanceof ResolveInfo) {
-                            // Fill in the shortcuts information
-                            ResolveInfo info = (ResolveInfo) rawInfo;
-                            images.add(getShortcutPreview(info, cellWidth, cellHeight));
-                        }
-                    }
+                    try {
+                        Thread.sleep(sleepMs);
+                    } catch (Exception e) {}
+                    loadWidgetPreviewsInBackground(task, data);
                 }
             },
             new AsyncTaskCallback() {
@@ -863,7 +847,7 @@
             addView(layout);
         }
     }
-    public void syncWidgetPageItems(int page) {
+    public void syncWidgetPageItems(int page, boolean immediate) {
         int numItemsPerPage = mWidgetCountX * mWidgetCountY;
         int contentWidth = mWidgetSpacingLayout.getContentWidth();
         int contentHeight = mWidgetSpacingLayout.getContentHeight();
@@ -880,7 +864,50 @@
             items.add(mWidgets.get(i));
         }
 
-        prepareLoadWidgetPreviewsTask(page, items, cellWidth, cellHeight, mWidgetCountX);
+        if (immediate) {
+            AsyncTaskPageData data = new AsyncTaskPageData(page, items, cellWidth, cellHeight,
+                    mWidgetCountX, null, null);
+            loadWidgetPreviewsInBackground(null, data);
+            onSyncWidgetPageItems(data);
+        } else {
+            prepareLoadWidgetPreviewsTask(page, items, cellWidth, cellHeight, mWidgetCountX);
+        }
+    }
+    private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
+            AsyncTaskPageData data) {
+        if (task != null) {
+            // Ensure that this task starts running at the correct priority
+            task.syncThreadPriority();
+        }
+
+        // Load each of the widget/shortcut previews
+        ArrayList<Object> items = data.items;
+        ArrayList<Bitmap> images = data.generatedImages;
+        int count = items.size();
+        int cellWidth = data.cellWidth;
+        int cellHeight = data.cellHeight;
+        for (int i = 0; i < count; ++i) {
+            if (task != null) {
+                // Ensure we haven't been cancelled yet
+                if (task.isCancelled()) break;
+                // Before work on each item, ensure that this task is running at the correct
+                // priority
+                task.syncThreadPriority();
+            }
+
+            Object rawInfo = items.get(i);
+            if (rawInfo instanceof AppWidgetProviderInfo) {
+                AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
+                int[] cellSpans = CellLayout.rectToCell(getResources(),
+                        info.minWidth, info.minHeight, null);
+                images.add(getWidgetPreview(info, cellSpans[0],cellSpans[1],
+                        cellWidth, cellHeight));
+            } else if (rawInfo instanceof ResolveInfo) {
+                // Fill in the shortcuts information
+                ResolveInfo info = (ResolveInfo) rawInfo;
+                images.add(getShortcutPreview(info, cellWidth, cellHeight));
+            }
+        }
     }
     private void onSyncWidgetPageItems(AsyncTaskPageData data) {
         int page = data.page;
@@ -991,13 +1018,13 @@
         }
     }
     @Override
-    public void syncPageItems(int page) {
+    public void syncPageItems(int page, boolean immediate) {
         switch (mContentType) {
         case Applications:
-            syncAppsPageItems(page);
+            syncAppsPageItems(page, immediate);
             break;
         case Widgets:
-            syncWidgetPageItems(page);
+            syncWidgetPageItems(page, immediate);
             break;
         }
     }
diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java
index 6bd4c03..d6ae145 100644
--- a/src/com/android/launcher2/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher2/AppsCustomizeTabHost.java
@@ -18,15 +18,19 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
 import android.widget.TabHost;
 import android.widget.TabWidget;
 import android.widget.TextView;
@@ -44,6 +48,7 @@
     private ViewGroup mTabs;
     private ViewGroup mTabsContainer;
     private AppsCustomizePagedView mAppsCustomizePane;
+    private ImageView mAnimationBuffer;
 
     private boolean mInTransition;
     private boolean mResetAfterTransition;
@@ -89,6 +94,7 @@
         mTabs = tabs;
         mTabsContainer = tabsContainer;
         mAppsCustomizePane = appsCustomizePane;
+        mAnimationBuffer = (ImageView) findViewById(R.id.animation_buffer);
         if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException();
 
         // Configure the tabs content factory to return the same paged view (that we change the
@@ -168,31 +174,53 @@
             final Resources res = getResources();
             final int duration = res.getInteger(R.integer.config_tabTransitionDuration);
 
-            ObjectAnimator anim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 0f);
-            anim.setDuration(duration);
-            anim.addListener(new AnimatorListenerAdapter() {
+            // We post a runnable here because there is a delay while the first page is loading and
+            // the feedback from having changed the tab almost feels better than having it stick
+            post(new Runnable() {
                 @Override
-                public void onAnimationStart(android.animation.Animator animation) {
+                public void run() {
+                    // Setup the animation buffer
+                    Bitmap b = Bitmap.createBitmap(mAppsCustomizePane.getMeasuredWidth(),
+                            mAppsCustomizePane.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
+                    Canvas c = new Canvas(b);
+                    mAppsCustomizePane.draw(c);
+                    mAppsCustomizePane.setAlpha(0f);
+                    mAnimationBuffer.setImageBitmap(b);
+                    mAnimationBuffer.setAlpha(1f);
+                    mAnimationBuffer.setVisibility(View.VISIBLE);
+                    c.setBitmap(null);
+                    b = null;
+
+                    // Toggle the new content
                     onTabChangedStart();
-                }
-                @Override
-                public void onAnimationEnd(android.animation.Animator animation) {
                     onTabChangedEnd(type);
 
-                    ObjectAnimator anim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f);
-                    anim.setDuration(duration);
-                    anim.addListener(new AnimatorListenerAdapter() {
+                    // Animate the transition
+                    ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f);
+                    outAnim.addListener(new AnimatorListenerAdapter() {
                         @Override
-                        public void onAnimationEnd(android.animation.Animator animation) {
+                        public void onAnimationEnd(Animator animation) {
+                            mAnimationBuffer.setVisibility(View.GONE);
+                            mAnimationBuffer.setImageBitmap(null);
+                        }
+                    });
+                    ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f);
+                    inAnim.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
                             if (!LauncherApplication.isScreenLarge()) {
                                 mAppsCustomizePane.flashScrollingIndicator();
                             }
+                            mAppsCustomizePane.loadAssociatedPages(
+                                    mAppsCustomizePane.getCurrentPage());
                         }
                     });
-                    anim.start();
+                    AnimatorSet animSet = new AnimatorSet();
+                    animSet.playTogether(outAnim, inAnim);
+                    animSet.setDuration(duration);
+                    animSet.start();
                 }
             });
-            anim.start();
         }
     }
 
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 7ab41bc..ad0c42b 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -670,6 +670,8 @@
                 mAppsCustomizeContent.setContentType(
                         mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
                 mAppsCustomizeTabHost.setCurrentTabByTag(curTab);
+                mAppsCustomizeContent.loadAssociatedPages(
+                        mAppsCustomizeContent.getCurrentPage());
             }
 
             // Note: currently we do not restore the page for the AppsCustomize pane because the
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index d2d734c..6e26363 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -1494,7 +1494,10 @@
         };
     }
 
-    public void loadAssociatedPages(int page) {
+    protected void loadAssociatedPages(int page) {
+        loadAssociatedPages(page, false);
+    }
+    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
         if (mContentIsRefreshable) {
             final int count = getChildCount();
             if (page < count) {
@@ -1503,11 +1506,14 @@
                 if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
                         + upperPageBound);
                 for (int i = 0; i < count; ++i) {
+                    if ((i != page) && immediateAndOnly) {
+                        continue;
+                    }
                     Page layout = (Page) getChildAt(i);
                     final int childCount = layout.getPageChildCount();
                     if (lowerPageBound <= i && i <= upperPageBound) {
                         if (mDirtyPageContent.get(i)) {
-                            syncPageItems(i);
+                            syncPageItems(i, (i == page) && immediateAndOnly);
                             mDirtyPageContent.set(i, false);
                         }
                     } else {
@@ -1607,7 +1613,7 @@
      * This method is called to synchronize the items that are on a particular page.  If views on
      * the page can be reused, then they should be updated within this method.
      */
-    public abstract void syncPageItems(int page);
+    public abstract void syncPageItems(int page, boolean immediate);
 
     protected void postInvalidatePageData(final boolean clearViews) {
         post(new Runnable() {
@@ -1622,9 +1628,12 @@
     }
 
     protected void invalidatePageData() {
-        invalidatePageData(-1);
+        invalidatePageData(-1, false);
     }
     protected void invalidatePageData(int currentPage) {
+        invalidatePageData(currentPage, false);
+    }
+    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
         if (!mIsDataReady) {
             return;
         }
@@ -1651,7 +1660,7 @@
             }
 
             // Load any pages that are necessary for the current window of views
-            loadAssociatedPages(mCurrentPage);
+            loadAssociatedPages(mCurrentPage, immediateAndOnly);
             mDirtyPageAlpha = true;
             updateAdjacentPagesAlpha();
             requestLayout();
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 947c946..c04762f 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -3544,7 +3544,7 @@
     }
 
     @Override
-    public void syncPageItems(int page) {
+    public void syncPageItems(int page, boolean immediate) {
     }
 
     @Override