[automerger] Fix bug where mw landscape cell size is calculated incorretly. am: a11380d370

Change-Id: I1484146ffebe52d4f003fad1f1684c638c073b82
diff --git a/go/res/values-az-rAZ/strings.xml b/go/res/values-az-rAZ/strings.xml
deleted file mode 100644
index c4b8cb7..0000000
--- a/go/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Qısayolu seçmək üçün toxunub saxlayın."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Qısayolu seçmək üçün iki dəfə basıb saxlayın və ya fərdi əməliyyatlardan istifadə edin."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Qısayollar"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> qısayolları"</string>
-</resources>
diff --git a/go/res/values-bs-rBA/strings.xml b/go/res/values-bs-rBA/strings.xml
deleted file mode 100644
index 7042468..0000000
--- a/go/res/values-bs-rBA/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Dodirnite i držite da uzmete prečicu."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Dvaput dodirnite i držite da uzmete prečicu ili koristite prilagođene akcije."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Prečice"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"Prečice aplikacije <xliff:g id="NAME">%1$s</xliff:g>"</string>
-</resources>
diff --git a/go/res/values-ka-rGE/strings.xml b/go/res/values-ka-rGE/strings.xml
deleted file mode 100644
index 1b46534..0000000
--- a/go/res/values-ka-rGE/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"შეეხეთ და დააყოვნეთ მალსახმობის ასარჩევად."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"ორმაგად შეეხეთ და გეჭიროთ მალსახმობის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"მალსახმობები"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g>-ის მალსახმობები"</string>
-</resources>
diff --git a/go/res/values-ky-rKG/strings.xml b/go/res/values-ky-rKG/strings.xml
deleted file mode 100644
index 4c7e973..0000000
--- a/go/res/values-ky-rKG/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Кыска жолду тандоо үчүн басып туруңуз."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Кыска жолду тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Кыска жолдор"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> кыска жол"</string>
-</resources>
diff --git a/go/res/values-lo-rLA/strings.xml b/go/res/values-lo-rLA/strings.xml
deleted file mode 100644
index 7864884..0000000
--- a/go/res/values-lo-rLA/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"ແຕະຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ ຫຼື ໃຊ້ຄຳສັ່ງແບບກຳນົດເອງ."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"ປຸ່ມລັດ"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"ປຸ່ມລັດ <xliff:g id="NAME">%1$s</xliff:g>"</string>
-</resources>
diff --git a/go/res/values-si-rLK/strings.xml b/go/res/values-si-rLK/strings.xml
deleted file mode 100644
index 4b25c90..0000000
--- a/go/res/values-si-rLK/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"කෙටි මගක් තෝරා ගැනීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"විජට් එකක් තෝරා ගැනීමට හෝ අභිරුචි භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"කෙටි මං"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"කෙටි මං <xliff:g id="NAME">%1$s</xliff:g>"</string>
-</resources>
diff --git a/go/res/values-uz-rUZ/strings.xml b/go/res/values-uz-rUZ/strings.xml
deleted file mode 100644
index 318bc15..0000000
--- a/go/res/values-uz-rUZ/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="long_press_widget_to_add" msgid="4001616142797446267">"Yorliqni tanlab olish uchun bosib turing."</string>
-    <string name="long_accessible_way_to_add" msgid="2725225828389948328">"Ikki marta bosib va bosib turgan holatda yorliqni tanlang yoki maxsus amaldan foydalaning."</string>
-    <string name="widget_button_text" msgid="4221900832360456858">"Yorliqlar"</string>
-    <string name="widgets_bottom_sheet_title" msgid="3949835990909395998">"<xliff:g id="NAME">%1$s</xliff:g> ilovasi yorliqlari"</string>
-</resources>
diff --git a/go/res/values/override.xml b/go/res/values/override.xml
new file mode 100644
index 0000000..268cb98
--- /dev/null
+++ b/go/res/values/override.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2017 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+<resources>
+    <!-- String representing the intent to delete a package. -->
+    <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;B.android.intent.extra.RETURN_RESULT=true;end</string>
+</resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index e7ec0a0..b41172b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -9,7 +9,7 @@
     <bool name="is_large_tablet">false</bool>
     <bool name="allow_rotation">false</bool>
 
