Merge "Do not hold synchronization locks if tracing is not enabled" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 71f638f..d7c16e6 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -132,16 +132,6 @@
             </intent-filter>
         </activity>
 
-        <!-- TODO: Remove -->
-        <activity android:name="com.android.launcher3.util.MyActivity"
-            android:theme="@style/AppTheme">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <!--
         Should point to the content provider which can be used to dump Launcher3 compatible
         worspace configuration to the dump's file descriptor by using launcher_dump.proto
diff --git a/SecondaryDisplayLauncher/res/values-as/strings.xml b/SecondaryDisplayLauncher/res/values-as/strings.xml
new file mode 100644
index 0000000..5e4f690
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-as/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"কাৰ্যকলাপটো লঞ্চ কৰিব পৰা নগ’ল"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"এপৰ শ্বর্টকাট যোগ কৰক"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"ৱালপেপাৰ ছেট কৰক"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bn/strings.xml b/SecondaryDisplayLauncher/res/values-bn/strings.xml
new file mode 100644
index 0000000..5500754
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-bn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"অ্যাক্টিভিটি চালু করা যায়নি"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"অ্যাপ শর্টকাট যোগ করুন"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"ওয়ালপেপার সেট করুন"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bs/strings.xml b/SecondaryDisplayLauncher/res/values-bs/strings.xml
index 2d700fd..7d2f011 100644
--- a/SecondaryDisplayLauncher/res/values-bs/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-bs/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="8116646702313548897">"Pokretač za sekundarni ekran"</string>
-    <string name="couldnt_launch" msgid="7873588052226763866">"Nije bilo moguće pokrenuti aktivnost"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspjelo"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj prečicu aplikacije"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Postavi pozadinsku sliku"</string>
 </resources>
diff --git a/SecondaryDisplayLauncher/res/values-gu/strings.xml b/SecondaryDisplayLauncher/res/values-gu/strings.xml
new file mode 100644
index 0000000..f13422e
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-gu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"પ્રવૃત્તિ લૉન્ચ કરી શકાઈ નથી"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"ઍપ શૉર્ટકટ ઉમેરો"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"વૉલપેપર સેટ કરો"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-iw/strings.xml b/SecondaryDisplayLauncher/res/values-iw/strings.xml
new file mode 100644
index 0000000..f904c38
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-iw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"לא ניתן היה להפעיל את הפעילות"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"הוספת קיצור דרך של אפליקציה"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"הגדרת טפט"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-kk/strings.xml b/SecondaryDisplayLauncher/res/values-kk/strings.xml
new file mode 100644
index 0000000..a679b09
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-kk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"Әрекет іске қосылмады"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"Қолданба таңбашасын енгізу"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"Тұсқағаз орнату"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-kn/strings.xml b/SecondaryDisplayLauncher/res/values-kn/strings.xml
new file mode 100644
index 0000000..aa0ccc1
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-kn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"ಚಟುವಟಿಕೆಯನ್ನು ಲಾಂಚ್‌ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"ಆ್ಯಪ್‌ ಶಾರ್ಟ್‌ಕಟ್ ಸೇರಿಸಿ"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"ವಾಲ್‌ಪೇಪರ್ ಹೊಂದಿಸಿ"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-mr/strings.xml b/SecondaryDisplayLauncher/res/values-mr/strings.xml
new file mode 100644
index 0000000..d54cc89
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-mr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"अ‍ॅक्टिव्हिटी लाँच करता आली नाही"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"अ‍ॅप शॉर्टकट जोडा"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"वॉलपेपर सेट करा"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ne/strings.xml b/SecondaryDisplayLauncher/res/values-ne/strings.xml
new file mode 100644
index 0000000..d40d806
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-ne/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"उक्त क्रियाकलाप सुरु गर्न सकिएन"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"अनुप्रयोगको सर्टकट थप्नुहोस्‌"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"वालपेपर सेट गर्नुहोस्"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-or/strings.xml b/SecondaryDisplayLauncher/res/values-or/strings.xml
new file mode 100644
index 0000000..6f04f99
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-or/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"ଗତିବିଧିକୁ ଲଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"ଆପ୍‌ ସର୍ଟକଟ୍‌ ଯୋଗ କରନ୍ତୁ"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"ୱାଲ୍‌‌ପେପର୍‌କୁ ସେଟ୍ କରନ୍ତୁ"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ta/strings.xml b/SecondaryDisplayLauncher/res/values-ta/strings.xml
new file mode 100644
index 0000000..e2cfb4b
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-ta/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"நடவடிக்கையைத் துவக்க இயலவில்லை"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"ஆப்ஸ் குறுக்குவழியைச் சேர்"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"வால்பேப்பரை அமை"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-te/strings.xml b/SecondaryDisplayLauncher/res/values-te/strings.xml
new file mode 100644
index 0000000..f204e2a
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-te/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2018 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="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
+    <string name="couldnt_launch" msgid="7873588052226763866">"కార్యకలాపాన్ని ప్రారంభించడం సాధ్యం కాలేదు"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"యాప్ షార్ట్‌కట్‌ని జోడించు"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"వాల్‌పేపర్‌ను సెట్ చేయండి"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values/strings.xml b/SecondaryDisplayLauncher/res/values/strings.xml
index 0f906c4..b68918a 100644
--- a/SecondaryDisplayLauncher/res/values/strings.xml
+++ b/SecondaryDisplayLauncher/res/values/strings.xml
@@ -18,7 +18,6 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name">SecondaryDisplayLauncher</string>
     <string name="couldnt_launch">Couldn\'t launch the activity</string>
     <string name="add_app_shortcut">Add app shortcut</string>
     <string name="set_wallpaper">Set wallpaper</string>
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index f60572c..4646fd7f 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -45,7 +45,6 @@
 import android.os.Looper;
 import android.view.View;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -194,7 +193,7 @@
             int topMargin = context.getResources()
                     .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
             int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top;
-            int paddingBottom = dp.availableHeightPx + dp.getInsets().top - targetRect.rect.bottom;
+            int paddingBottom = dp.heightPx - dp.getInsets().bottom - targetRect.rect.bottom;
 
             return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop);
         }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index fec38bf..cedd952 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,8 +16,10 @@
 
 package com.android.quickstep;
 
+import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.os.Build;
 import android.os.Process;
 import android.util.SparseBooleanArray;
 import com.android.launcher3.MainThreadExecutor;
@@ -36,16 +38,20 @@
 /**
  * Manages the recent task list from the system, caching it as necessary.
  */
