Using package broadcasts to key adding of shortcuts on the workspace.

Change-Id: Id4f83cb0351d21e3f7c029c7fe39efdacd2d6f17
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index be0dd8a..0d088ac 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -1569,9 +1569,6 @@
         if (!DISABLE_ALL_APPS) {
             addAppsWithoutInvalidate(list);
             updatePageCountsAndInvalidateData();
-        } else {
-            // TODO: Maybe put them somewhere else?
-            mLauncher.getHotseat().addAppsToAllAppsFolder(list);
         }
     }
     private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d647d96..3bc7be4 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -253,7 +253,7 @@
             // Try adding to the workspace screens incrementally, starting at the default or center
             // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
             final int screen = Launcher.DEFAULT_SCREEN;
-            for (int i = 0; i < Launcher.SCREEN_COUNT; i++) {
+            for (int i = 0; i < Launcher.SCREEN_COUNT && !found; i++) {
                 int si = i;
                 if (0 <= si && si < Launcher.SCREEN_COUNT) {
                     found = installShortcut(context, data, items, name, intent, si, exists, sp,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0294961..c216572 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -71,6 +71,7 @@
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
+import android.util.Pair;
 import android.view.*;
 import android.view.View.OnLongClickListener;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -96,6 +97,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -3590,6 +3592,7 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void finishBindingItems(final boolean upgradePath) {
+
         if (waitUntilResume(new Runnable() {
                 public void run() {
                     finishBindingItems(upgradePath);
@@ -3663,6 +3666,17 @@
         return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
     }
 
+    private ValueAnimator createNewAppBounceAnimation(View v, int i) {
+        ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
+                PropertyValuesHolder.ofFloat("alpha", 1f),
+                PropertyValuesHolder.ofFloat("scaleX", 1f),
+                PropertyValuesHolder.ofFloat("scaleY", 1f));
+        bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+        bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+        bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
+        return bounceAnim;
+    }
+
     /**
      * Runs a new animation that scales up icons that were added while Launcher was in the
      * background.
@@ -3694,14 +3708,7 @@
         } else {
             for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
                 View v = mNewShortcutAnimateViews.get(i);
-                ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
-                        PropertyValuesHolder.ofFloat("alpha", 1f),
-                        PropertyValuesHolder.ofFloat("scaleX", 1f),
-                        PropertyValuesHolder.ofFloat("scaleY", 1f));
-                bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
-                bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
-                bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
-                bounceAnims.add(bounceAnim);
+                bounceAnims.add(createNewAppBounceAnimation(v, i));
             }
             anim.playTogether(bounceAnims);
             anim.addListener(new AnimatorListenerAdapter() {
@@ -3789,6 +3796,69 @@
             return;
         }
 
+        final Launcher launcher = this;
+        final Context context = this;
+        final ArrayList<ApplicationInfo> appsCopy = new ArrayList<ApplicationInfo>();
+        appsCopy.addAll(apps);
+
+        // Process newly added apps
+        mWorkspace.post(new Runnable() {
+            @Override
+            public void run() {
+                // Process newly added apps
+                LauncherModel model = getModel();
+                Iterator<ApplicationInfo> iter = appsCopy.iterator();
+                ArrayList<View> animatedShortcuts = new ArrayList<View>();
+
+                while (iter.hasNext()) {
+                    ApplicationInfo a = iter.next();
+                    Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
+                            a.title.toString(), a.intent);
+                    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.
+                        long screenId = LauncherAppState.getInstance().getLauncherProvider().generateNewScreenId();
+                        mWorkspace.insertNewWorkspaceScreen(screenId, false);
+                        model.updateWorkspaceScreenOrder(launcher, mWorkspace.getScreenOrder(),
+                            new Runnable() {
+                                @Override
+                                public void run() {
+                                    bindAppsAdded(appsCopy);
+                                }
+                            });
+                        return;
+                    } else {
+                        final ShortcutInfo shortcutInfo = a.makeShortcut();
+                        final View shortcut = createShortcut(shortcutInfo);
+                        // Add the view to the screen
+                        mWorkspace.addInScreenFromBind(shortcut,
+                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                                coords.first, coords.second[0], coords.second[1], 1, 1);
+                        // Add the shortcut to the db
+                        model.addItemToDatabase(context, shortcutInfo,
+                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                                coords.first, coords.second[0], coords.second[1], false);
+                        // Animate the shortcut
+                        animatedShortcuts.add(shortcut);
+                    }
+                    iter.remove();
+                }
+
+                // Animate all the applications up
+                AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+                Collection<Animator> bounceAnims = new ArrayList<Animator>();
+                for (int i = 0; i < animatedShortcuts.size(); ++i) {
+                    View shortcut = animatedShortcuts.get(i);
+                    shortcut.setAlpha(0f);
+                    shortcut.setScaleX(0f);
+                    shortcut.setScaleY(0f);
+                    bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
+                }
+                anim.playTogether(bounceAnims);
+                anim.start();
+            }
+        });
 
         if (mAppsCustomizeContent != null) {
             mAppsCustomizeContent.addApps(apps);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 11aeb20..5253591 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -19,14 +19,7 @@
 import android.app.SearchManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentProviderClient;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
+import android.content.*;
 import android.content.Intent.ShortcutIconResource;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
@@ -47,7 +40,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
-
+import android.util.Pair;
 import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
 
 import java.lang.ref.WeakReference;
@@ -215,6 +208,61 @@
         }
     }
 
+    static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
+                                 long screen) {
+        final int xCount = LauncherModel.getCellCountX();
+        final int yCount = LauncherModel.getCellCountY();
+        boolean[][] occupied = new boolean[xCount][yCount];
+
+        int cellX, cellY, spanX, spanY;
+        for (int i = 0; i < items.size(); ++i) {
+            final ItemInfo item = items.get(i);
+            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (item.screenId == screen) {
+                    cellX = item.cellX;
+                    cellY = item.cellY;
+                    spanX = item.spanX;
+                    spanY = item.spanY;
+                    for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
+                        for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
+                            occupied[x][y] = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
+    }
+    static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name,
+                                                        Intent launchIntent) {
+        // Lock on the app so that we don't try and get the items while apps are being added
+        LauncherAppState app = LauncherAppState.getInstance();
+        LauncherModel model = app.getModel();
+        boolean found = false;
+        synchronized (app) {
+            // Flush the LauncherModel worker thread, so that if we just did another
+            // processInstallShortcut, we give it time for its shortcut to get added to the
+            // database (getItemsInLocalCoordinates reads the database)
+            model.flushWorkerThread();
+            final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
+            final boolean shortcutExists = LauncherModel.shortcutExists(context, name, launchIntent);
+
+            // Try adding to the workspace screens incrementally, starting at the default or center
+            // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
+            for (int screen = 0; screen < sBgWorkspaceScreens.size() && !found; screen++) {
+                int[] tmpCoordinates = new int[2];
+                if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates,
+                        sBgWorkspaceScreens.get(screen))) {
+                    // Update the Launcher db
+                    return new Pair<Long, int[]>(sBgWorkspaceScreens.get(screen), tmpCoordinates);
+                }
+            }
+        }
+        // XXX: Create a new page and add it to the first spot
+        return null;
+    }
+
     public Bitmap getFallbackIcon() {
         return Bitmap.createBitmap(mDefaultIcon);
     }
@@ -817,7 +865,10 @@
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
-    static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+    void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+        updateWorkspaceScreenOrder(context, screens, null);
+    }
+    void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens, final Runnable mainThreadCb) {
         final ContentResolver cr = context.getContentResolver();
         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
 
@@ -853,6 +904,14 @@
             }
         };
         runOnWorkerThread(r);
+        if (mainThreadCb != null) {
+            runOnWorkerThread(new Runnable() {
+                @Override
+                public void run() {
+                    runOnMainThread(mainThreadCb);
+                }
+            });
+        }
     }
 
     /**
@@ -1713,9 +1772,9 @@
                         String line = "";
 
                         Iterator<Long> iter = occupied.keySet().iterator();
-                        for (int s = 0; s < nScreens; s++) {
+                        while (iter.hasNext()) {
                             long screenId = iter.next();
-                            if (s > 0) {
+                            if (screenId > 0) {
                                 line += " | ";
                             }
                             for (int x = 0; x < mCellCountX; x++) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index cac3d66..88c8e2a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -497,8 +497,6 @@
         return insertNewWorkspaceScreen(screenId, false);
     }
 
-    // If screen id is -1, this indicates there is no screen assigned, so we generate
-    // a new screen id.
     public long insertNewWorkspaceScreen(long screenId, boolean updateDb) {
         CellLayout newScreen = (CellLayout)
                 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
@@ -508,7 +506,7 @@
         mScreenOrder.add(screenId);
         if (updateDb) {
             // On bind we don't need to update the screens in the database.
-            LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+            mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
         }
         return screenId;
     }
@@ -537,6 +535,7 @@
 
     public long commitExtraEmptyScreen() {
         CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+        mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
         mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
 
         long newId = LauncherAppState.getInstance().getLauncherProvider().generateNewScreenId();
@@ -575,11 +574,15 @@
         return mScreenOrder.get(index);
     }
 
+    ArrayList<Long> getScreenOrder() {
+        return mScreenOrder;
+    }
+
     public void stripEmptyScreens() {
         ArrayList<Long> removeScreens = new ArrayList<Long>();
         for (Long id: mWorkspaceScreens.keySet()) {
             CellLayout cl = mWorkspaceScreens.get(id);
-            if (id != EXTRA_EMPTY_SCREEN_ID && cl.getShortcutsAndWidgets().getChildCount() == 0) {
+            if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
         }