-    <integer name="extracted_color_gradient_alpha">191</integer>
+    <integer name="extracted_color_gradient_alpha">153</integer>
 
     <!-- A string pointer to the original app name string. This allows derived projects to
      easily override the app name without providing all translations -->
@@ -19,6 +19,9 @@
     q=<query> to the data to the intent -->
     <string name="market_search_intent" translatable="false">market://search?c=apps</string>
 
+    <!-- String representing the intent to delete a package.-->
+    <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
+
     <!-- Values for icon shape overrides. These should correspond to entries defined
      in icon_shape_override_paths_names -->
     <string-array translatable="false" name="icon_shape_override_paths_values">
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5bdf512..17bc7f9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -29,7 +29,7 @@
     </style>
 
     <style name="BaseLauncherThemeWithCustomAttrs" parent="@style/BaseLauncherTheme">
-        <item name="allAppsScrimColor">#DDFFFFFF</item>
+        <item name="allAppsScrimColor">#CCFFFFFF</item>
         <item name="popupColorPrimary">#FFF</item>
         <item name="popupColorSecondary">#F5F5F5</item> <!-- Gray 100 -->
         <item name="popupColorTertiary">#E0E0E0</item> <!-- Gray 300 -->
@@ -61,7 +61,7 @@
         <item name="android:textColorHint">#A0FFFFFF</item>
         <item name="android:colorControlHighlight">#A0FFFFFF</item>
         <item name="android:colorPrimary">#FF333333</item>
-        <item name="allAppsScrimColor">#33000000</item>
+        <item name="allAppsScrimColor">#7A000000</item>
         <item name="popupColorPrimary">?android:attr/colorPrimary</item>
         <item name="popupColorSecondary">#424242</item> <!-- Gray 800 -->
         <item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 1a405f9..a486a3a 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -55,7 +55,6 @@
 
     private final int[] mDirectionVector = new int[2];
     private final int[] mLastDirectionVector = new int[2];
-    private final int[] mTmpPt = new int[2];
 
     private final IntRange mTempRange1 = new IntRange();
     private final IntRange mTempRange2 = new IntRange();
@@ -344,13 +343,12 @@
         return rect;
     }
 
