Merge "Widget resizing can now displace items"
diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java
index 0199d01..0c5bbeb 100644
--- a/src/com/android/launcher2/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher2/AppsCustomizeTabHost.java
@@ -436,6 +436,8 @@
 
     private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) {
         ViewGroup parent = (ViewGroup) getParent();
+        if (parent == null) return;
+
         final int count = parent.getChildCount();
         if (!isChildrenDrawingOrderEnabled()) {
             for (int i = 0; i < count; i++) {
diff --git a/src/com/android/launcher2/DeleteDropTarget.java b/src/com/android/launcher2/DeleteDropTarget.java
index 7e4225b..d8ea6ef 100644
--- a/src/com/android/launcher2/DeleteDropTarget.java
+++ b/src/com/android/launcher2/DeleteDropTarget.java
@@ -38,6 +38,7 @@
 
 public class DeleteDropTarget extends ButtonDropTarget {
     private static int DELETE_ANIMATION_DURATION = 285;
+    private static int FLIND_DELETE_ANIMATION_DURATION = 350;
     private static int MODE_FLING_DELETE_TO_TRASH = 0;
     private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
 
@@ -307,7 +308,7 @@
         private long mPrevTime;
         private boolean mHasOffsetForScale;
 
-        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(1.5f);
+        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
 
         public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
                 long startTime) {
@@ -373,7 +374,7 @@
 
         final ViewConfiguration config = ViewConfiguration.get(mLauncher);
         final DragLayer dragLayer = mLauncher.getDragLayer();
-        final int duration = DELETE_ANIMATION_DURATION;
+        final int duration = FLIND_DELETE_ANIMATION_DURATION;
         final long startTime = AnimationUtils.currentAnimationTimeMillis();
 
         // NOTE: Because it takes time for the first frame of animation to actually be
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index 2a1d65a..f2db487 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -63,7 +63,7 @@
     static final int SCROLL_RIGHT = 1;
 
     private static final float MAX_FLING_DEGREES = 35f;
-    private static final int FLING_TO_DELETE_THRESHOLD_Y_VELOCITY = -1400;
+    private static final int FLING_TO_DELETE_THRESHOLD_Y_VELOCITY = -1500;
 
     private Launcher mLauncher;
     private Handler mHandler;
@@ -111,6 +111,7 @@
     private InputMethodManager mInputMethodManager;
 
     private int mLastTouch[] = new int[2];
+    private long mLastTouchUpTime = -1;
     private int mDistanceSinceScroll = 0;
 
     private int mTmpPoint[] = new int[2];
@@ -440,6 +441,18 @@
         return mTmpPoint;
     }
 
+    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/DragView.java b/src/com/android/launcher2/DragView.java
index 5636f99..a6aa595 100644
--- a/src/com/android/launcher2/DragView.java
+++ b/src/com/android/launcher2/DragView.java
@@ -22,7 +22,6 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PorterDuff;
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..d451ad2 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
@@ -2341,7 +2360,6 @@
             mStateAnimation = null;
         }
         final Resources res = getResources();
-        final Launcher instance = this;
 
         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
         final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
@@ -2521,9 +2539,6 @@
         updateWallpaperVisibility(true);
         showHotseat(animated);
         if (animated) {
-            final float oldScaleX = fromView.getScaleX();
-            final float oldScaleY = fromView.getScaleY();
-
             final LauncherViewPropertyAnimator scaleAnim =
                     new LauncherViewPropertyAnimator(fromView);
             scaleAnim.
@@ -3258,27 +3273,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 just add the items in place 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 +3322,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/PagedView.java b/src/com/android/launcher2/PagedView.java
index 604e73c..1fc39f6 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -17,7 +17,6 @@
 package com.android.launcher2;
 
 import android.animation.Animator;
-import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
@@ -29,7 +28,6 @@
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.ActionMode;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -42,8 +40,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
-import android.widget.Checkable;
-import android.widget.ImageView;
 import android.widget.Scroller;
 
 import com.android.launcher.R;
diff --git a/src/com/android/launcher2/StrokedTextView.java b/src/com/android/launcher2/StrokedTextView.java
index 20f9f48..4e28d17 100644
--- a/src/com/android/launcher2/StrokedTextView.java
+++ b/src/com/android/launcher2/StrokedTextView.java
@@ -89,7 +89,6 @@
     protected void onDraw(Canvas canvas) {
         if (mCache != null) {
             if (mUpdateCachedBitmap) {
-                final int gap = getCompoundDrawablePadding();
                 final int w = getMeasuredWidth();
                 final int h = getMeasuredHeight();
                 final String text = getText().toString();
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();
+            }
         }
     }
 }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index aa343b0..86f4611 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -1838,7 +1838,6 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
-        final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
         Bitmap b;
 
         if (v instanceof TextView) {