Merge "Responsive caret drawable" into ub-launcher3-calgary
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9f30752..2bc0cae 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -35,6 +35,8 @@
     <string name="safemode_shortcut_error">Downloaded app disabled in Safe mode</string>
     <!-- SafeMode widget error string -->
     <string name="safemode_widget_error">Widgets disabled in Safe mode</string>
+    <!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
+    <string name="shortcut_not_available">Shortcut isn\'t available</string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d668d2a..78e0aa0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -465,7 +465,11 @@
             } else {
                 // We only load the page synchronously if the user rotates (or triggers a
                 // configuration change) while launcher is in the foreground
-                mModel.startLoader(mWorkspace.getRestorePage());
+                if (!mModel.startLoader(mWorkspace.getRestorePage())) {
+                    // If we are not binding synchronously, show a fade in animation when
+                    // the first page bind completes.
+                    mDragLayer.setAlpha(0);
+                }
             }
         }
 
@@ -2655,6 +2659,8 @@
                 int error = R.string.activity_not_available;
                 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
                     error = R.string.safemode_shortcut_error;
+                } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0) {
+                    error = R.string.shortcut_not_available;
                 }
                 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
                 return;
@@ -4003,6 +4009,32 @@
         }
     }
 
+    @Override
+    public void finishFirstPageBind(final ViewOnDrawExecutor executor) {
+        Runnable r = new Runnable() {
+            public void run() {
+                finishFirstPageBind(executor);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        Runnable onComplete = new Runnable() {
+            @Override
+            public void run() {
+                if (executor != null) {
+                    executor.onLoadAnimationCompleted();
+                }
+            }
+        };
+        if (mDragLayer.getAlpha() < 1) {
+            mDragLayer.animate().alpha(1).withEndAction(onComplete).start();
+        } else {
+            onComplete.run();
+        }
+    }
+
     /**
      * Callback saying that there aren't any more items to bind.
      *
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 17c39fa..b465b3a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -195,6 +195,7 @@
         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
                               boolean forceAnimateIcons);
         public void bindScreens(ArrayList<Long> orderedScreenIds);
+        public void finishFirstPageBind(ViewOnDrawExecutor executor);
         public void finishBindingItems();
         public void bindAppWidget(LauncherAppWidgetInfo info);
         public void bindAllApplications(ArrayList<AppInfo> apps);
@@ -1282,7 +1283,11 @@
         return (mCallbacks != null && mCallbacks.get() == callbacks);
     }
 
-    public void startLoader(int synchronousBindPage) {
+    /**
+     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
+     * @return true if the page could be bound synchronously.
+     */
+    public boolean startLoader(int synchronousBindPage) {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
         InstallShortcutReceiver.enableInstallQueue();
         synchronized (mLock) {
@@ -1302,12 +1307,14 @@
                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
                         && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
+                    return true;
                 } else {
                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                     sWorker.post(mLoaderTask);
                 }
             }
         }
+        return false;
     }
 
     public void stopLoader() {
@@ -2508,17 +2515,19 @@
                 orderedScreenIds.addAll(sBgWorkspaceScreens);
             }
 
-            final boolean isLoadingSynchronously =
-                    synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
-            int currScreen = isLoadingSynchronously ? synchronizeBindPage :
-                oldCallbacks.getCurrentWorkspaceScreen();
-            if (currScreen >= orderedScreenIds.size()) {
-                // There may be no workspace screens (just hotseat items and an empty page).
-                currScreen = PagedView.INVALID_RESTORE_PAGE;
+            final int currentScreen;
+            {
+                int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
+                        ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
+                if (currScreen >= orderedScreenIds.size()) {
+                    // There may be no workspace screens (just hotseat items and an empty page).
+                    currScreen = PagedView.INVALID_RESTORE_PAGE;
+                }
+                currentScreen = currScreen;
             }
-            final int currentScreen = currScreen;
-            final long currentScreenId = currentScreen < 0
-                    ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
+            final boolean validFirstPage = currentScreen >= 0;
+            final long currentScreenId =
+                    validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
 
             // Separate the items that are on the current screen, and all the other remaining items
             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
@@ -2551,12 +2560,24 @@
             // Load items on the current page.
             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
 
-            // In case of isLoadingSynchronously, only bind the first screen, and defer binding the
-            // remaining screens after first onDraw is called. This ensures that the first screen
-            // is immediately visible (eg. during rotation)
-            // In case of !isLoadingSynchronously, bind all pages one after other.
-            final Executor deferredExecutor = isLoadingSynchronously ?
-                    new ViewOnDrawExecutor(mHandler) : mainExecutor;
+            // In case of validFirstPage, only bind the first screen, and defer binding the
+            // remaining screens after first onDraw (and an optional the fade animation whichever
+            // happens later).
+            // This ensures that the first screen is immediately visible (eg. during rotation)
+            // In case of !validFirstPage, bind all pages one after other.
+            final Executor deferredExecutor =
+                    validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
+
+            mainExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+                    if (callbacks != null) {
+                        callbacks.finishFirstPageBind(
+                                validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
+                    }
+                }
+            });
 
             bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
 