-    /**
-     * This is the final step of the resize. Here we save the new widget size and position
-     * to LauncherModel and animate the resize frame.
-     */
-    public void commitResize() {
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // We are done with resizing the widget. Save the widget size & position to LauncherModel
         resizeWidgetIfNeeded(true);
-        requestLayout();
     }
 
     private void onTouchUp() {
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index e15cf9f..84d6a9b 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -11,12 +11,17 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.widget.Toast;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 
+import java.net.URISyntaxException;
+
 public class UninstallDropTarget extends ButtonDropTarget {
 
+    private static final String TAG = "UninstallDropTarget";
+
     public UninstallDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -99,25 +104,28 @@
             final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
         final ComponentName cn = getUninstallTarget(launcher, info);
 
-        final boolean isUninstallable;
+        boolean canUninstall;
         if (cn == null) {
             // System applications cannot be installed. For now, show a toast explaining that.
             // We may give them the option of disabling apps this way.
             Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
-            isUninstallable = false;
+            canUninstall = false;
         } else {
-            Intent intent = new Intent(Intent.ACTION_DELETE,
-                    Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            intent.putExtra(Intent.EXTRA_USER, info.user);
-            launcher.startActivity(intent);
-            isUninstallable = true;
+            try {
+                Intent i = Intent.parseUri(launcher.getString(R.string.delete_package_intent), 0)
+                        .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
+                        .putExtra(Intent.EXTRA_USER, info.user);
+                launcher.startActivity(i);
+                canUninstall = true;
+            } catch (URISyntaxException e) {
+                Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
+                canUninstall = false;
+            }
         }
         if (callback != null) {
-            sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
+            sendUninstallResult(launcher, canUninstall, cn, info.user, callback);
         }
-        return isUninstallable;
+        return canUninstall;
     }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f8d6498..bdeed4c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -84,7 +84,6 @@
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.VerticalFlingDetector;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -571,33 +570,6 @@
         }
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
-        if (FeatureFlags.PULLDOWN_SEARCH) {
-            firstPage.setOnTouchListener(new VerticalFlingDetector(mLauncher) {
-                // detect fling when touch started from empty space
-                @Override
-                public boolean onTouch(View v, MotionEvent ev) {
-                    if (workspaceInModalState()) return false;
-                    if (shouldConsumeTouch(v)) return true;
-                    if (super.onTouch(v, ev)) {
-                        mLauncher.startSearch("", false, null, false);
-                        return true;
-                    }
-                    return false;
-                }
-            });
-            firstPage.setOnInterceptTouchListener(new VerticalFlingDetector(mLauncher) {
-                // detect fling when touch started from on top of the icons
-                @Override
-                public boolean onTouch(View v, MotionEvent ev) {
-                    if (shouldConsumeTouch(v)) return true;
-                    if (super.onTouch(v, ev)) {
-                        mLauncher.startSearch("", false, null, false);
-                        return true;
-                    }
-                    return false;
-                }
-            });
-        }
         // Always add a QSB on the first screen.
         if (qsb == null) {
             // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 6a4cbcb..c88359b 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -52,8 +52,6 @@
     public static final boolean QSB_ON_FIRST_SCREEN = true;
     // When enabled the all-apps icon is not added to the hotseat.
     public static final boolean NO_ALL_APPS_ICON = true;
-    // When enabled fling down gesture on the first workspace triggers search.
-    public static final boolean PULLDOWN_SEARCH = false;
     // When enabled the status bar may show dark icons based on the top of the wallpaper.
     public static final boolean LIGHT_STATUS_BAR = false;
     // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index ee6a0e0..fde7995 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -544,7 +544,6 @@
 
     public void clearResizeFrame() {
         if (mCurrentResizeFrame != null) {
-            mCurrentResizeFrame.commitResize();
             removeView(mCurrentResizeFrame);
             mCurrentResizeFrame = null;
         }
diff --git a/src/com/android/launcher3/util/VerticalFlingDetector.java b/src/com/android/launcher3/util/VerticalFlingDetector.java
deleted file mode 100644
index 7236c2d..0000000
--- a/src/com/android/launcher3/util/VerticalFlingDetector.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-public class VerticalFlingDetector implements View.OnTouchListener {
-
-    private static final float CUSTOM_SLOP_MULTIPLIER = 2.2f;
-    private static final int SEC_IN_MILLIS = 1000;
-
-    private VelocityTracker mVelocityTracker;
-    private float mMinimumFlingVelocity;
-    private float mMaximumFlingVelocity;
-    private float mDownX, mDownY;
-    private boolean mShouldCheckFling;
-    private double mCustomTouchSlop;
-
-    public VerticalFlingDetector(Context context) {
-        ViewConfiguration vc = ViewConfiguration.get(context);
-        mMinimumFlingVelocity = vc.getScaledMinimumFlingVelocity();
-        mMaximumFlingVelocity = vc.getScaledMaximumFlingVelocity();
-        mCustomTouchSlop = CUSTOM_SLOP_MULTIPLIER * vc.getScaledTouchSlop();
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mDownX = ev.getX();
-                mDownY = ev.getY();
-                mShouldCheckFling = false;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (mShouldCheckFling) {
-                    break;
-                }
-                if (Math.abs(ev.getY() - mDownY) > mCustomTouchSlop &&
-                        Math.abs(ev.getY() - mDownY) > Math.abs(ev.getX() - mDownX)) {
-                    mShouldCheckFling = true;
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mShouldCheckFling) {
-                    mVelocityTracker.computeCurrentVelocity(SEC_IN_MILLIS, mMaximumFlingVelocity);
-                    // only when fling is detected in down direction
-                    if (mVelocityTracker.getYVelocity() > mMinimumFlingVelocity) {
-                        cleanUp();
-                        return true;
-                    }
-                }
-                // fall through.
-            case MotionEvent.ACTION_CANCEL:
-                cleanUp();
-        }
-        return false;
-    }
-
-    private void cleanUp() {
-        if (mVelocityTracker == null) {
-            return;
-        }
-        mVelocityTracker.recycle();
-        mVelocityTracker = null;
-    }
-}
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
index 3e89eeb..335b8c7 100644
--- a/src/com/android/launcher3/widget/WidgetListRowEntry.java
+++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java
@@ -41,4 +41,8 @@
         this.widgets = items;
     }
 
+    @Override
+    public String toString() {
+        return pkgItem.packageName + ":" + widgets.size();
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 14a9d17..acec3dd 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -17,10 +17,12 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.graphics.Point;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Toast;
@@ -31,8 +33,10 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.model.PackageItemInfo;
@@ -74,7 +78,11 @@
     public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mAdapter = new WidgetsListAdapter(this, this, context);
+        LauncherAppState apps = LauncherAppState.getInstance(context);
+        mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context),
+                apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this,
+                new WidgetsDiffReporter(apps.getIconCache()));
+        mAdapter.setNotifyListener();
         if (LOGD) {
             Log.d(TAG, "WidgetsContainerView constructor");
         }
