Adding delay upon user interaction to prevent the new-app animation from taking over your phone. (Bug 6248609)

- Fixing issue where we might have been reading the db items while handling previous broadcast and adding db items to invalid positions
- Making items add alternating from the center page (as opposed to the current page)
- Re-adding the strict-mode fix (really requires 1. to be true)
- Adding flag for enabling strict mode exceptions
- Removing items from the new apps add list on uninstall-shortcut broadcast

Change-Id: I495e80bf5f8dbb4b87dd709460937d6f2a1e05e7
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index 2a1d65a..9a61db6 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -440,6 +440,19 @@
         return mTmpPoint;
     }
 
+    private long mLastTouchUpTime = -1;
+    long getLastGestureUpTime() {
+        if (mDragging) {
+            return System.currentTimeMillis();
+        } else {
+            return mLastTouchUpTime;
+        }
+    }
+
+    void resetLastGestureUpTime() {
+        mLastTouchUpTime = -1;
+    }
+
     /**
      * Call this from a drag source view.
      */
@@ -467,6 +480,7 @@
                 mLastDropTarget = null;
                 break;
             case MotionEvent.ACTION_UP:
+                mLastTouchUpTime = System.currentTimeMillis();
                 if (mDragging) {
                     PointF vec = isFlingingToDelete(mDragObject.dragSource);
                     if (vec != null) {
diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java
index 4c0974f..19b1c69 100644
--- a/src/com/android/launcher2/InstallShortcutReceiver.java
+++ b/src/com/android/launcher2/InstallShortcutReceiver.java
@@ -56,7 +56,6 @@
         String spKey = LauncherApplication.getSharedPreferencesKey();
         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
 
-        final int screen = Launcher.getScreen();
         final Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         if (intent == null) {
             return;
@@ -74,17 +73,23 @@
             }
         }
 
-        final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
-        final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+        // Lock on the app so that we don't try and get the items while apps are being added
+        LauncherApplication app = (LauncherApplication) context.getApplicationContext();
         final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL};
-
-        // Try adding the target to the workspace screens incrementally, starting at the current
-        // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
         boolean found = false;
-        for (int i = 0; i < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) {
-            int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1);
-            if (0 <= si && si < Launcher.SCREEN_COUNT) {
-                found = installShortcut(context, data, items, name, intent, si, exists, sp, result);
+        synchronized (app) {
+            final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
+            final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+
+            // 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 < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) {
+                int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1);
+                if (0 <= si && si < Launcher.SCREEN_COUNT) {
+                    found = installShortcut(context, data, items, name, intent, si, exists, sp,
+                            result);
+                }
             }
         }
 
@@ -102,8 +107,8 @@
     }
 
     private boolean installShortcut(Context context, Intent data, ArrayList<ItemInfo> items,
-            String name, Intent intent, int screen, boolean shortcutExists,
-            SharedPreferences sharedPrefs, int[] result) {
+            String name, Intent intent, final int screen, boolean shortcutExists,
+            final SharedPreferences sharedPrefs, int[] result) {
         if (findEmptyCell(context, items, mCoordinates, screen)) {
             if (intent != null) {
                 if (intent.getAction() == null) {
@@ -122,10 +127,15 @@
                         newApps = sharedPrefs.getStringSet(NEW_APPS_LIST_KEY, newApps);
                     }
                     newApps.add(intent.toUri(0).toString());
-                    sharedPrefs.edit()
-                               .putInt(NEW_APPS_PAGE_KEY, screen)
-                               .putStringSet(NEW_APPS_LIST_KEY, newApps)
-                               .commit();
+                    final Set<String> savedNewApps = newApps;
+                    new Thread("setNewAppsThread") {
+                        public void run() {
+                            sharedPrefs.edit()
+                                       .putInt(NEW_APPS_PAGE_KEY, screen)
+                                       .putStringSet(NEW_APPS_LIST_KEY, savedNewApps)
+                                       .commit();
+                        }
+                    }.start();
 
                     // Update the Launcher db
                     LauncherApplication app = (LauncherApplication) context.getApplicationContext();
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index b3ce968..cef7137 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -55,10 +55,10 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -85,7 +85,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.BounceInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Advanceable;
@@ -123,6 +122,7 @@
 
     static final boolean PROFILE_STARTUP = false;
     static final boolean DEBUG_WIDGETS = false;
+    static final boolean DEBUG_STRICT_MODE = false;
 
     private static final int MENU_GROUP_WALLPAPER = 1;
     private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
@@ -182,6 +182,9 @@
     private static final Object sLock = new Object();
     private static int sScreen = DEFAULT_SCREEN;
 
+    // How long to wait before the new-shortcut animation automatically pans the workspace
+    private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 10;
+
     private final BroadcastReceiver mCloseSystemDialogsReceiver
             = new CloseSystemDialogsIntentReceiver();
     private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
@@ -289,6 +292,21 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        if (DEBUG_STRICT_MODE) {
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .detectDiskReads()
+                    .detectDiskWrites()
+                    .detectNetwork()   // or .detectAll() for all detectable problems
+                    .penaltyLog()
+                    .build());
+            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+                    .detectLeakedSqlLiteObjects()
+                    .detectLeakedClosableObjects()
+                    .penaltyLog()
+                    .penaltyDeath()
+                    .build());
+        }
+
         super.onCreate(savedInstanceState);
         LauncherApplication app = ((LauncherApplication)getApplication());
         mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(),
@@ -635,6 +653,7 @@
         super.onPause();
         mPaused = true;
         mDragController.cancelDrag();
+        mDragController.resetLastGestureUpTime();
     }
 
     @Override