+@TargetApi(Build.VERSION_CODES.P)
 public class RecentTasksList extends TaskStackChangeListener {
 
     private final KeyguardManagerCompat mKeyguardManager;
     private final MainThreadExecutor mMainThreadExecutor;
     private final BackgroundExecutor mBgThreadExecutor;
+    private final TaskListStabilizer mStabilizer = new TaskListStabilizer();
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
     // The last change id when the list was last loaded completely, must be <= the list change id
     private int mLastLoadedId;
+    // The last change id was loaded with keysOnly  = true
+    private boolean mLastLoadHadKeysOnly;
 
     ArrayList<Task> mTasks = new ArrayList<>();
 
@@ -57,41 +63,43 @@
     }
 
     /**
-     * Asynchronously fetches the list of recent tasks.
+     * Fetches the task keys skipping any local cache.
+     */
+    public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
+        // Kick off task loading in the background
+        mBgThreadExecutor.submit(() -> {
+            ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
+            mMainThreadExecutor.execute(() -> callback.accept(tasks));
+        });
+    }
+
+    /**
+     * Asynchronously fetches the list of recent tasks, reusing cached list if available.
      *
-     * @param numTasks The maximum number of tasks to fetch
      * @param loadKeysOnly Whether to load other associated task data, or just the key
      * @param callback The callback to receive the list of recent tasks
      * @return The change id of the current task list
      */