@@ -232,7 +240,6 @@
      */
     public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
         mAdapter.setWidgets(model);
-        mAdapter.notifyDataSetChanged();
 
         View loader = getContentView().findViewById(R.id.loader);
         if (loader != null) {
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
new file mode 100644
index 0000000..d9c9ef9
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.util.Log;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly.
+ */
+public class WidgetsDiffReporter {
+    private final boolean DEBUG = true;
+    private final String TAG = "WidgetsDiffReporter";
+    private final IconCache mIconCache;
+    private NotifyListener mListener;
+
+    public interface NotifyListener {
+        void notifyDataSetChanged();
+        void notifyItemChanged(int index);
+        void notifyItemInserted(int index);
+        void notifyItemRemoved(int index);
+    }
+
+    public WidgetsDiffReporter(IconCache iconCache) {
+        mIconCache = iconCache;
+    }
+
+    public void setListener(NotifyListener listener) {
+        mListener = listener;
+    }
+
+    public void process(ArrayList<WidgetListRowEntry> currentEntries,
+            ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) {
+        if (DEBUG) {
+            Log.d(TAG, "process oldEntries#=" + currentEntries.size()
+                    + " newEntries#=" + newEntries.size());
+        }
+        if (currentEntries.size() == 0 && newEntries.size() > 0) {
+            currentEntries.addAll(newEntries);
+            mListener.notifyDataSetChanged();
+            return;
+        }
+        ArrayList<WidgetListRowEntry> orgEntries =
+                (ArrayList<WidgetListRowEntry>) currentEntries.clone();
+        Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator();
+        Iterator<WidgetListRowEntry> newIter = newEntries.iterator();
+
+        WidgetListRowEntry orgRowEntry = orgIter.next();
+        WidgetListRowEntry newRowEntry = newIter.next();
+
+        do {
+            int diff = comparePackageName(orgRowEntry, newRowEntry, comparator);
+            if (DEBUG) {
+                Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
+                        diff, orgRowEntry != null? orgRowEntry.toString() : null,
+                        newRowEntry != null? newRowEntry.toString() : null));
+            }
+            int index = -1;
+            if (diff < 0) {
+                index = currentEntries.indexOf(orgRowEntry);
+                mListener.notifyItemRemoved(index);
+                if (DEBUG) {
+                    Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
+                            orgRowEntry.titleSectionName));
+                }
+                currentEntries.remove(index);
+                orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
+            } else if (diff > 0) {
+                index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry):
+                        currentEntries.size();
+                currentEntries.add(index, newRowEntry);
+                newRowEntry = newIter.hasNext() ? newIter.next() : null;
+                mListener.notifyItemInserted(index);
+                if (DEBUG) {
+                    Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
+                            newRowEntry.titleSectionName));
+                }
+            } else {
+                // same package name but,
+                // did the icon, title, etc, change?
+                // or did the widget size and desc, span, etc change?
+                if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) ||
+                        !orgRowEntry.widgets.equals(newRowEntry.widgets)) {
+                    index = currentEntries.indexOf(orgRowEntry);
+                    currentEntries.set(index, newRowEntry);
+                    mListener.notifyItemChanged(index);
+                    if (DEBUG) {
+                        Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
+                                newRowEntry.titleSectionName));
+                    }
+                }
+                orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
+                newRowEntry = newIter.hasNext() ? newIter.next() : null;
+            }
+        } while(orgRowEntry != null || newRowEntry != null);
+    }
+
+    /**
+     * Compare package name using the same comparator as in {@link WidgetsListAdapter}.
+     * Also handle null row pointers.
+     */
+    private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow,
+            WidgetListRowEntryComparator comparator) {
+        if (curRow == null && newRow == null) {
+            throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null.");
+        }
+
+        if (curRow == null && newRow != null) {
+            return 1; // new row needs to be inserted
+        } else if (curRow != null && newRow == null) {
+            return -1; // old row needs to be deleted
+        }
+        return comparator.compare(curRow, newRow);
+    }
+
+    private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
+        return curInfo.iconBitmap.equals(newInfo.iconBitmap) &&
+                !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user);
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index a1eb0ab..6b1800c 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -21,9 +21,10 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -55,40 +56,67 @@
 
     private final WidgetPreviewLoader mWidgetPreviewLoader;
     private final LayoutInflater mLayoutInflater;