@@ -2590,7 +2611,7 @@
             };
             deferredExecutor.execute(r);
 
-            if (isLoadingSynchronously) {
+            if (validFirstPage) {
                 r = new Runnable() {
                     public void run() {
                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
@@ -2821,9 +2842,13 @@
 
         // Now add the new shortcuts to the map.
         for (ShortcutInfoCompat shortcut : shortcuts) {
-            ComponentKey targetComponent
-                    = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
-            mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+            boolean shouldShowInContainer = shortcut.isEnabled()
+                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+            if (shouldShowInContainer) {
+                ComponentKey targetComponent
+                        = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+                mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index c87bc08..00ac9bd 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -100,22 +100,28 @@
     /**
      * Indicates that the icon is disabled due to safe mode restrictions.
      */
-    public static final int FLAG_DISABLED_SAFEMODE = 1;
+    public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
 
     /**
      * Indicates that the icon is disabled as the app is not available.
      */
-    public static final int FLAG_DISABLED_NOT_AVAILABLE = 2;
+    public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
 
     /**
      * Indicates that the icon is disabled as the app is suspended
      */
-    public static final int FLAG_DISABLED_SUSPENDED = 4;
+    public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
 
     /**
      * Indicates that the icon is disabled as the user is in quiet mode.
      */
-    public static final int FLAG_DISABLED_QUIET_USER = 8;
+    public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
+
+
+    /**
+     * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
+     */
+    public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
 
     /**
      * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
@@ -293,6 +299,11 @@
         }
         contentDescription = UserManagerCompat.getInstance(context)
                 .getBadgedLabelForUser(label, user);
+        if (shortcutInfo.isEnabled()) {
+            isDisabled &= ~FLAG_DISABLED_BY_PUBLISHER;
+        } else {
+            isDisabled |= FLAG_DISABLED_BY_PUBLISHER;
+        }
 
         LauncherAppState launcherAppState = LauncherAppState.getInstance();
         Drawable unbadgedIcon = launcherAppState.getShortcutManager()
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 3a513f1..ba48f26 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -43,6 +43,7 @@
 import android.view.animation.DecelerateInterpolator;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -718,7 +719,8 @@
         }
         mIsOpen = false;
         mDeferContainerRemoval = false;
-        cleanupDeferredDrag(true);
+        // Make the original icon visible in All Apps, but not in Workspace or Folders.
+        cleanupDeferredDrag(mDeferredDragIcon.getTag() instanceof AppInfo);
         mLauncher.getDragController().removeDragListener(this);
         mLauncher.getDragLayer().removeView(this);
     }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index 00553df..d7fcda6 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.util.ComponentKey;
 
 /**
  * Wrapper class for {@link android.content.pm.ShortcutInfo}, representing deep shortcuts into apps.
@@ -101,6 +100,14 @@
         return mShortcutInfo.isDeclaredInManifest();
     }
 
+    public boolean isEnabled() {
+        return mShortcutInfo.isEnabled();
+    }
+
+    public boolean isDynamic() {
+        return mShortcutInfo.isDynamic();
+    }
+
     public int getRank() {
         return mShortcutInfo.getRank();
     }
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 01808ba..9bd2882 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.util;
 
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewTreeObserver.OnDrawListener;
@@ -39,6 +40,9 @@
     private View mAttachedView;
     private boolean mCompleted;
 
+    private boolean mLoadAnimationCompleted;
+    private boolean mFirstDrawCompleted;
+
     public ViewOnDrawExecutor(DeferredHandler handler) {
         mHandler = handler;
     }
@@ -72,19 +76,31 @@
 
     @Override
     public void onDraw() {
+        mFirstDrawCompleted = true;
         mAttachedView.post(this);
     }
 
+    public void onLoadAnimationCompleted() {
+        mLoadAnimationCompleted = true;
+        if (mAttachedView != null) {
+            mAttachedView.post(this);
+        }
+    }
+
     @Override
     public void run() {
-        for (final Runnable r : mTasks) {
-            mHandler.post(r);
+        // Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called.
+        if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) {
+            for (final Runnable r : mTasks) {
+                mHandler.post(r);
+            }
+            markCompleted();
         }
-        markCompleted();
     }
 
     public void markCompleted() {
         mTasks.clear();
+        mCompleted = true;
         if (mAttachedView != null) {
             mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
             mAttachedView.removeOnAttachStateChangeListener(this);