-    public synchronized int getTasks(int numTasks, boolean loadKeysOnly,
-            Consumer<ArrayList<Task>> callback) {
+    public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
         final int requestLoadId = mChangeId;
-        final int numLoadTasks = numTasks > 0
-                ? numTasks
-                : Integer.MAX_VALUE;
+        Runnable resultCallback = callback == null
+                ? () -> { }
+                : () -> callback.accept(mStabilizer.reorder(mTasks));
 
-        if (mLastLoadedId == mChangeId) {
+        if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
             // The list is up to date, callback with the same list
-            mMainThreadExecutor.execute(() -> {
-                if (callback != null) {
-                    callback.accept(mTasks);
-                }
-            });
+            mMainThreadExecutor.execute(resultCallback);
         }
 
         // Kick off task loading in the background
         mBgThreadExecutor.submit(() -> {
-            ArrayList<Task> tasks = loadTasksInBackground(numLoadTasks,
-                    loadKeysOnly);
+            ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
 
             mMainThreadExecutor.execute(() -> {
                 mTasks = tasks;
                 mLastLoadedId = requestLoadId;
-
-                if (callback != null) {
-                    callback.accept(tasks);
-                }
+                mLastLoadHadKeysOnly = loadKeysOnly;
+                resultCallback.run();
             });
         });
 
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index a62e6ad..75ccba6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -98,7 +98,7 @@
      * @return the request id associated with this call.
      */
     public int getTasks(Consumer<ArrayList<Task>> callback) {
-        return mTaskList.getTasks(-1, false /* loadKeysOnly */, callback);
+        return mTaskList.getTasks(false /* loadKeysOnly */, callback);
     }
 
     /**
@@ -124,7 +124,7 @@
      *                 called on the UI thread.
      */
     public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) {
-        mTaskList.getTasks(-1, true /* loadKeysOnly */, (tasks) -> {
+        mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
             for (Task task : tasks) {
                 if (task.key.id == taskId) {
                     callback.accept(task.key);
@@ -150,7 +150,7 @@
 
         // Keep the cache up to date with the latest thumbnails
         int runningTaskId = RecentsModel.getRunningTaskId();
-        mTaskList.getTasks(mThumbnailCache.getCacheSize(), true /* keysOnly */, (tasks) -> {
+        mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
             for (Task task : tasks) {
                 if (task.key.id == runningTaskId) {
                     // Skip the running task, it's not going to have an up-to-date snapshot by the
diff --git a/quickstep/src/com/android/quickstep/TaskListStabilizer.java b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
new file mode 100644
index 0000000..0d23973
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 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.quickstep;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER;
+
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
+public class TaskListStabilizer {
+
+    private static final long TASK_CACHE_TIMEOUT_MS = 5000;
+
+    private final SparseArray<Task> mTempMap = new SparseArray<>();
+    private final IntArray mTempArray = new IntArray();
+    private final IntSet mTempSet = new IntSet();
+
+    private final IntArray mLastStableOrder = new IntArray();
+    private final IntSet mLastSet = new IntSet();
+    private final IntArray mLastUnstableOrder = new IntArray();
+
+    private long mLastReorderTime;
+
+    public ArrayList<Task> reorder(ArrayList<Task> tasks) {
+        if (!ENABLE_TASK_STABILIZER.get()) {
+            return tasks;
+        }
+
+        // Create task id array
+        int count = tasks.size();
+        mTempArray.clear();
+        mTempSet.clear();
+        mTempMap.clear();
+
+        for (int i = 0; i < count; i++) {
+            Task t = tasks.get(i);
+            mTempMap.put(t.key.id, t);
+
+            mTempSet.add(t.key.id);
+            mTempArray.add(t.key.id);
+        }
+
+        if (mLastSet.equals(mTempSet) && isStabilizationQuickEnough()) {
+            if (mLastStableOrder.equals(mTempArray)) {
+                // Everything is same
+                return tasks;
+            }
+
+            if (!mLastUnstableOrder.equals(mTempArray)) {
+                // Fast reordering, record the current time.
+                mLastUnstableOrder.copyFrom(mTempArray);
+                mLastReorderTime = SystemClock.uptimeMillis();
+            }
+
+            // Reorder the tasks based on the last stable order.
+            ArrayList<Task> sorted = new ArrayList<>(count);
+            for (int i = 0; i < count; i++) {
+                sorted.add(mTempMap.get(mLastStableOrder.get(i)));
+            }
+            return sorted;
+        }
+
+        // Cache the data
+        mLastStableOrder.copyFrom(mTempArray);
+        mLastUnstableOrder.copyFrom(mTempArray);
+        mLastSet.copyFrom(mTempSet);
+
+        mLastReorderTime = SystemClock.uptimeMillis();
+
+        return tasks;
+    }
+
+    private boolean isStabilizationQuickEnough() {
+        return (SystemClock.uptimeMillis() - mLastReorderTime) < TASK_CACHE_TIMEOUT_MS;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index e1ffcf0..686b52b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -27,6 +27,7 @@
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -1028,10 +1029,17 @@
 
         mRecentsView.animateUpRunningTaskIconScale();
         if (mQuickScrubController.isQuickSwitch()) {
+            // Adjust the running task so that it is centered and fills the screen.
             TaskView runningTask = mRecentsView.getRunningTaskView();
             if (runningTask != null) {
-                runningTask.setTranslationY(-mActivity.getResources().getDimension(
-                        R.dimen.task_thumbnail_half_top_margin) * 1f / mRecentsView.getScaleX());
+                float insetHeight = mDp.heightPx - mDp.getInsets().top - mDp.getInsets().bottom;
+                // Usually insetDiff will be 0, unless we allow apps to draw under the insets. In
+                // that case (insetDiff != 0), we need to center in the system-specified available
+                // height rather than launcher's inset height by adding half the insetDiff.
+                float insetDiff = mDp.availableHeightPx - insetHeight;
+                float topMargin = mActivity.getResources().getDimension(
+                        R.dimen.task_thumbnail_half_top_margin);
+                runningTask.setTranslationY((insetDiff / 2 - topMargin) / mRecentsView.getScaleX());
             }
         }
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 898de97..d8a3282 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -329,13 +329,14 @@
         canvas.concat(mTmpMatrix);
         canvas.translate(mTargetRect.left, mTargetRect.top);
 
+        float scale = mTargetRect.width() / mSourceRect.width();
         float insetProgress = (1 - progress);
         ttv.drawOnCanvas(canvas,
                 -mSourceWindowClipInsets.left * insetProgress,
                 -mSourceWindowClipInsets.top * insetProgress,
                 ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
                 ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
-                Utilities.mapRange(progress, mWindowCornerRadius, ttv.getCornerRadius()));
+                Utilities.mapRange(progress, mWindowCornerRadius * scale, ttv.getCornerRadius()));
     }
 
     public RectF getTargetRect() {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7842fa4..6861bc1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -60,6 +61,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ListView;
 import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
@@ -545,8 +547,8 @@
         // Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub.
         mTempRect.top -= mTaskTopMargin;
         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
-                dp.availableWidthPx + mInsets.left - mTempRect.right,
-                dp.availableHeightPx + mInsets.top - mTempRect.bottom);
+                dp.widthPx - mInsets.right - mTempRect.right,
+                dp.heightPx - mInsets.bottom - mTempRect.bottom);
     }
 
     protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
@@ -749,7 +751,8 @@
         setRunningTaskIconScaledDown(runningTaskIconScaledDown);
         setRunningTaskHidden(runningTaskTileHidden);
 
-        setCurrentPage(0);
+        TaskView tv = getRunningTaskView();
+        setCurrentPage(tv == null ? 0 : indexOfChild(tv));
 
         // Load the tasks (if the loading is already
         mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
index e810ab2..8fb408b 100644
--- a/res/values-v26/styles.xml
+++ b/res/values-v26/styles.xml
@@ -19,6 +19,7 @@
 <resources>
     <!-- Theme for the widget container. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:colorPrimaryDark">#E8EAED</item>
         <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
     <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java
new file mode 100644
index 0000000..07834fc
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java
@@ -0,0 +1,121 @@
+package com.android.launcher3.model;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.junit.Before;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLog;
+
+public abstract class BaseGridChangesTestCase {
+
+
+    public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+    public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+
+    public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+    public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+    public static final int NO__ICON = -1;
+
+    public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
+
+    public Context mContext;
+    public TestLauncherProvider mProvider;
+    public SQLiteDatabase mDb;
+
+    @Before
+    public void setUpBaseCase() {
+        ShadowLog.stream = System.out;
+
+        mContext = RuntimeEnvironment.application;
+        mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+        ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
+        mDb = mProvider.getDb();
+    }
+
+    /**
+     * Adds a dummy item in the DB.
+     * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
+     *             folder (where the type represents the number of items in the folder).
+     */
+    public int addItem(int type, int screen, int container, int x, int y) {
+        int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
+                LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+
+        ContentValues values = new ContentValues();
+        values.put(LauncherSettings.Favorites._ID, id);
+        values.put(LauncherSettings.Favorites.CONTAINER, container);
+        values.put(LauncherSettings.Favorites.SCREEN, screen);
+        values.put(LauncherSettings.Favorites.CELLX, x);
+        values.put(LauncherSettings.Favorites.CELLY, y);
+        values.put(LauncherSettings.Favorites.SPANX, 1);
+        values.put(LauncherSettings.Favorites.SPANY, 1);
+
+        if (type == APP_ICON || type == SHORTCUT) {
+            values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
+            values.put(LauncherSettings.Favorites.INTENT,
+                    new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
+        } else {
+            values.put(LauncherSettings.Favorites.ITEM_TYPE,
+                    LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
+            // Add folder items.
+            for (int i = 0; i < type; i++) {
+                addItem(APP_ICON, 0, id, 0, 0);
+            }
+        }
+
+        mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+        return id;
+    }
+
+    public int[][][] createGrid(int[][][] typeArray) {
+        return createGrid(typeArray, 1);
+    }
+
+    /**
+     * Initializes the DB with dummy elements to represent the provided grid structure.
+     * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
+     *                  type definitions. The first dimension represents the screens and the next
+     *                  two represent the workspace grid.
+     * @param startScreen First screen id from where the icons will be added.
+     * @return the same grid representation where each entry is the corresponding item id.
+     */
+    public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+        LauncherSettings.Settings.call(mContext.getContentResolver(),
+                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+        int[][][] ids = new int[typeArray.length][][];
+
+        for (int i = 0; i < typeArray.length; i++) {
+            // Add screen to DB
+            int screenId = startScreen + i;
+
+            // Keep the screen id counter up to date
+            LauncherSettings.Settings.call(mContext.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+            ids[i] = new int[typeArray[i].length][];
+            for (int y = 0; y < typeArray[i].length; y++) {
+                ids[i][y] = new int[typeArray[i][y].length];
+                for (int x = 0; x < typeArray[i][y].length; x++) {
+                    if (typeArray[i][y][x] < 0) {
+                        // Empty cell
+                        ids[i][y][x] = -1;
+                    } else {
+                        ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
+                    }
+                }
+            }
+        }
+
+        return ids;
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
new file mode 100644
index 0000000..53287a9
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
@@ -0,0 +1,115 @@
+package com.android.launcher3.model;
+
+
+import static android.database.DatabaseUtils.queryNumEntries;
+
+import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentValues;
+import android.graphics.Point;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Unit tests for {@link GridBackupTable}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class GridBackupTableTest extends BaseGridChangesTestCase {
+
+    private static final int BACKUP_ITEM_COUNT = 12;
+
+    @Before
+    public void setupGridData() {
+        createGrid(new int[][][]{{
+                { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
+                { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+                { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+                { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
+        }});
+        assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
+    }
+
+    @Test
+    public void backupTableCreated() {
+        GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 4, 4, 4);
+        assertFalse(backupTable.backupOrRestoreAsNeeded());
+        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+        // One extra entry for properties
+        assertEquals(BACKUP_ITEM_COUNT + 1, queryNumEntries(mDb, BACKUP_TABLE_NAME));
+    }
+
+    @Test
+    public void backupTableRestored() {
+        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+        // Delete entries
+        mDb.delete(TABLE_NAME, null, null);
+        assertEquals(0, queryNumEntries(mDb, TABLE_NAME));
+
+        GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 3, 3, 3);
+        assertTrue(backupTable.backupOrRestoreAsNeeded());
+
+        // Items have been restored
+        assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
+
+        Point outSize = new Point();
+        assertEquals(4, backupTable.getRestoreHotseatAndGridSize(outSize));
+        assertEquals(4, outSize.x);
+        assertEquals(4, outSize.y);
+    }
+
+    @Test
+    public void backupTableRemovedOnAdd() {
+        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+        addItem(1, 2, DESKTOP, 1, 1);
+        assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
+    }
+
+    @Test
+    public void backupTableRemovedOnDelete() {
+        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+        mContext.getContentResolver().delete(Favorites.CONTENT_URI, null, null);
+        assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
+    }
+
+    @Test
+    public void backupTableRetainedOnUpdate() {
+        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
+        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+
+        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+
+        ContentValues values = new ContentValues();
+        values.put(Favorites.RANK, 4);
+        // Something was updated
+        assertTrue(mContext.getContentResolver()
+                .update(Favorites.CONTENT_URI, values, null, null) > 0);
+
+        // Backup table remains
+        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index d4188aa..ce07a27 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -5,29 +5,21 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
 import android.database.Cursor;
 import android.graphics.Point;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.config.FlagOverrideRule;
 import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
 import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.TestLauncherProvider;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowContentResolver;
 
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -36,47 +28,33 @@
  * Unit tests for {@link GridSizeMigrationTask}
  */
 @RunWith(RobolectricTestRunner.class)
-public class GridSizeMigrationTaskTest {
-
-    private static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
-    private static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
-    private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-    private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-
-    private static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
+public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase {
 
     @Rule
     public final FlagOverrideRule flags = new FlagOverrideRule();
 
     private HashSet<String> mValidPackages;
     private InvariantDeviceProfile mIdp;
-    private Context mContext;
-    private TestLauncherProvider mProvider;
 
     @Before
     public void setUp() {
         mValidPackages = new HashSet<>();
         mValidPackages.add(TEST_PACKAGE);
         mIdp = new InvariantDeviceProfile();
-        mContext = RuntimeEnvironment.application;
-
-        mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
-        ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
     }
 
     @Test
     public void testHotseatMigration_apps_dropped() throws Exception {
         int[] hotseatItems = {
-                addItem(APPLICATION, 0, HOTSEAT, 0, 0),
+                addItem(APP_ICON, 0, HOTSEAT, 0, 0),
                 addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
                 -1,
                 addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
-                addItem(APPLICATION, 4, HOTSEAT, 0, 0),
+                addItem(APP_ICON, 4, HOTSEAT, 0, 0),
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
                 .migrateHotseat();
         // First item is dropped as it has the least weight.
         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -85,7 +63,7 @@
     @Test
     public void testHotseatMigration_shortcuts_dropped() throws Exception {
         int[] hotseatItems = {
-                addItem(APPLICATION, 0, HOTSEAT, 0, 0),
+                addItem(APP_ICON, 0, HOTSEAT, 0, 0),
                 addItem(30, 1, HOTSEAT, 0, 0),
                 -1,
                 addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
@@ -93,7 +71,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
                 .migrateHotseat();
         // First item is dropped as it has the least weight.
         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -138,7 +116,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Column 2 and row 2 got removed.
@@ -158,7 +136,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column get moved to new screen
@@ -183,7 +161,7 @@
                 {  3,  1, -1,  4},
         }});
 
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on the 3rd
@@ -215,7 +193,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -244,7 +222,7 @@
                 {  5,  2,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                 new Point(4, 4), new Point(3, 4)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -269,7 +247,7 @@
                 {  5,  6,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -283,54 +261,13 @@
         }});
     }
 
-    private int[][][] createGrid(int[][][] typeArray) throws Exception {
-        return createGrid(typeArray, 1);
-    }
-
-    /**
-     * Initializes the DB with dummy elements to represent the provided grid structure.
-     * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
-     *                  type definitions. The first dimension represents the screens and the next
-     *                  two represent the workspace grid.
-     * @return the same grid representation where each entry is the corresponding item id.
-     */
-    private int[][][] createGrid(int[][][] typeArray, int startScreen) throws Exception {
-        LauncherSettings.Settings.call(mContext.getContentResolver(),
-                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-        int[][][] ids = new int[typeArray.length][][];
-
-        for (int i = 0; i < typeArray.length; i++) {
-            // Add screen to DB
-            int screenId = startScreen + i;
-
-            // Keep the screen id counter up to date
-            LauncherSettings.Settings.call(mContext.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
-            ids[i] = new int[typeArray[i].length][];
-            for (int y = 0; y < typeArray[i].length; y++) {
-                ids[i][y] = new int[typeArray[i][y].length];
-                for (int x = 0; x < typeArray[i][y].length; x++) {
-                    if (typeArray[i][y][x] < 0) {
-                        // Empty cell
-                        ids[i][y][x] = -1;
-                    } else {
-                        ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
-                    }
-                }
-            }
-        }
-
-        return ids;
-    }
-
     /**
      * Verifies that the workspace items are arranged in the provided order.
      * @param ids A 3d array where the first dimension represents the screen, and the rest two
      *            represent the workspace grid.
      */
     private void verifyWorkspace(int[][][] ids) {
-        IntArray allScreens = getWorkspaceScreenIds(mContext);
+        IntArray allScreens = getWorkspaceScreenIds(mDb);
         assertEquals(ids.length, allScreens.size());
         int total = 0;
 
@@ -367,42 +304,6 @@
         c.close();
     }
 
-    /**
-     * Adds a dummy item in the DB.
-     * @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
-     *             folder (where the type represents the number of items in the folder).
-     */
-    private int addItem(int type, int screen, int container, int x, int y) throws Exception {
-        int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
-                LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
-        ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites._ID, id);
-        values.put(LauncherSettings.Favorites.CONTAINER, container);
-        values.put(LauncherSettings.Favorites.SCREEN, screen);
-        values.put(LauncherSettings.Favorites.CELLX, x);
-        values.put(LauncherSettings.Favorites.CELLY, y);
-        values.put(LauncherSettings.Favorites.SPANX, 1);
-        values.put(LauncherSettings.Favorites.SPANY, 1);
-
-        if (type == APPLICATION || type == SHORTCUT) {
-            values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
-            values.put(LauncherSettings.Favorites.INTENT,
-                    new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
-        } else {
-            values.put(LauncherSettings.Favorites.ITEM_TYPE,
-                    LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
-            // Add folder items.
-            for (int i = 0; i < type; i++) {
-                addItem(APPLICATION, 0, id, 0, 0);
-            }
-        }
-
-        mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
-        return id;
-    }
-
     @Test
     public void testMultiStepMigration_small_to_large() throws Exception {
         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
@@ -435,7 +336,7 @@
         private final LinkedList<Point> mPoints;
 
         public MultiStepMigrationTaskVerifier(int... points) {
-            super(null, null);
+            super(null, null, null);
 
             mPoints = new LinkedList<>();
             for (int i = 0; i < points.length; i += 2) {
diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
index 32808f5..31e303e 100644
--- a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
+++ b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
@@ -23,6 +23,11 @@
         }
     }
 
+    public SQLiteDatabase getDb() {
+        createDbIfNotExists();
+        return mOpenHelper.getWritableDatabase();
+    }
+
     @Override
     protected void notifyListeners() { }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 257e46c..dbefa45 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -40,6 +40,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -135,7 +136,7 @@
         float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
         float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
         // Sort the profiles based on the closeness to the device size
-        allOptions.sort((a, b) ->
+        Collections.sort(allOptions, (a, b) ->
                 Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
                         dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
         DisplayOption interpolatedDisplayOption =
@@ -314,18 +315,6 @@
         return (float) Math.hypot(x1 - x0, y1 - y0);
     }
 
-    /**
-     * Returns the closest device profiles ordered by closeness to the specified width and height
-     */
-    @VisibleForTesting
-    static ArrayList<DisplayOption> sortByClosenessToSize(
-            float width, float height, ArrayList<DisplayOption> points) {
-        points.sort((a, b) ->
-                Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
-                        dist(width, height, b.minWidthDps, b.minHeightDps)));
-        return points;
-    }
-
     @VisibleForTesting
     static DisplayOption invDistWeightedInterpolate(float width, float height,
                 ArrayList<DisplayOption> points) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8ed3314..24dc17a 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
@@ -210,6 +213,7 @@
         addModifiedTime(initialValues);
         final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
         if (rowId < 0) return null;
+        mOpenHelper.onAddOrDeleteOp(db);
 
         uri = ContentUris.withAppendedId(uri, rowId);
         notifyListeners();
@@ -282,6 +286,7 @@
                     return 0;
                 }
             }
+            mOpenHelper.onAddOrDeleteOp(db);
             t.commit();
         }
 
@@ -290,15 +295,30 @@
         return values.length;
     }
 
+    @TargetApi(Build.VERSION_CODES.M)
     @Override
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws OperationApplicationException {
         createDbIfNotExists();
         try (SQLiteTransaction t = new SQLiteTransaction(mOpenHelper.getWritableDatabase())) {
-            ContentProviderResult[] result =  super.applyBatch(operations);
+            boolean isAddOrDelete = !Utilities.ATLEAST_MARSHMALLOW;
+
+            final int numOperations = operations.size();
+            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+            for (int i = 0; i < numOperations; i++) {
+                ContentProviderOperation op = operations.get(i);
+                results[i] = op.apply(this, results, i);
+
+                isAddOrDelete |= (op.isInsert() || op.isDelete()) &&
+                        results[i].count != null && results[i].count > 0;
+            }
+            if (isAddOrDelete) {
+                mOpenHelper.onAddOrDeleteOp(t.getDb());
+            }
+
             t.commit();
             reloadLauncherIfExternal();
-            return result;
+            return results;
         }
     }
 
@@ -315,6 +335,7 @@
         }
         int count = db.delete(args.table, args.where, args.args);
         if (count > 0) {
+            mOpenHelper.onAddOrDeleteOp(db);
             notifyListeners();
             reloadLauncherIfExternal();
         }
@@ -381,6 +402,17 @@
                 mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
                 return null;
             }
+            case LauncherSettings.Settings.METHOD_NEW_TRANSACTION: {
+                Bundle result = new Bundle();
+                result.putBinder(LauncherSettings.Settings.EXTRA_VALUE,
+                        new SQLiteTransaction(mOpenHelper.getWritableDatabase()));
+                return result;
+            }
+            case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: {
+                mOpenHelper.mBackupTableExists =
+                        tableExists(mOpenHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
+                return null;
+            }
         }
         return null;
     }
@@ -528,17 +560,19 @@
         private final Context mContext;
         private int mMaxItemId = -1;
         private int mMaxScreenId = -1;
+        private boolean mBackupTableExists;
 
         DatabaseHelper(Context context, Handler widgetHostResetHandler) {
             this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
             // Table creation sometimes fails silently, which leads to a crash loop.
             // This way, we will try to create a table every time after crash, so the device
             // would eventually be able to recover.
-            if (!tableExists(Favorites.TABLE_NAME)) {
+            if (!tableExists(getReadableDatabase(), Favorites.TABLE_NAME)) {
                 Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
                 // This operation is a no-op if the table already exists.
                 addFavoritesTable(getWritableDatabase(), true);
             }
+            mBackupTableExists = tableExists(getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
 
             initIds();
         }
@@ -564,18 +598,6 @@
             }
         }
 
-        private boolean tableExists(String tableName) {
-            Cursor c = getReadableDatabase().query(
-                    true, "sqlite_master", new String[] {"tbl_name"},
-                    "tbl_name = ?", new String[] {tableName},
-                    null, null, null, null, null);
-            try {
-                return c.getCount() > 0;
-            } finally {
-                c.close();
-            }
-        }
-
         @Override
         public void onCreate(SQLiteDatabase db) {
             if (LOGD) Log.d(TAG, "creating new launcher database");
@@ -590,6 +612,13 @@
             onEmptyDbCreated();
         }
 
+        protected void onAddOrDeleteOp(SQLiteDatabase db) {
+            if (mBackupTableExists) {
+                dropTable(db, Favorites.BACKUP_TABLE_NAME);
+                mBackupTableExists = false;
+            }
+        }
+
         /**
          * Overriden in tests.
          */
@@ -733,7 +762,7 @@
                                 Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
                         db.execSQL(query);
                     }
-                    db.execSQL("DROP TABLE IF EXISTS workspaceScreens");
+                    dropTable(db, "workspaceScreens");
                 }
                 case 28:
                     // DB Upgraded successfully
@@ -762,8 +791,8 @@
          */
         public void createEmptyDB(SQLiteDatabase db) {
             try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-                db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
-                db.execSQL("DROP TABLE IF EXISTS workspaceScreens");
+                dropTable(db, Favorites.TABLE_NAME);
+                dropTable(db, "workspaceScreens");
                 onCreate(db);
                 t.commit();
             }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 79c8208..e248ba0 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -89,6 +89,11 @@
         public static final String TABLE_NAME = "favorites";
 
         /**
+         * Backup table created when when the favorites table is modified during grid migration
+         */
+        public static final String BACKUP_TABLE_NAME = "favorites_bakup";
+
+        /**
          * The content:// style URL for this table
          */
         public static final Uri CONTENT_URI = Uri.parse("content://" +
@@ -231,8 +236,13 @@
         public static final String OPTIONS = "options";
 
         public static void addTableToDb(SQLiteDatabase db, long myProfileId, boolean optional) {
+            addTableToDb(db, myProfileId, optional, TABLE_NAME);
+        }
+
+        public static void addTableToDb(SQLiteDatabase db, long myProfileId, boolean optional,
+                String tableName) {
             String ifNotExists = optional ? " IF NOT EXISTS " : "";
-            db.execSQL("CREATE TABLE " + ifNotExists + TABLE_NAME + " (" +
+            db.execSQL("CREATE TABLE " + ifNotExists + tableName + " (" +
                     "_id INTEGER PRIMARY KEY," +
                     "title TEXT," +
                     "intent TEXT," +
@@ -279,6 +289,10 @@
 
         public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
 
+        public static final String METHOD_NEW_TRANSACTION = "new_db_transaction";
+
+        public static final String METHOD_REFRESH_BACKUP_TABLE = "refresh_backup_table";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 23ad912..3f420fa 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -95,6 +95,9 @@
     public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
             "APPLY_CONFIG_AT_RUNTIME", false, "Apply display changes dynamically");
 
+    public static final TogglableFlag ENABLE_TASK_STABILIZER = new TogglableFlag(
+            "ENABLE_TASK_STABILIZER", true, "Stable task list across fast task switches");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
new file mode 100644
index 0000000..804a040
--- /dev/null
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 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.model;
+
+import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * Helper class to backup and restore Favorites table into a separate table
+ * within the same data base.
+ */
+public class GridBackupTable {
+    private static final String TAG = "GridBackupTable";
+
+    private static final int ID_PROPERTY = -1;
+
+    private static final String KEY_HOTSEAT_SIZE = Favorites.SCREEN;
+    private static final String KEY_GRID_X_SIZE = Favorites.SPANX;
+    private static final String KEY_GRID_Y_SIZE = Favorites.SPANY;
+    private static final String KEY_DB_VERSION = Favorites.RANK;
+
+    private final Context mContext;
+    private final SQLiteDatabase mDb;
+
+    private final int mOldHotseatSize;
+    private final int mOldGridX;
+    private final int mOldGridY;
+
+    private int mRestoredHotseatSize;
+    private int mRestoredGridX;
+    private int mRestoredGridY;
+
+    public GridBackupTable(Context context, SQLiteDatabase db,
+            int hotseatSize, int gridX, int gridY) {
+        mContext = context;
+        mDb = db;
+
+        mOldHotseatSize = hotseatSize;
+        mOldGridX = gridX;
+        mOldGridY = gridY;
+    }
+
+    public boolean backupOrRestoreAsNeeded() {
+        // Check if backup table exists
+        if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
+            if (Settings.call(mContext.getContentResolver(), Settings.METHOD_WAS_EMPTY_DB_CREATED)
+                    .getBoolean(Settings.EXTRA_VALUE, false)) {
+                // No need to copy if empty DB was created.
+                return false;
+            }
+
+            copyTable(Favorites.TABLE_NAME, BACKUP_TABLE_NAME);
+            encodeDBProperties();
+            return false;
+        }
+
+        if (!loadDbProperties()) {
+            return false;
+        }
+        copyTable(BACKUP_TABLE_NAME, Favorites.TABLE_NAME);
+        Log.d(TAG, "Backup table found");
+        return true;
+    }
+
+    public int getRestoreHotseatAndGridSize(Point outGridSize) {
+        outGridSize.set(mRestoredGridX, mRestoredGridY);
+        return mRestoredHotseatSize;
+    }
+
+    private void copyTable(String from, String to) {
+        long userSerial = UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
+                Process.myUserHandle());
+        dropTable(mDb, to);
+        Favorites.addTableToDb(mDb, userSerial, false, to);
+        mDb.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
+    }
+
+    private void encodeDBProperties() {
+        ContentValues values = new ContentValues();
+        values.put(Favorites._ID, ID_PROPERTY);
+        values.put(KEY_DB_VERSION, mDb.getVersion());
+        values.put(KEY_GRID_X_SIZE, mOldGridX);
+        values.put(KEY_GRID_Y_SIZE, mOldGridY);
+        values.put(KEY_HOTSEAT_SIZE, mOldHotseatSize);
+        mDb.insert(BACKUP_TABLE_NAME, null, values);
+    }
+
+    private boolean loadDbProperties() {
+        try (Cursor c = mDb.query(BACKUP_TABLE_NAME, new String[] {
+                        KEY_DB_VERSION,     // 0
+                        KEY_GRID_X_SIZE,    // 1
+                        KEY_GRID_Y_SIZE,    // 2
+                        KEY_HOTSEAT_SIZE},  // 3
+                "_id=" + ID_PROPERTY, null, null, null, null)) {
+            if (!c.moveToNext()) {
+                Log.e(TAG, "Meta data not found in backup table");
+                return false;
+            }
+            if (mDb.getVersion() != c.getInt(0)) {
+                return false;
+            }
+
+            mRestoredGridX = c.getInt(1);
+            mRestoredGridY = c.getInt(2);
+            mRestoredHotseatSize = c.getInt(3);
+            return true;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index a9ddccb..1c7bffa 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -1,10 +1,10 @@
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.Utilities.parsePoint;
 
 import android.content.ComponentName;
-import android.content.ContentProviderOperation;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -12,24 +12,27 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
 
 import java.util.ArrayList;
@@ -60,13 +63,13 @@
     private static final float WT_WIDGET_FACTOR = 0.6f;
     private static final float WT_FOLDER_FACTOR = 0.5f;
 
-    private final Context mContext;
-    private final InvariantDeviceProfile mIdp;
+    protected final SQLiteDatabase mDb;
+    protected final Context mContext;
 
-    private final ContentValues mTempValues = new ContentValues();
     protected final IntArray mEntryToRemove = new IntArray();
-    private final ArrayList<ContentProviderOperation> mUpdateOperations = new ArrayList<>();
     protected final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
+
+    private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
     private final HashSet<String> mValidPackages;
 
     private final int mSrcX, mSrcY;
@@ -76,11 +79,11 @@
     private final int mSrcHotseatSize;
     private final int mDestHotseatSize;
 
-    protected GridSizeMigrationTask(Context context, InvariantDeviceProfile idp,
+    protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
             HashSet<String> validPackages, Point sourceSize, Point targetSize) {
         mContext = context;
+        mDb = db;
         mValidPackages = validPackages;
-        mIdp = idp;
 
         mSrcX = sourceSize.x;
         mSrcY = sourceSize.y;
@@ -95,11 +98,10 @@
         mSrcHotseatSize = mDestHotseatSize = -1;
     }
 
-    protected GridSizeMigrationTask(Context context,
-            InvariantDeviceProfile idp, HashSet<String> validPackages,
-            int srcHotseatSize, int destHotseatSize) {
+    protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
+            HashSet<String> validPackages, int srcHotseatSize, int destHotseatSize) {
         mContext = context;
-        mIdp = idp;
+        mDb = db;
         mValidPackages = validPackages;
 
         mSrcHotseatSize = srcHotseatSize;
@@ -118,20 +120,21 @@
      */
     private boolean applyOperations() throws Exception {
         // Update items
-        if (!mUpdateOperations.isEmpty()) {
-            mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+        int updateCount = mUpdateOperations.size();
+        for (int i = 0; i < updateCount; i++) {
+            mDb.update(Favorites.TABLE_NAME, mUpdateOperations.valueAt(i),
+                    "_id=" + mUpdateOperations.keyAt(i), null);
         }
 
         if (!mEntryToRemove.isEmpty()) {
             if (DEBUG) {
                 Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
             }
-            mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
-                    Utilities.createDbSelectionQuery(
-                            LauncherSettings.Favorites._ID, mEntryToRemove), null);
+            mDb.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
+                    Favorites._ID, mEntryToRemove), null);
         }
 
-        return !mUpdateOperations.isEmpty() || !mEntryToRemove.isEmpty();
+        return updateCount > 0 || !mEntryToRemove.isEmpty();
     }
 
     /**
@@ -181,24 +184,17 @@
     }
 
     @VisibleForTesting
-    static IntArray getWorkspaceScreenIds(Context context) {
-        IntSet set = new IntSet();
-        try (Cursor c = context.getContentResolver().query(Favorites.CONTENT_URI,
-                new String[] {Favorites.SCREEN},
+    static IntArray getWorkspaceScreenIds(SQLiteDatabase db) {
+        return LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.SCREEN,
                 Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
-                null, Favorites.SCREEN)) {
-            while (c.moveToNext()) {
-                set.add(c.getInt(0));
-            }
-        }
-        return set.getArray();
+                Favorites.SCREEN, Favorites.SCREEN);
     }
 
     /**
      * @return true if any DB change was made
      */
     protected boolean migrateWorkspace() throws Exception {
-        IntArray allScreens = getWorkspaceScreenIds(mContext);
+        IntArray allScreens = getWorkspaceScreenIds(mDb);
         if (allScreens.isEmpty()) {
             throw new Exception("Unable to get workspace screens");
         }
@@ -230,8 +226,7 @@
                     int newScreenId = LauncherSettings.Settings.call(
                             mContext.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
+                            .getInt(EXTRA_VALUE);
                     for (DbEntry item : placement.finalPlacedItems) {
                         if (!mCarryOver.remove(itemMap.get(item.id))) {
                             throw new Exception("Unable to find matching items");
@@ -362,11 +357,9 @@
      * Updates an item in the DB.
      */
     protected void update(DbEntry item) {
-        mTempValues.clear();
-        item.addToContentValues(mTempValues);
-        mUpdateOperations.add(ContentProviderOperation
-                .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
-                .withValues(mTempValues).build());
+        ContentValues values = new ContentValues();
+        item.addToContentValues(values);
+        mUpdateOperations.put(item.id, values);
     }
 
     /**
@@ -612,13 +605,13 @@
     }
 
     private ArrayList<DbEntry> loadHotseatEntries() {
-        Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+        Cursor c =  queryWorkspace(
                 new String[]{
                         Favorites._ID,                  // 0
                         Favorites.ITEM_TYPE,            // 1
                         Favorites.INTENT,               // 2
                         Favorites.SCREEN},              // 3
-                Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT, null, null, null);
+                Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT);
 
         final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
         final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
@@ -796,8 +789,7 @@
     }
 
     protected Cursor queryWorkspace(String[] columns, String where) {
-        return mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                columns, where, null, null, null);
+        return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
     }
 
     /**
@@ -864,11 +856,11 @@
         }
 
         public void addToContentValues(ContentValues values) {
-            values.put(LauncherSettings.Favorites.SCREEN, screenId);
-            values.put(LauncherSettings.Favorites.CELLX, cellX);
-            values.put(LauncherSettings.Favorites.CELLY, cellY);
-            values.put(LauncherSettings.Favorites.SPANX, spanX);
-            values.put(LauncherSettings.Favorites.SPANY, spanY);
+            values.put(Favorites.SCREEN, screenId);
+            values.put(Favorites.CELLX, cellX);
+            values.put(Favorites.CELLY, cellY);
+            values.put(Favorites.SPANX, spanX);
+            values.put(Favorites.SPANY, spanY);
         }
     }
 
@@ -907,34 +899,43 @@
         }
 
         long migrationStartTime = System.currentTimeMillis();
-        try {
+        try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
+                context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
+                .getBinder(Settings.EXTRA_VALUE)) {
+
+            int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                    idp.numHotseatIcons);
+            Point sourceSize = parsePoint(prefs.getString(
+                    KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
+
             boolean dbChanged = false;
 
+            GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
+                    srcHotseatCount, sourceSize.x, sourceSize.y);
+            if (backupTable.backupOrRestoreAsNeeded()) {
+                dbChanged = true;
+                srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
+            }
+
             HashSet<String> validPackages = getValidPackages(context);
             // Hotseat
-            int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
-                    idp.numHotseatIcons);
             if (srcHotseatCount != idp.numHotseatIcons) {
                 // Migrate hotseat.
-
-                dbChanged = new GridSizeMigrationTask(context, LauncherAppState.getIDP(context),
+                dbChanged = new GridSizeMigrationTask(context, transaction.getDb(),
                         validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
             }
 
             // Grid size
             Point targetSize = new Point(idp.numColumns, idp.numRows);
-            Point sourceSize = parsePoint(prefs.getString(
-                    KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
-
-            if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize,
-                    targetSize)) {
+            if (new MultiStepMigrationTask(validPackages, context, transaction.getDb())
+                    .migrate(sourceSize, targetSize)) {
                 dbChanged = true;
             }
 
             if (dbChanged) {
                 // Make sure we haven't removed everything.
                 final Cursor c = context.getContentResolver().query(
-                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+                        Favorites.CONTENT_URI, null, null, null, null);
                 boolean hasData = c.moveToNext();
                 c.close();
                 if (!hasData) {
@@ -942,6 +943,8 @@
                 }
             }
 
+            transaction.commit();
+            Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Error during grid migration", e);
@@ -982,19 +985,24 @@
      */
     public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context)
             throws Exception {
-        GridSizeMigrationTask task = new GridSizeMigrationTask(
-                context, LauncherAppState.getIDP(context), getValidPackages(context),
-                Integer.MAX_VALUE, Integer.MAX_VALUE);
+        try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
+                context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
+                .getBinder(Settings.EXTRA_VALUE)) {
+            GridSizeMigrationTask task = new GridSizeMigrationTask(
+                    context, transaction.getDb(), 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();
-        IntSparseArrayMap<Object> positions = new IntSparseArrayMap<>();
-        for (DbEntry item : items) {
-            positions.put(item.screenId, item);
+            // Load all the valid entries
+            ArrayList<DbEntry> items = task.loadHotseatEntries();
+            // Delete any entry marked for deletion by above load.
+            task.applyOperations();
+            IntSparseArrayMap<Object> positions = new IntSparseArrayMap<>();
+            for (DbEntry item : items) {
+                positions.put(item.screenId, item);
+            }
+            transaction.commit();
+            return positions;
         }
-        return positions;
     }
 
     /**
@@ -1003,10 +1011,13 @@
     protected static class MultiStepMigrationTask {
         private final HashSet<String> mValidPackages;
         private final Context mContext;
+        private final SQLiteDatabase mDb;
 
-        public MultiStepMigrationTask(HashSet<String> validPackages, Context context) {
+        public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
+                SQLiteDatabase db) {
             mValidPackages = validPackages;
             mContext = context;
+            mDb = db;
         }
 
         public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
@@ -1042,7 +1053,7 @@
         }
 
         protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
-            return new GridSizeMigrationTask(mContext, LauncherAppState.getIDP(mContext),
+            return new GridSizeMigrationTask(mContext, mDb,
                     mValidPackages, sourceSize, nextSize).migrateWorkspace();
         }
     }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index a4fe570..8a6aa1af 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -672,6 +672,11 @@
                                 appWidgetInfo.spanY = c.getInt(spanYIndex);
                                 appWidgetInfo.user = c.user;
 
+                                if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
+                                    c.markDeleted("Widget has invalid size: "
+                                            + appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
+                                    continue;
+                                }
                                 if (!c.isOnWorkspaceOrHotseat()) {
                                     c.markDeleted("Widget found where container != " +
                                             "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index b79478a..2c843f9 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -21,6 +21,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.Binder;
 import android.util.Log;
 
 import com.android.launcher3.LauncherAppState;
@@ -103,10 +104,22 @@
         return out;
     }
 
+    public static boolean tableExists(SQLiteDatabase db, String tableName) {
+        try (Cursor c = db.query(true, "sqlite_master", new String[] {"tbl_name"},
+                "tbl_name = ?", new String[] {tableName},
+                null, null, null, null, null)) {
+            return c.getCount() > 0;
+        }
+    }
+
+    public static void dropTable(SQLiteDatabase db, String tableName) {
+        db.execSQL("DROP TABLE IF EXISTS " + tableName);
+    }
+
     /**
      * Utility class to simplify managing sqlite transactions
      */
-    public static class SQLiteTransaction implements AutoCloseable {
+    public static class SQLiteTransaction extends Binder implements AutoCloseable {
         private final SQLiteDatabase mDb;
 
         public SQLiteTransaction(SQLiteDatabase db) {
@@ -122,5 +135,9 @@
         public void close() {
             mDb.endTransaction();
         }
+
+        public SQLiteDatabase getDb() {
+            return mDb;
+        }
     }
 }
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
index 9166b83..6d839f3 100644
--- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
+++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
@@ -37,29 +37,21 @@
  */
 public class LossyScreenMigrationTask extends GridSizeMigrationTask {
 
-    private final SQLiteDatabase mDb;
-
     private final IntSparseArrayMap<DbEntry> mOriginalItems;
     private final IntSparseArrayMap<DbEntry> mUpdates;
 
     protected LossyScreenMigrationTask(
             Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
         // Decrease the rows count by 1
-        super(context, idp, getValidPackages(context),
+        super(context, db, getValidPackages(context),
                 new Point(idp.numColumns, idp.numRows + 1),
                 new Point(idp.numColumns, idp.numRows));
 
-        mDb = db;
         mOriginalItems = new IntSparseArrayMap<>();
         mUpdates = new IntSparseArrayMap<>();
     }
 
     @Override
-    protected Cursor queryWorkspace(String[] columns, String where) {
-        return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
-    }
-
-    @Override
     protected void update(DbEntry item) {
         mUpdates.put(item.id, item.copy());
     }
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 17c66b4..bcca4d8 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.provider;
 
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -111,7 +113,7 @@
         db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
         Favorites.addTableToDb(db, newProfileId, false);
         db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
-        db.execSQL("DROP TABLE favorites_old;");
+        dropTable(db, "favorites_old");
     }
 
     /**
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index b2fb32a..d2a551f 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -100,6 +100,14 @@
     }
 
     /**
+     * Sets the array to be same as {@param other}
+     */
+    public void copyFrom(IntArray other) {
+        clear();
+        addAll(other);
+    }
+
+    /**
      * Ensures capacity to append at least <code>count</code> values.
      */
     private void ensureCapacity(int count) {
@@ -127,6 +135,25 @@
         return wrap(toArray());
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof IntArray) {
+            IntArray arr = (IntArray) obj;
+            if (mSize == arr.mSize) {
+                for (int i = 0; i < mSize; i++) {
+                    if (arr.mValues[i] != mValues[i]) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns the value at the specified position in this array.
      */
diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java
index 2459fad..851f129 100644
--- a/src/com/android/launcher3/util/IntSet.java
+++ b/src/com/android/launcher3/util/IntSet.java
@@ -49,10 +49,29 @@
         return mArray.size();
     }
 
+    public void clear() {
+        mArray.clear();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        return (obj instanceof IntSet) && ((IntSet) obj).mArray.equals(mArray);
+    }
+
     public IntArray getArray() {
         return mArray;
     }
 
+    /**
+     * Sets this set to be same as {@param other}
+     */
+    public void copyFrom(IntSet other) {
+        mArray.copyFrom(other.mArray);
+    }
+
     public static IntSet wrap(IntArray array) {
         IntSet set = new IntSet();
         set.mArray.addAll(array);
diff --git a/src/com/android/launcher3/util/MyActivity.java b/src/com/android/launcher3/util/MyActivity.java
deleted file mode 100644
index 379a8da..0000000
--- a/src/com/android/launcher3/util/MyActivity.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2018 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.annotation.TargetApi;
-import android.app.Activity;
-import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.launcher3.LauncherAppState;
-
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
-
-/**
- * TODO: Remove this class
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class MyActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... voids) {
-                LauncherPreviewRenderer renderer = new LauncherPreviewRenderer(MyActivity.this,
-                        LauncherAppState.getIDP(MyActivity.this));
-
-                Bitmap b = renderer.createScreenShot();
-                return b;
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                ImageView view = new ImageView(MyActivity.this);
-                view.setImageBitmap(bitmap);
-                setContentView(view);
-
-                view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-
-            }
-        }.execute();
-    }
-}