-
-    private final View.OnClickListener mIconClickListener;
-    private final View.OnLongClickListener mIconLongClickListener;
-
-    private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
     private final AlphabeticIndexCompat mIndexer;
 
+    private final OnClickListener mIconClickListener;
+    private final OnLongClickListener mIconLongClickListener;
     private final int mIndent;
+    private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
+    private final WidgetsDiffReporter mDiffReporter;
 
-    public WidgetsListAdapter(View.OnClickListener iconClickListener,
-            View.OnLongClickListener iconLongClickListener,
-            Context context) {
-        mLayoutInflater = LayoutInflater.from(context);
-        mWidgetPreviewLoader = LauncherAppState.getInstance(context).getWidgetCache();
-
-        mIndexer = new AlphabeticIndexCompat(context);
-
+    public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
+            WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat,
+            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
+            WidgetsDiffReporter diffReporter) {
+        mLayoutInflater = layoutInflater;
+        mWidgetPreviewLoader = widgetPreviewLoader;
+        mIndexer = indexCompat;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
+        mDiffReporter = diffReporter;
     }
 
-    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
-        mEntries.clear();
-        WidgetItemComparator widgetComparator = new WidgetItemComparator();
+    public void setNotifyListener() {
+        mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() {
+            @Override
+            public void notifyDataSetChanged() {
+                WidgetsListAdapter.this.notifyDataSetChanged();
+            }
 
+            @Override
+            public void notifyItemChanged(int index) {
+                WidgetsListAdapter.this.notifyItemChanged(index);
+            }
+
+            @Override
+            public void notifyItemInserted(int index) {
+                WidgetsListAdapter.this.notifyItemInserted(index);
+            }
+
+            @Override
+            public void notifyItemRemoved(int index) {
+                WidgetsListAdapter.this.notifyItemRemoved(index);
+            }
+        });
+    }
+
+    /**
+     * Update the widget list.
+     */
+    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
+        ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>();
+
+        WidgetItemComparator widgetComparator = new WidgetItemComparator();
         for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
             WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
             row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
             Collections.sort(row.widgets, widgetComparator);
