Show promise app icon progress in All Apps and setup market intent
on clicking promise app icon in All Apps.

Only the progress will be changed with animation on progress update,
no relayout is performed. If the icon is newly bound, the progress
will not be animated.

Bug: 23952570
Change-Id: I98d3f945f08a2abadf53f20e6007c15e56d5d410
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index f9a6742..add0185 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -39,7 +39,6 @@
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
@@ -181,6 +180,10 @@
         // Verify high res immediately
         verifyHighRes();
 
+        if (info instanceof PromiseAppInfo) {
+            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
+            applyProgressLevel(promiseAppInfo.level);
+        }
         applyBadgeState(info, false /* animate */);
     }
 
@@ -476,27 +479,36 @@
                     ((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ?
                             info.getInstallProgress() : 0)) : 100;
 
-            setContentDescription(progressLevel > 0 ?
-                    getContext().getString(R.string.app_downloading_title, info.title,
-                            NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
-                    getContext().getString(R.string.app_waiting_download_title, info.title));
+            PreloadIconDrawable preloadDrawable = applyProgressLevel(progressLevel);
+            if (preloadDrawable != null && promiseStateChanged) {
+                preloadDrawable.maybePerformFinishedAnimation();
+            }
+        }
+    }
+
+    public PreloadIconDrawable applyProgressLevel(int progressLevel) {
+        if (getTag() instanceof ItemInfoWithIcon) {
+            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
+            setContentDescription(progressLevel > 0
+                    ? getContext().getString(R.string.app_downloading_title, info.title,
+                    NumberFormat.getPercentInstance().format(progressLevel * 0.01))
+                    : getContext().getString(R.string.app_waiting_download_title, info.title));
 
             if (mIcon != null) {
                 final PreloadIconDrawable preloadDrawable;
                 if (mIcon instanceof PreloadIconDrawable) {
                     preloadDrawable = (PreloadIconDrawable) mIcon;
+                    preloadDrawable.setLevel(progressLevel);
                 } else {
                     preloadDrawable = DrawableFactory.get(getContext())
                             .newPendingIcon(info.iconBitmap, getContext());
+                    preloadDrawable.setLevel(progressLevel);
                     setIcon(preloadDrawable);
                 }
-
-                preloadDrawable.setLevel(progressLevel);
-                if (promiseStateChanged) {
-                    preloadDrawable.maybePerformFinishedAnimation();
-                }
+                return preloadDrawable;
             }
         }
+        return null;
     }
 
     public void applyBadgeState(ItemInfo itemInfo, boolean animate) {
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 34adf47..2f61a01 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -60,6 +60,12 @@
      */
     public static boolean startDetailsActivityForInfo(
             ItemInfo info, Launcher launcher, DropTargetResultCallback callback) {
+        if (info instanceof PromiseAppInfo) {
+            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
+            launcher.startActivity(promiseAppInfo.getMarketIntent());
+            return true;
+        }
+
         boolean result = false;
         ComponentName componentName = null;
         if (info instanceof AppInfo) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d5baabf..c7f0b47 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2454,7 +2454,13 @@
 
     private void startAppShortcutOrInfoActivity(View v) {
         ItemInfo item = (ItemInfo) v.getTag();
-        Intent intent = item.getIntent();
+        Intent intent;
+        if (item instanceof PromiseAppInfo) {
+            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+            intent = promiseAppInfo.getMarketIntent();
+        } else {
+            intent = item.getIntent();
+        }
         if (intent == null) {
             throw new IllegalArgumentException("Input must have a valid intent");
         }
@@ -3763,6 +3769,22 @@
     }
 
     @Override
+    public void bindPromiseAppProgressUpdated(final PromiseAppInfo app) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindPromiseAppProgressUpdated(app);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
+        }
+
+        if (mAppsView != null) {
+            mAppsView.updatePromiseAppProgress(app);
+        }
+    }
+
+    @Override
     public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
         Runnable r = new Runnable() {
             public void run() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 707ec86..39b9dd3 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -195,6 +195,7 @@
                                   ArrayList<ItemInfo> addAnimated,
                                   ArrayList<AppInfo> addedApps);
         public void bindAppsUpdated(ArrayList<AppInfo> apps);
+        public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
                 ArrayList<ShortcutInfo> removed, UserHandle user);
         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java
index 04ba1d3..1749dde 100644
--- a/src/com/android/launcher3/PromiseAppInfo.java
+++ b/src/com/android/launcher3/PromiseAppInfo.java
@@ -20,6 +20,7 @@
 import android.support.annotation.NonNull;
 
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.util.PackageManagerHelper;
 
 public class PromiseAppInfo extends AppInfo {
 
@@ -44,4 +45,8 @@
         shortcut.status |= ShortcutInfo.FLAG_RESTORE_STARTED;
         return shortcut;
     }
+
+    public Intent getMarketIntent() {
+        return PackageManagerHelper.getMarketIntent(componentName.getPackageName());
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index cc5fa8c..7219141 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -37,6 +37,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -45,6 +46,7 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -158,6 +160,17 @@
         mSearchBarController.refreshSearchResult();
     }
 
+    public void updatePromiseAppProgress(PromiseAppInfo app) {
+        int childCount = mAppsRecyclerView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mAppsRecyclerView.getChildAt(i);
+            if (child instanceof BubbleTextView && child.getTag() == app) {
+                BubbleTextView bubbleTextView = (BubbleTextView) child;
+                bubbleTextView.applyProgressLevel(app.level);
+            }
+        }
+    }
+
     /**
      * Removes some apps from the list.
      */
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 9c5f189..f8793e0 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -51,7 +51,7 @@
         }
 
         synchronized (apps) {
-            final ArrayList<AppInfo> updated = new ArrayList<>();
+            PromiseAppInfo updated = null;
             final ArrayList<AppInfo> removed = new ArrayList<>();
             for (int i=0; i < apps.size(); i++) {
                 final AppInfo appInfo = apps.get(i);
@@ -61,7 +61,7 @@
                         final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
                         if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
                             promiseAppInfo.level = mInstallInfo.progress;
-                            updated.add(appInfo);
+                            updated = promiseAppInfo;
                         } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED
                                 || mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
                             apps.removePromiseApp(appInfo);
@@ -70,13 +70,12 @@
                     }
                 }
             }
-            if (!updated.isEmpty()) {
+            if (updated != null) {
+                final PromiseAppInfo updatedPromiseApp = updated;
                 scheduleCallbackTask(new CallbackTask() {
                     @Override
                     public void execute(Callbacks callbacks) {
-                        // TODO: this currently causes unnecessary relayouts
-                        // we need to introduce a new bindPromiseAppsChanged
-                        callbacks.bindAppsUpdated(updated);
+                        callbacks.bindPromiseAppProgressUpdated(updatedPromiseApp);
                     }
                 });
             }