Merge "Moving the scrim to draglayer" into ub-launcher3-calgary-polish
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 3da3535..bbe1f4a 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -50,7 +50,7 @@
         android:fullBackupContent="@xml/backupscheme"
         android:hardwareAccelerated="true"
         android:icon="@mipmap/ic_launcher_home"
-        android:label="@string/app_name"
+        android:label="@string/derived_app_name"
         android:largeHeap="@bool/config_largeHeap"
         android:restoreAnyVersion="true"
         android:supportsRtl="true" >
diff --git a/res/values/config.xml b/res/values/config.xml
index 94f02f9..2347f66 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -9,6 +9,10 @@
     <bool name="is_large_tablet">false</bool>
     <bool name="allow_rotation">false</bool>
 
+    <!-- A string pointer to the original app name string. This allows derived projects to
+     easily override the app name without providing all translations -->
+    <string name="derived_app_name" translatable="false">@string/app_name</string>
+
 <!-- DragController -->
     <item type="id" name="drag_event_parity" />
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 15f47b4..e6802bd 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -589,9 +589,9 @@
             return new int[] {0, 0};
         }
 
-        // In landscape, we just match the vertical display width
-        int containerWidth = heightPx;
-        int padding = (availableWidthPx - containerWidth) / 2;
+        // In landscape, we match the width of the workspace
+        int padding = (pageIndicatorLandGutterRightNavBarPx +
+                hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2;
         return new int[]{ padding, padding };
     }
 }
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index ceaedef..c738480 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -52,6 +54,7 @@
     private int mBackgroundColor;
     @ViewDebug.ExportedProperty(category = "launcher")
     private ColorDrawable mBackground;
+    private ValueAnimator mBackgroundColorAnimator;
 
     public Hotseat(Context context) {
         this(context, null);
@@ -177,18 +180,27 @@
     public void updateColor(ExtractedColors extractedColors, boolean animate) {
         if (!mHasVerticalHotseat) {
             int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT);
+            if (mBackgroundColorAnimator != null) {
+                mBackgroundColorAnimator.cancel();
+            }
             if (!animate) {
                 setBackgroundColor(color);
             } else {
-                ValueAnimator animator = ValueAnimator.ofInt(mBackgroundColor, color);
-                animator.setEvaluator(new ArgbEvaluator());
-                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                mBackgroundColorAnimator = ValueAnimator.ofInt(mBackgroundColor, color);
+                mBackgroundColorAnimator.setEvaluator(new ArgbEvaluator());
+                mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                     @Override
                     public void onAnimationUpdate(ValueAnimator animation) {
                         mBackground.setColor((Integer) animation.getAnimatedValue());
                     }
                 });
-                animator.start();
+                mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mBackgroundColorAnimator = null;
+                    }
+                });
+                mBackgroundColorAnimator.start();
             }
             mBackgroundColor = color;
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3d35b1b..f125287 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -865,7 +865,7 @@
             } else {
                 // TODO: Show a snack bar with link to settings
                 Toast.makeText(this, getString(R.string.msg_no_phone_permission,
-                        getString(R.string.app_name)), Toast.LENGTH_SHORT).show();
+                        getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
             }
         }
         if (mLauncherCallbacks != null) {
@@ -2760,8 +2760,10 @@
      */
     public void onClickSettingsButton(View v) {
         if (LOGD) Log.d(TAG, "onClickSettingsButton");
-        startActivity(new Intent(Utilities.ACTION_APPLICATION_PREFERENCES)
-                .setPackage(getPackageName()));
+        Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+                .setPackage(getPackageName());
+        intent.setSourceBounds(getViewBounds(v));
+        startActivity(intent, getActivityLaunchOptions(v));
     }
 
     public View.OnTouchListener getHapticFeedbackTouchListener() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d4223e1..1607a4a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -862,7 +862,9 @@
                     Intent targetIntent = info.promisedIntent == null
                             ? info.intent : info.promisedIntent;
                     if (targetIntent != null && info.user.equals(user)) {
-                        String s = targetIntent.toUri(0);
+                        Intent copyIntent = new Intent(targetIntent);
+                        copyIntent.setSourceBounds(intent.getSourceBounds());
+                        String s = copyIntent.toUri(0);
                         if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
                             return true;
                         }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 50f7156..2988fb9 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -183,10 +183,6 @@
         }
     }
 