@@ -3258,27 +3277,46 @@
             Runnable newAppsRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    runNewAppsAnimation();
+                    runNewAppsAnimation(false);
                 }
             };
-            if (mNewShortcutAnimatePage > -1 &&
-                    mNewShortcutAnimatePage != mWorkspace.getCurrentPage()) {
-                mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable);
+
+            boolean willSnapPage = mNewShortcutAnimatePage > -1 &&
+                    mNewShortcutAnimatePage != mWorkspace.getCurrentPage();
+            if (canRunNewAppsAnimation()) {
+                // If the user has not interacted recently, then either snap to the new page to show
+                // the new-apps animation or just run them if they are to appear on the current page
+                if (willSnapPage) {
+                    mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable);
+                } else {
+                    runNewAppsAnimation(false);
+                }
             } else {
-                newAppsRunnable.run();
+                // If the user has interacted recently, then run the animations immediately if they
+                // are on another page (or just normally if they are added to the current page)
+                runNewAppsAnimation(willSnapPage);
             }
         }
 
         mWorkspaceLoading = false;
     }
 
+    private boolean canRunNewAppsAnimation() {
+        long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
+        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+    }
+
     /**
      * Runs a new animation that scales up icons that were added while Launcher was in the
      * background.
+     *
+     * @param immediate whether to run the animation or show the results immediately
      */
-    private void runNewAppsAnimation() {
+    private void runNewAppsAnimation(boolean immediate) {
         AnimatorSet anim = new AnimatorSet();
         Collection<Animator> bounceAnims = new ArrayList<Animator>();
+
+        // Order these new views spatially so that they animate in order
         Collections.sort(mNewShortcutAnimateViews, new Comparator<View>() {
             @Override
             public int compare(View a, View b) {
@@ -3288,25 +3326,35 @@
                 return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX);
             }
         });
-        for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
-            View v = mNewShortcutAnimateViews.get(i);
-            ValueAnimator bounceAnim = ObjectAnimator.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);
-        }
-        anim.playTogether(bounceAnims);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+
+        // Animate each of the views in place (or show them immediately if requested)
+        if (immediate) {
+            for (View v : mNewShortcutAnimateViews) {
+                v.setAlpha(1f);
+                v.setScaleX(1f);
+                v.setScaleY(1f);
             }
-        });
-        anim.start();
+        } else {
+            for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
+                View v = mNewShortcutAnimateViews.get(i);
+                ValueAnimator bounceAnim = ObjectAnimator.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);
+            }
+            anim.playTogether(bounceAnims);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+                }
+            });
+            anim.start();
+        }
 
         // Clean up
         mNewShortcutAnimatePage = -1;
diff --git a/src/com/android/launcher2/UninstallShortcutReceiver.java b/src/com/android/launcher2/UninstallShortcutReceiver.java
index eb4ee4c..3f6de7c 100644
--- a/src/com/android/launcher2/UninstallShortcutReceiver.java
+++ b/src/com/android/launcher2/UninstallShortcutReceiver.java
@@ -20,11 +20,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ContentResolver;
+import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.net.Uri;
 import android.widget.Toast;
 
 import java.net.URISyntaxException;
+import java.util.HashSet;
+import java.util.Set;
 
 import com.android.launcher.R;
 
@@ -36,7 +39,16 @@
         if (!ACTION_UNINSTALL_SHORTCUT.equals(data.getAction())) {
             return;
         }
+        String spKey = LauncherApplication.getSharedPreferencesKey();
+        SharedPreferences sharedPrefs = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
 
+        LauncherApplication app = (LauncherApplication) context.getApplicationContext();
+        synchronized (app) {
+            removeShortcut(context, data, sharedPrefs);
+        }
+    }
+
+    private void removeShortcut(Context context, Intent data, final SharedPreferences sharedPrefs) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
         boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
@@ -77,6 +89,29 @@
                 Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name),
                         Toast.LENGTH_SHORT).show();
             }
+
+            // Remove any items due to be animated
+            boolean appRemoved;
+            Set<String> newApps = new HashSet<String>();
+            newApps = sharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
+            do {
+                appRemoved = newApps.remove(intent.toUri(0).toString());
+            } while (appRemoved);
+            if (appRemoved) {
+                final Set<String> savedNewApps = newApps;
+                new Thread("setNewAppsThread-remove") {
+                    public void run() {
+                        SharedPreferences.Editor editor = sharedPrefs.edit();
+                        editor.putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY,
+                                savedNewApps);
+                        if (savedNewApps.isEmpty()) {
+                            // Reset the page index if there are no more items
+                            editor.putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1);
+                        }
+                        editor.commit();
+                    }
+                }.start();
+            }
         }
     }
 }