-            mEntries.add(row);
+            tempEntries.add(row);
         }
-
-        Collections.sort(mEntries, new WidgetListRowEntryComparator());
+        WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
+        Collections.sort(tempEntries, rowComparator);
+        mDiffReporter.process(mEntries, tempEntries, rowComparator);
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
new file mode 100644
index 0000000..40b65e4
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.MultiHashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WidgetsListAdapterTest {
+
+    private final String TAG = "WidgetsListAdapterTest";
+
+    @Mock private LayoutInflater mMockLayoutInflater;
+    @Mock private WidgetPreviewLoader mMockWidgetCache;
+    @Mock private WidgetsDiffReporter.NotifyListener mListener;
+    @Mock private IconCache mIconCache;
+
+    private WidgetsListAdapter mAdapter;
+    private AlphabeticIndexCompat mIndexCompat;
+    private InvariantDeviceProfile mTestProfile;
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getTargetContext();
+        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile.numRows = 5;
+        mTestProfile.numColumns = 5;
+        mIndexCompat = new AlphabeticIndexCompat(mContext);
+        WidgetsDiffReporter reporter = new WidgetsDiffReporter(mIconCache);
+        reporter.setListener(mListener);
+        mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
+                mIndexCompat, null, null, reporter);
+    }
+
+    @Test
+    public void test_notifyDataSetChanged() throws Exception {
+        mAdapter.setWidgets(generateSampleMap(1));
+        verify(mListener, times(1)).notifyDataSetChanged();
+    }
+
+    @Test
+    public void test_notifyItemInserted() throws Exception {
+        mAdapter.setWidgets(generateSampleMap(1));
+        mAdapter.setWidgets(generateSampleMap(2));
+        verify(mListener, times(1)).notifyDataSetChanged();
+        verify(mListener, times(1)).notifyItemInserted(1);
+    }
+
+    @Test
+    public void test_notifyItemRemoved() throws Exception {
+        mAdapter.setWidgets(generateSampleMap(2));
+        mAdapter.setWidgets(generateSampleMap(1));
+        verify(mListener, times(1)).notifyDataSetChanged();
+        verify(mListener, times(1)).notifyItemRemoved(1);
+    }
+
+    @Test
+    public void testNotifyItemChanged_PackageIconDiff() throws Exception {
+        mAdapter.setWidgets(generateSampleMap(1));
+        mAdapter.setWidgets(generateSampleMap(1));
+        verify(mListener, times(1)).notifyDataSetChanged();
+        verify(mListener, times(1)).notifyItemChanged(0);
+    }
+
+    @Test
+    public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception {
+        // TODO: same package name but item number changed
+    }
+
+    @Test
+    public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception {
+        // TODO: insert and remove combined.          curMap
+        // newMap [A, C, D]                           [A, B, E]
+        // B - C < 0, removed B from index 1          [A, E]
+        // E - C > 0, C inserted to index 1           [A, C, E]
+        // E - D > 0, D inserted to index 2           [A, C, D, E]
+        // E - null = -1, E deleted from index 3      [A, C, D]
+    }
+
+    /**
+     * Helper method to generate the sample widget model map that can be used for the tests
+     * @param num the number of WidgetItem the map should contain
+     * @return
+     */
+    private MultiHashMap<PackageItemInfo, WidgetItem> generateSampleMap(int num) {
+        MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
+        if (num <= 0) return newMap;
+
+        PackageManager pm = mContext.getPackageManager();
+        AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
+        for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) {
+            WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
+                    .fromProviderInfo(mContext, widgetInfo), pm, mTestProfile);
+
+            PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
+            pInfo.title = pInfo.packageName;
+            pInfo.user = wi.user;
+            pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8);
+            newMap.addToList(pInfo, wi);
+            if (newMap.size() == num) {
+                break;
+            }
+        }
+        return newMap;
+    }
+}