-    // TODO: Use Intent.ACTION_APPLICATION_PREFERENCES when N SDK is available.
-    public static final String ACTION_APPLICATION_PREFERENCES
-            = "android.intent.action.APPLICATION_PREFERENCES";
-
     public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
         byte[] data = c.getBlob(iconIndex);
         try {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 600768e..fd647c7 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -974,6 +974,26 @@
     }
 
     /**
+     * Removes any broken item from the hotseat.
+     * @return a map with occupied hotseat position set to non-null value.
+     */
+    public static LongArrayMap<Object> removeBrokenHotseatItems(Context context) throws Exception {
+        GridSizeMigrationTask task = new GridSizeMigrationTask(context,
+                LauncherAppState.getInstance().getInvariantDeviceProfile(),
+                getValidPackages(context), Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+        // Load all the valid entries
+        ArrayList<DbEntry> items = task.loadHotseatEntries();
+        // Delete any entry marked for deletion by above load.
+        task.applyOperations();
+        LongArrayMap<Object> positions = new LongArrayMap<>();
+        for (DbEntry item : items) {
+            positions.put(item.screenId, item);
+        }
+        return positions;
+    }
+
+    /**
      * Task to run grid migration in multiple steps when the size difference is more than 1.
      */
     protected static class MultiStepMigrationTask {
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index b3f0c82..0d7ba1e 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -68,6 +68,17 @@
             return thisWorkProfile ? 1 : -1;
         }
 
-        return sCollator.compare(label, another.label);
+        int labelCompare = sCollator.compare(label, another.label);
+        if (labelCompare != 0) {
+            return labelCompare;
+        }
+
+        // If the label is same, put the smaller widget before the larger widget. If the area is
+        // also same, put the widget with smaller height before.
+        int thisArea = spanX * spanY;
+        int otherArea = another.spanX * another.spanY;
+        return thisArea == otherArea
+                ? Integer.compare(spanY, another.spanY)
+                : Integer.compare(thisArea, otherArea);
     }
 }
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 233c3ed..5cb34e8 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -148,7 +148,6 @@
 
         // Set of package names present in hotseat
         final HashSet<String> hotseatTargetApps = new HashSet<>();
-        final LongArrayMap<Intent> hotseatItems = new LongArrayMap<>();
         int maxId = 0;
 
         // Number of imported items on workspace and hotseat
@@ -270,7 +269,6 @@
                     if (intent.getComponent() != null) {
                         intent.setPackage(intent.getComponent().getPackageName());
                     }
-                    hotseatItems.put(screen, intent);
                     hotseatTargetApps.add(getPackage(intent));
                 }
 
@@ -299,7 +297,13 @@
         if (totalItemsOnWorkspace < MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION) {
             throw new Exception("Insufficient data");
         }
+        if (!insertOperations.isEmpty()) {
+            mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY,
+                    insertOperations);
+            insertOperations.clear();
+        }
 
+        LongArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
         int myHotseatCount = LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons;
         if (!FeatureFlags.NO_ALL_APPS_ICON) {
             myHotseatCount--;
@@ -307,14 +311,15 @@
         if (hotseatItems.size() < myHotseatCount) {
             // Insufficient hotseat items. Add a few more.
             HotseatParserCallback parserCallback = new HotseatParserCallback(
-                    hotseatTargetApps, hotseatItems, insertOperations, maxId + 1);
+                    hotseatTargetApps, hotseatItems, insertOperations, maxId + 1, myHotseatCount);
             new HotseatLayoutParser(mContext,
                     parserCallback).loadLayout(null, new ArrayList<Long>());
             mHotseatSize = (int) hotseatItems.keyAt(hotseatItems.size() - 1) + 1;
-        }
-        if (!insertOperations.isEmpty()) {
-            mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY,
-                    insertOperations);
+
+            if (!insertOperations.isEmpty()) {
+                mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY,
+                        insertOperations);
+            }
         }
     }
 
@@ -404,16 +409,18 @@
      */
     private static class HotseatParserCallback implements LayoutParserCallback {
         private final HashSet<String> mExisitingApps;
-        private final LongArrayMap<Intent> mExistingItems;
+        private final LongArrayMap<Object> mExistingItems;
         private final ArrayList<ContentProviderOperation> mOutOps;
+        private final int mRequiredSize;
         private int mStartItemId;
 
         HotseatParserCallback(
-                HashSet<String> existingApps, LongArrayMap<Intent> existingItems,
-                ArrayList<ContentProviderOperation> outOps, int startItemId) {
+                HashSet<String> existingApps, LongArrayMap<Object> existingItems,
+                ArrayList<ContentProviderOperation> outOps, int startItemId, int requiredSize) {
             mExisitingApps = existingApps;
             mExistingItems = existingItems;
             mOutOps = outOps;
+            mRequiredSize = requiredSize;
             mStartItemId = startItemId;
         }
 
@@ -424,6 +431,10 @@
 
         @Override
         public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
+            if (mExistingItems.size() >= mRequiredSize) {
+                // No need to add more items.
+                return 0;
+            }
             Intent intent;
             try {
                 intent = Intent.parseUri(values.getAsString(Favorites.INTENT), 0);
diff --git a/src/com/android/launcher3/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 144b411..5882f21 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -32,8 +32,8 @@
     private final int mHashCode;
 
     public ComponentKey(ComponentName componentName, UserHandleCompat user) {
-        assert (componentName != null);
-        assert (user != null);
+        Preconditions.assertNotNull(componentName);
+        Preconditions.assertNotNull(user);
         this.componentName = componentName;
         this.user = user;
         mHashCode = Arrays.hashCode(new Object[] {componentName, user});
@@ -58,6 +58,8 @@
             componentName = ComponentName.unflattenFromString(componentKeyStr);
             user = UserHandleCompat.myUserHandle();
         }
+        Preconditions.assertNotNull(componentName);
+        Preconditions.assertNotNull(user);
         mHashCode = Arrays.hashCode(new Object[] {componentName, user});
     }
 
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 3760c63..89353e1 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -26,6 +26,12 @@
  */
 public class Preconditions {
 
+    public static void assertNotNull(Object o) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD && o == null) {
+            throw new IllegalStateException();
+        }
+    }
+
     public static void assertWorkerThread() {
         if (ProviderConfig.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) {
             throw new IllegalStateException();