Merge "Dismiss pip will fade task in and snap to it" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 211e1ff..8f4d5be 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -44,6 +44,28 @@
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
 
+
+    <!--
+    Permissions required for read/write access to the workspace data. These permission name
+    should not conflict with that defined in other apps, as such an app should embed its package
+    name in the permissions. eq com.mypackage.permission.READ_SETTINGS
+    -->
+    <permission
+        android:name="${packageName}.permission.READ_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_read_settings"
+        android:description="@string/permdesc_read_settings"/>
+    <permission
+        android:name="${packageName}.permission.WRITE_SETTINGS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_write_settings"
+        android:description="@string/permdesc_write_settings"/>
+
+    <uses-permission android:name="${packageName}.permission.READ_SETTINGS" />
+    <uses-permission android:name="${packageName}.permission.WRITE_SETTINGS" />
+
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
@@ -118,5 +140,31 @@
             android:name="com.android.launcher3.launcher_dump_provider"
             android:value="com.android.launcher3.LauncherProvider" />
 
+        <!--
+        The settings provider contains Home's data, like the workspace favorites. The permissions
+        should be changed to what is defined above. The authorities should also be changed to
+        represent the package name.
+        -->
+        <provider
+            android:name="com.android.launcher3.LauncherProvider"
+            android:authorities="${packageName}.settings"
+            android:exported="true"
+            android:writePermission="${packageName}.permission.WRITE_SETTINGS"
+            android:readPermission="${packageName}.permission.READ_SETTINGS" />
+
+        <!--
+        The settings activity. To extend point settings_fragment_name to appropriate fragment class
+        -->
+        <activity
+            android:name="com.android.launcher3.SettingsActivity"
+            android:label="@string/settings_button_text"
+            android:theme="@android:style/Theme.DeviceDefault.Settings"
+            android:autoRemoveFromRecents="true">
+            <intent-filter>
+                <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3212980..4ac51ab 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -26,29 +26,6 @@
     Refer comments around specific entries on how to extend individual components.
     -->
 
-    <!--
-    Permissions required for read/write access to the workspace data. These permission name
-    should not conflict with that defined in other apps, as such an app should embed its package
-    name in the permissions. eq com.mypackage.permission.READ_SETTINGS
-    -->
-    <permission
-        android:name="com.android.launcher3.permission.READ_SETTINGS"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="signatureOrSystem"
-        android:label="@string/permlab_read_settings"
-        android:description="@string/permdesc_read_settings"/>
-    <permission
-        android:name="com.android.launcher3.permission.WRITE_SETTINGS"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="signatureOrSystem"
-        android:label="@string/permlab_write_settings"
-        android:description="@string/permdesc_write_settings"/>
-
-    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
-    <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
-    <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
-
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
@@ -86,31 +63,5 @@
             </intent-filter>
         </activity>
 
-        <!--
-        The settings activity. When extending keep the intent filter present
-        -->
-        <activity
-            android:name="com.android.launcher3.SettingsActivity"
-            android:label="@string/settings_button_text"
-            android:theme="@android:style/Theme.DeviceDefault.Settings"
-            android:autoRemoveFromRecents="true">
-            <intent-filter>
-                <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
-        <!--
-        The settings provider contains Home's data, like the workspace favorites. The permissions
-        should be changed to what is defined above. The authorities should also be changed to
-        represent the package name.
-        -->
-        <provider
-            android:name="com.android.launcher3.LauncherProvider"
-            android:authorities="com.android.launcher3.settings"
-            android:exported="true"
-            android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"
-            android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />
-
     </application>
 </manifest>
diff --git a/proguard.flags b/proguard.flags
index e401116..ddae07e 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -97,30 +97,19 @@
 # support jar.
 -keep class android.support.v7.widget.RecyclerView { *; }
 
-# LauncherAppTransitionManager
--keep class com.android.launcher3.LauncherAppTransitionManagerImpl {
+# Preference fragments
+-keep class ** extends android.preference.PreferenceFragment {
     public <init>(...);
 }
 
-# InstantAppResolver
--keep class com.android.quickstep.InstantAppResolverImpl {
-    public <init>(...);
-}
-
-# MainProcessInitializer
--keep class com.android.quickstep.QuickstepProcessInitializer {
-    public <init>(...);
-}
-
-# UserEventDispatcherExtension
--keep class com.android.quickstep.logging.UserEventDispatcherExtension {
+## Prevent obfuscating various overridable objects
+-keep class ** implements com.android.launcher3.util.ResourceBasedOverride {
     public <init>(...);
 }
 
 -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
   *;
 }
-
 -keep interface com.android.launcher3.model.nano.LauncherDumpProto.** {
   *;
 }
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index cb74855..74e0b1e 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -24,6 +24,7 @@
 
     <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="28"/>
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
+
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
@@ -59,10 +60,10 @@
             android:resumeWhilePausing="true"
             android:taskAffinity="" />
 
-        <!-- Content provider to settings search -->
+        <!-- Content provider to settings search. The autority should be same as the packageName -->
         <provider
             android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
-            android:authorities="com.android.launcher3"
+            android:authorities="${packageName}"
             android:grantUriPermissions="true"
             android:multiprocess="true"
             android:permission="android.permission.READ_SEARCH_INDEXABLES"
@@ -72,7 +73,6 @@
             </intent-filter>
         </provider>
 
-
         <service
             android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
             tools:node="remove" />
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 17d5c60..49c4492 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -33,7 +33,7 @@
 
     <!-- Launcher app transition -->
     <dimen name="content_trans_y">50dp</dimen>
-    <dimen name="springs_trans_y">-70dp</dimen>
+    <dimen name="workspace_trans_y">50dp</dimen>
     <dimen name="closing_window_trans_y">115dp</dimen>
 
     <dimen name="recents_empty_message_text_size">16sp</dimen>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 252e3ea..14633af 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.view.View.TRANSLATION_Y;
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -28,10 +27,8 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OSCILLATE;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
 import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
@@ -57,7 +54,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Pair;
-import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -119,20 +115,12 @@
 
     public static final int RECENTS_LAUNCH_DURATION = 336;
     public static final int RECENTS_QUICKSCRUB_LAUNCH_DURATION = 300;
-    private static final int LAUNCHER_RESUME_START_DELAY = 40;
+    private static final int LAUNCHER_RESUME_START_DELAY = 100;
     private static final int CLOSING_TRANSITION_DURATION_MS = 250;
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
 
-    private static final int APP_CLOSE_ROW_START_DELAY_MS = 8;
-
-    // The sum of [slide, oscillate, and settle] should be <= LAUNCHER_RESUME_TOTAL_DURATION.
-    private static final int LAUNCHER_RESUME_TOTAL_DURATION = 346;
-    private static final int SPRING_SLIDE_DURATION = 166;
-    private static final int SPRING_OSCILLATE_DURATION = 130;
-    private static final int SPRING_SETTLE_DURATION = 50;
-
     private final Launcher mLauncher;
     private final DragLayer mDragLayer;
     private final AlphaProperty mDragLayerAlpha;
@@ -141,8 +129,7 @@
     private final boolean mIsRtl;
 
     private final float mContentTransY;
-    private final float mStartSlideTransY;
-    private final float mEndSlideTransY;
+    private final float mWorkspaceTransY;
     private final float mClosingWindowTransY;
 
     private DeviceProfile mDeviceProfile;
@@ -172,9 +159,8 @@
 
         Resources res = mLauncher.getResources();
         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
+        mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
-        mStartSlideTransY = res.getDimensionPixelSize(R.dimen.springs_trans_y);
-        mEndSlideTransY = -mStartSlideTransY * 0.1f;
 
         mLauncher.addOnDeviceProfileChangeListener(this);
         registerRemoteAnimations();
@@ -786,33 +772,25 @@
             });
         } else {
             AnimatorSet workspaceAnimator = new AnimatorSet();
+
+            mDragLayer.setTranslationY(-mWorkspaceTransY);;
+            workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
+                    -mWorkspaceTransY, 0));
+
+            mDragLayerAlpha.setValue(0);
+            workspaceAnimator.play(ObjectAnimator.ofFloat(
+                    mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
+
             workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-
-            ShortcutAndWidgetContainer currentPage = ((CellLayout) mLauncher.getWorkspace()
-                    .getChildAt(mLauncher.getWorkspace().getCurrentPage()))
-                    .getShortcutsAndWidgets();
-
-            // Set up springs on workspace items.
-            for (int i = currentPage.getChildCount() - 1; i >= 0; i--) {
-                View child = currentPage.getChildAt(i);
-                CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
-                addStaggeredAnimationForView(child, workspaceAnimator, lp.cellY + lp.cellVSpan);
-            }
-
-            // Set up a spring for the shelf.
-            if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-                AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-                float shiftRange = allAppsController.getShiftRange();
-                float slideStart = shiftRange / (shiftRange - mStartSlideTransY);
-                float oscillateStart = shiftRange / (shiftRange - mEndSlideTransY);
-
-                buildSpringAnimation(workspaceAnimator, allAppsController, ALL_APPS_PROGRESS,
-                        0 /* startDelay */, slideStart, oscillateStart, 1f /* finalPosition */);
-            }
+            workspaceAnimator.setDuration(333);
+            workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
 
             mDragLayer.getScrim().hideSysUiScrim(true);
+
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
+            mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
             workspaceAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -823,66 +801,6 @@
         }
     }
 
-    /**
-     * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row.
-     *
-     * @param v View in a ShortcutAndWidgetContainer.
-     * @param row The bottom-most row that contains the view.
-     */
-    private void addStaggeredAnimationForView(View v, AnimatorSet outAnimator, int row) {
-        // Invert the rows, because we stagger starting from the bottom of the screen.
-        int invertedRow = LauncherAppState.getIDP(mLauncher).numRows - row + 1;
-        long startDelay = (long) (invertedRow * APP_CLOSE_ROW_START_DELAY_MS);
-
-        v.setAlpha(0);
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 1f);
-        alpha.setInterpolator(LINEAR);
-        alpha.setDuration(SPRING_SLIDE_DURATION + SPRING_OSCILLATE_DURATION);
-        alpha.setStartDelay(startDelay);
-        outAnimator.play(alpha);
-
-        buildSpringAnimation(outAnimator, v, TRANSLATION_Y, startDelay, mStartSlideTransY,
-                mEndSlideTransY, 0f /* finalPosition */);
-
-        outAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                v.setAlpha(1f);
-                v.setTranslationY(0);
-            }
-        });
-    }
-
-    /**
-     * Spring animations consists of three sequential animators: a slide, an oscillation, and
-     * a settle.
-     */
-    private <T> void buildSpringAnimation(AnimatorSet outAnimator, T objectToSpring,
-            Property<T, Float> property, long startDelay, float slideStart, float oscillateStart,
-            float finalPosition) {
-        // Ensures a clean hand-off between slide and oscillate.
-        float slideEnd = Utilities.mapToRange(0, 0, 1f, oscillateStart, finalPosition, OSCILLATE);
-
-        property.set(objectToSpring, slideStart);
-
-        ObjectAnimator slideIn = ObjectAnimator.ofFloat(objectToSpring, property, slideStart,
-                slideEnd);
-        slideIn.setInterpolator(DEACCEL);
-        slideIn.setStartDelay(startDelay);
-        slideIn.setDuration(SPRING_SLIDE_DURATION);
-
-        ObjectAnimator oscillate = ObjectAnimator.ofFloat(objectToSpring, property, oscillateStart,
-                finalPosition);
-        oscillate.setInterpolator(OSCILLATE);
-        oscillate.setDuration(SPRING_OSCILLATE_DURATION);
-
-        ObjectAnimator settle = ObjectAnimator.ofFloat(objectToSpring, property, finalPosition);
-        settle.setInterpolator(LINEAR);
-        settle.setDuration(SPRING_SETTLE_DURATION);
-
-        outAnimator.playSequentially(slideIn, oscillate, settle);
-    }
-
     private void resetContentView() {
         mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
         mDragLayerAlpha.setValue(1f);
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 275075f..8fa6c49 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
@@ -156,6 +157,10 @@
             QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
                     .getQuickScrubController();
             controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this);
+
+            // For the duration of the gesture, lock the screen orientation to ensure that we do not
+            // rotate mid-quickscrub
+            activity.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
index 0785093..6b66ec8 100644
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -20,17 +20,22 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
 
 import android.animation.ValueAnimator;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.Interpolators.OvershootParams;
 import com.android.launcher3.uioverrides.PortraitStatesTouchController;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -89,7 +94,9 @@
     }
 
     public void end(float velocity, boolean isFling, Runnable callback) {
+        float velocityPxPerMs = velocity / 1000;
         long duration = MAX_SWIPE_DURATION;
+        Interpolator interpolator = DEACCEL;
 
         final float currentFraction = mAnimator.getProgressFraction();
         final boolean toAllApps;
@@ -107,6 +114,16 @@
             long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
+
+            if (blockedFling && !toAllApps) {
+                Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
+                        currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
+                duration = (overshoot.duration + duration);
+                duration = Utilities.boundToRange(duration, MIN_OVERSHOOT_DURATION,
+                        MAX_SWIPE_DURATION);
+                interpolator = overshoot.interpolator;
+                endProgress = overshoot.end;
+            }
         } else {
             toAllApps = velocity < 0;
             endProgress = toAllApps ? 1 : 0;
@@ -119,18 +136,16 @@
                 // we want the page's snap velocity to approximately match the velocity at
                 // which the user flings, so we scale the duration by a value near to the
                 // derivative of the scroll interpolator at zero, ie. 2.
-                long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity));
+                long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
                 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
             }
         }
 
-        if (blockedFling && !toAllApps) {
-            duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
-        }
         final boolean finalIsFling = isFling;
         mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
+
         ValueAnimator animator = mAnimator.getAnimationPlayer();
-        animator.setDuration(duration).setInterpolator(DEACCEL);
+        animator.setDuration(duration).setInterpolator(interpolator);
         animator.setFloatValues(currentFraction, endProgress);
         animator.start();
     }
diff --git a/quickstep/src/com/android/quickstep/OverviewCallbacks.java b/quickstep/src/com/android/quickstep/OverviewCallbacks.java
index ac4a40b..ef9c5c0 100644
--- a/quickstep/src/com/android/quickstep/OverviewCallbacks.java
+++ b/quickstep/src/com/android/quickstep/OverviewCallbacks.java
@@ -18,20 +18,20 @@
 import android.content.Context;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
  * Callbacks related to overview/quicksteps.
  */
-public class OverviewCallbacks {
+public class OverviewCallbacks implements ResourceBasedOverride {
 
     private static OverviewCallbacks sInstance;
 
     public static OverviewCallbacks get(Context context) {
         Preconditions.assertUIThread();
         if (sInstance == null) {
-            sInstance = Utilities.getOverrideObject(OverviewCallbacks.class,
+            sInstance = Overrides.getObject(OverviewCallbacks.class,
                     context.getApplicationContext(), R.string.overview_callbacks_class);
         }
         return sInstance;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index eff94fc..7094a53 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -197,14 +197,10 @@
     }
 
     public void onTip(int actionType, int viewType) {
-        mMainThreadExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
+        mMainThreadExecutor.execute(() ->
                 UserEventDispatcher.newInstance(mContext,
-                        new InvariantDeviceProfile(mContext).getDeviceProfile(mContext))
-                        .logActionTip(actionType, viewType);
-            }
-        });
+                        InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext))
+                        .logActionTip(actionType, viewType));
     }
 
     public ActivityControlHelper getActivityControlHelper() {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index ed8b4d2..32079bf 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.badge.BadgeInfo;
@@ -131,21 +130,13 @@
     }
 
     private void initDeviceProfile() {
-        // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
+
+        // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
         // activity.
-        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
-        if (isInMultiWindowModeCompat()) {
-            InvariantDeviceProfile idp = appState == null
-                    ? new InvariantDeviceProfile(this) : appState.getInvariantDeviceProfile();
-            DeviceProfile dp = idp.getDeviceProfile(this);
-            mDeviceProfile = mRecentsRootView == null ? dp.copy(this)
-                    : dp.getMultiWindowProfile(this, mRecentsRootView.getLastKnownSize());
-        } else {
-            // If we are reusing the Invariant device profile, make a copy.
-            mDeviceProfile = appState == null
-                    ? new InvariantDeviceProfile(this).getDeviceProfile(this)
-                    : appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this);
-        }
+        mDeviceProfile = (mRecentsRootView != null) && isInMultiWindowModeCompat()
+                ? dp.getMultiWindowProfile(this, mRecentsRootView.getLastKnownSize())
+                : dp.copy(this);
         onDeviceProfileInitiated();
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 9d3ac6a..c272b1a 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -22,22 +22,22 @@
 import android.view.View;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
  * Factory class to create and add an overlays on the TaskView
  */
-public class TaskOverlayFactory {
+public class TaskOverlayFactory implements ResourceBasedOverride {
 
     private static TaskOverlayFactory sInstance;
 
     public static TaskOverlayFactory get(Context context) {
         Preconditions.assertUIThread();
         if (sInstance == null) {
-            sInstance = Utilities.getOverrideObject(TaskOverlayFactory.class,
+            sInstance = Overrides.getObject(TaskOverlayFactory.class,
                     context.getApplicationContext(), R.string.task_overlay_factory_class);
         }
         return sInstance;
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index ff3137d..3662633 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
@@ -56,7 +57,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -163,6 +163,7 @@
 
     public static final long MAX_SWIPE_DURATION = 350;
     public static final long MIN_SWIPE_DURATION = 80;
+    public static final long MIN_OVERSHOOT_DURATION = 120;
 
     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
     private static final float SWIPE_DURATION_MULTIPLIER =
@@ -498,7 +499,8 @@
         setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
 
         // Start the window animation without waiting for launcher.
-        animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR);
+        animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
+                true /* goingToHome */);
     }
 
     private void shiftAnimationDestinationForQuickscrub() {
@@ -608,10 +610,7 @@
 
     public void onRecentsAnimationStart(RecentsAnimationControllerCompat controller,
             RemoteAnimationTargetSet targets, Rect homeContentInsets, Rect minimizedHomeBounds) {
-        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
-        InvariantDeviceProfile idp = appState == null ?
-                new InvariantDeviceProfile(mContext) : appState.getInvariantDeviceProfile();
-        DeviceProfile dp = idp.getDeviceProfile(mContext);
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
         final Rect overviewStackBounds;
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
 
@@ -699,36 +698,46 @@
     private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
         float velocityPxPerMs = endVelocity / 1000;
         long duration = MAX_SWIPE_DURATION;
-        final float endShift;
+        float currentShift = mCurrentShift.value;
+        final boolean goingToHome;
+        float endShift;
         final float startShift;
-        final Interpolator interpolator;
+        Interpolator interpolator = DEACCEL;
         if (!isFling) {
-            endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted ? 1 : 0;
-            long expectedDuration = Math.abs(Math.round((endShift - mCurrentShift.value)
+            goingToHome = currentShift >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted;
+            endShift = goingToHome ? 1 : 0;
+            long expectedDuration = Math.abs(Math.round((endShift - currentShift)
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
-            startShift = mCurrentShift.value;
-            interpolator = DEACCEL;
+            startShift = currentShift;
+            interpolator = goingToHome ? OVERSHOOT_1_2 : DEACCEL;
         } else {
-            endShift = endVelocity < 0 ? 1 : 0;
-            interpolator = endVelocity < 0
-                    ? Interpolators.overshootInterpolatorForVelocity(velocityPxPerMs, 2f)
-                    : DEACCEL;
+            goingToHome = endVelocity < 0;
+            endShift = goingToHome ? 1 : 0;
+            startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
+                    * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
-                float distanceToTravel = (endShift - mCurrentShift.value) * mTransitionDragLength;
+                if (goingToHome) {
+                    Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
+                            startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
+                    endShift = overshoot.end;
+                    interpolator = overshoot.interpolator;
+                    duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
+                            MAX_SWIPE_DURATION);
+                } else {
+                    float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
 
-                // we want the page's snap velocity to approximately match the velocity at
-                // which the user flings, so we scale the duration by a value near to the
-                // derivative of the scroll interpolator at zero, ie. 2.
-                long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
-                duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+                    // we want the page's snap velocity to approximately match the velocity at
+                    // which the user flings, so we scale the duration by a value near to the
+                    // derivative of the scroll interpolator at zero, ie. 2.
+                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
+                    duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+                }
             }
-            startShift = Utilities.boundToRange(mCurrentShift.value - velocityPxPerMs
-                    * SINGLE_FRAME_MS / (mTransitionDragLength), 0, 1);
         }
-        animateToProgress(startShift, endShift, duration, interpolator);
+        animateToProgress(startShift, endShift, duration, interpolator, goingToHome);
     }
 
     private void doLogGesture(boolean toLauncher) {
@@ -754,14 +763,14 @@
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
     private void animateToProgress(float start, float end, long duration,
-            Interpolator interpolator) {
+            Interpolator interpolator, boolean goingToHome) {
         mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
-                interpolator));
+                interpolator, goingToHome));
     }
 
     private void animateToProgressInternal(float start, float end, long duration,
-            Interpolator interpolator) {
-        mIsGoingToHome = Float.compare(end, 1) == 0;
+            Interpolator interpolator, boolean goingToHome) {
+        mIsGoingToHome = goingToHome;
         ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
         anim.setInterpolator(interpolator);
         anim.addListener(new AnimationSuccessListener() {
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index c149de5..61740d7 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -40,10 +40,6 @@
         super(launcher, null);
         mLauncher = launcher;
         setVisibility(INVISIBLE);
-
-        // For the duration of the gesture, lock the screen orientation to ensure that we do not
-        // rotate mid-quickscrub
-        launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 6eb6854..098349a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -175,9 +175,9 @@
         params.width = sTempRect.width();
         params.gravity = Gravity.LEFT;
         setLayoutParams(params);
-        setX(sTempRect.left - insets.left);
-        setY(sTempRect.top + getResources().getDimension(R.dimen.task_thumbnail_top_margin)
-                - insets.top);
+        setX(Math.round(sTempRect.left - insets.left));
+        setY(Math.round(sTempRect.top - insets.top
+                + getResources().getDimension(R.dimen.task_thumbnail_top_margin)));
     }
 
     private void animateOpen() {
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 5a8c6ca..2677b21 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -41,7 +41,7 @@
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"按住捷徑即可選取。"</string>
-    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"撳兩下之後撳住,就可以揀選捷徑或者用自訂嘅操作。"</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"連㩒兩下之後繼續㩒住,就可以揀選捷徑或者用自訂嘅操作。"</string>
     <string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"我的收藏寄存區沒有足夠空間"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"應用程式清單"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index f2d6c21..f462b9c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -18,6 +18,9 @@
     <!-- String representing the intent to delete a package.-->
     <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
 
+    <!-- String representing the fragment class for settings activity.-->
+    <string name="settings_fragment_name" translatable="false">com.android.launcher3.SettingsActivity$LauncherSettingsFragment</string>
+
     <!-- Values for icon shape overrides. These should correspond to entries defined
      in icon_shape_override_paths_names -->
     <string-array translatable="false" name="icon_shape_override_paths_values">
@@ -92,9 +95,6 @@
     <!-- Name of an app transition manager class. -->
     <string name="app_transition_manager_class" translatable="false"></string>
 
-    <!-- Name of a color extraction implementation class. -->
-    <string name="color_extraction_impl_class" translatable="false"></string>
-
     <!-- Name of a subclass of com.android.launcher3.util.InstantAppResolver. Can be empty. -->
     <string name="instant_app_resolver_class" translatable="false"></string>
 
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
index 923835a..9b6166f 100644
--- a/src/com/android/launcher3/AppFilter.java
+++ b/src/com/android/launcher3/AppFilter.java
@@ -3,10 +3,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 
-public class AppFilter {
+import com.android.launcher3.util.ResourceBasedOverride;
+
+public class AppFilter implements ResourceBasedOverride {
 
     public static AppFilter newInstance(Context context) {
-        return Utilities.getOverrideObject(AppFilter.class, context, R.string.app_filter_class);
+        return Overrides.getObject(AppFilter.class, context, R.string.app_filter_class);
     }
 
     public boolean shouldShowApp(ComponentName app) {
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 9dc3129..f496600 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -478,7 +478,8 @@
             info.contentDescription = "";
             info.usingLowResIcon = false;
         } else {
-            getTitleAndIcon(info, new ActivityInfoProvider(info.getIntent(), info.user),
+            Intent intent = info.getIntent();
+            getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
                     true, useLowResIcon);
         }
     }
@@ -801,7 +802,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int RELEASE_VERSION = 23;
+        private final static int RELEASE_VERSION = 24;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
@@ -869,22 +870,6 @@
         }
     }
 
-    private class ActivityInfoProvider extends Provider<LauncherActivityInfo> {
-
-        private final Intent mIntent;
-        private final UserHandle mUser;
-
-        public ActivityInfoProvider(Intent intent, UserHandle user) {
-            mIntent = intent;
-            mUser = user;
-        }
-
-        @Override
-        public LauncherActivityInfo get() {
-            return mLauncherApps.resolveActivity(mIntent, mUser);
-        }
-    }
-
     /**
      * Interface for receiving itemInfo with high-res icon.
      */
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index b469a8f..ed8d03c 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -5,14 +5,16 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 
+import com.android.launcher3.util.ResourceBasedOverride;
+
 import java.util.Locale;
 
-public class IconProvider {
+public class IconProvider implements ResourceBasedOverride {
 
     protected String mSystemState;
 
     public static IconProvider newInstance(Context context) {
-        IconProvider provider = Utilities.getOverrideObject(
+        IconProvider provider = Overrides.getObject(
                 IconProvider.class, context, R.string.icon_provider_class);
         provider.updateSystemStateString(context);
         return provider;
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index fe8a841..b9d45fb 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -47,7 +47,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 
 import org.json.JSONException;
@@ -486,13 +485,10 @@
                 if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
                     app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
                 } else {
-                    app.getModel().updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
-                        @Override
-                        public ShortcutInfo get() {
-                            app.getIconCache().getTitleAndIcon(
-                                    si, activityInfo, false /* useLowResIcon */);
-                            return si;
-                        }
+                    app.getModel().updateAndBindShortcutInfo(() -> {
+                        app.getIconCache().getTitleAndIcon(
+                                si, activityInfo, false /* useLowResIcon */);
+                        return si;
                     });
                 }
                 return Pair.create((ItemInfo) si, (Object) activityInfo);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index f63cce5..22bc162 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -29,6 +29,8 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -41,8 +43,12 @@
 
 public class InvariantDeviceProfile {
 
-    // This is a static that we use for the default icon size on a 4/5-inch phone
-    private static float DEFAULT_ICON_SIZE_DP = 60;
+    // We do not need any synchronization for this variable as its only written on UI thread.
+    public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
+            new MainThreadInitializedObject<>((c) -> {
+                new ConfigMonitor(c).register();
+                return new InvariantDeviceProfile(c);
+            });
 
     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
 
@@ -118,7 +124,7 @@
     }
 
     @TargetApi(23)
-    public InvariantDeviceProfile(Context context) {
+    private InvariantDeviceProfile(Context context) {
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = wm.getDefaultDisplay();
         DisplayMetrics dm = new DisplayMetrics();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3a8679e..9f16857 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -74,7 +74,6 @@
 import android.widget.Toast;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -741,6 +740,8 @@
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
 
+        UiFactory.onLauncherStateOrResumeChanged(this);
+
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
     }
@@ -1050,12 +1051,7 @@
     }
 
     public FolderIcon findFolderIcon(final long folderIconId) {
-        return (FolderIcon) mWorkspace.getFirstMatch(new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info != null && info.id == folderIconId;
-            }
-        });
+        return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index a46692b..5159de1 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,12 +16,13 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
+
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Looper;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -29,21 +30,17 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SettingsObserver;
 
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-
-import static com.android.launcher3.SettingsActivity.NOTIFICATION_BADGING;
-
 public class LauncherAppState {
 
     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
-    private static LauncherAppState INSTANCE;
+    private static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
+            new MainThreadInitializedObject<>((c) -> new LauncherAppState(c));
 
     private final Context mContext;
     private final LauncherModel mModel;
@@ -53,27 +50,11 @@
     private final SettingsObserver mNotificationBadgingObserver;
 
     public static LauncherAppState getInstance(final Context context) {
-        if (INSTANCE == null) {
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                INSTANCE = new LauncherAppState(context.getApplicationContext());
-            } else {
-                try {
-                    return new MainThreadExecutor().submit(new Callable<LauncherAppState>() {
-                        @Override
-                        public LauncherAppState call() throws Exception {
-                            return LauncherAppState.getInstance(context);
-                        }
-                    }).get();
-                } catch (InterruptedException|ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-        return INSTANCE;
+        return INSTANCE.get(context);
     }
 
     public static LauncherAppState getInstanceNoCreate() {
-        return INSTANCE;
+        return INSTANCE.getNoCreate();
     }
 
     public Context getContext() {
@@ -89,7 +70,7 @@
         Preconditions.assertUIThread();
         mContext = context;
 
-        mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
+        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
         mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
@@ -112,7 +93,6 @@
 
         mContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(mContext).enableAndResetCache();
-        new ConfigMonitor(mContext).register();
 
         if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) {
             mNotificationBadgingObserver = null;
@@ -171,7 +151,7 @@
      * Shorthand for {@link #getInvariantDeviceProfile()}
      */
     public static InvariantDeviceProfile getIDP(Context context) {
-        return LauncherAppState.getInstance(context).getInvariantDeviceProfile();
+        return InvariantDeviceProfile.INSTANCE.get(context);
     }
 
     private static LauncherProvider getLocalProvider(Context context) {
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 4037a23..970e558 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -23,13 +23,15 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
+import com.android.launcher3.util.ResourceBasedOverride;
+
 /**
  * Manages the opening and closing app transitions from Launcher.
  */
-public class LauncherAppTransitionManager {
+public class LauncherAppTransitionManager implements ResourceBasedOverride {
 
     public static LauncherAppTransitionManager newInstance(Context context) {
-        return Utilities.getOverrideObject(LauncherAppTransitionManager.class,
+        return Overrides.getObject(LauncherAppTransitionManager.class,
                 context, R.string.app_transition_manager_class);
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 37538ae..19aa795 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -620,15 +620,12 @@
     }
 
     public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
-        updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
-            @Override
-            public ShortcutInfo get() {
-                si.updateFromDeepShortcutInfo(info, mApp.getContext());
-                LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
-                li.createShortcutIcon(info).applyTo(si);
-                li.recycle();
-                return si;
-            }
+        updateAndBindShortcutInfo(() -> {
+            si.updateFromDeepShortcutInfo(info, mApp.getContext());
+            LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+            li.createShortcutIcon(info).applyTo(si);
+            li.recycle();
+            return si;
         });
     }
 
diff --git a/src/com/android/launcher3/LauncherScroller.java b/src/com/android/launcher3/LauncherScroller.java
index a9b4955..e5042c4 100644
--- a/src/com/android/launcher3/LauncherScroller.java
+++ b/src/com/android/launcher3/LauncherScroller.java
@@ -18,541 +18,37 @@
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
-import android.hardware.SensorManager;
-import android.os.Build;
-import android.view.ViewConfiguration;
-import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+import android.widget.Scroller;
 
 /**
- * This class differs from the framework {@link android.widget.Scroller} in that
- * you can modify the Interpolator post-construction.
+ * Extension of {@link android.widget.Scroller} with the ability to modify the
+ * Interpolator post-construction.
  */
-public class LauncherScroller  {
-    private int mMode;
+public class LauncherScroller extends Scroller {
 
-    private int mStartX;
-    private int mStartY;
-    private int mFinalX;
-    private int mFinalY;
+    private final InterpolatorWrapper mInterpolatorWrapper;
 
-    private int mMinX;
-    private int mMaxX;
-    private int mMinY;
-    private int mMaxY;
-
-    private int mCurrX;
-    private int mCurrY;
-    private long mStartTime;
-    private int mDuration;
-    private float mDurationReciprocal;
-    private float mDeltaX;
-    private float mDeltaY;
-    private boolean mFinished;
-    private TimeInterpolator mInterpolator;
-    private boolean mFlywheel;
-
-    private float mVelocity;
-    private float mCurrVelocity;
-    private int mDistance;
-
-    private float mFlingFriction = ViewConfiguration.getScrollFriction();
-
-    private static final int DEFAULT_DURATION = 250;
-    private static final int SCROLL_MODE = 0;
-    private static final int FLING_MODE = 1;
-
-    private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
-    private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
-    private static final float START_TENSION = 0.5f;
-    private static final float END_TENSION = 1.0f;
-    private static final float P1 = START_TENSION * INFLEXION;
-    private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
-
-    private static final int NB_SAMPLES = 100;
-    private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
-    private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
-
-    private float mDeceleration;
-    private final float mPpi;
-
-    // A context-specific coefficient adjusted to physical values.
-    private float mPhysicalCoeff;
-
-    static {
-        float x_min = 0.0f;
-        float y_min = 0.0f;
-        for (int i = 0; i < NB_SAMPLES; i++) {
-            final float alpha = (float) i / NB_SAMPLES;
-
-            float x_max = 1.0f;
-            float x, tx, coef;
-            while (true) {
-                x = x_min + (x_max - x_min) / 2.0f;
-                coef = 3.0f * x * (1.0f - x);
-                tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
-                if (Math.abs(tx - alpha) < 1E-5) break;
-                if (tx > alpha) x_max = x;
-                else x_min = x;
-            }
-            SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
-
-            float y_max = 1.0f;
-            float y, dy;
-            while (true) {
-                y = y_min + (y_max - y_min) / 2.0f;
-                coef = 3.0f * y * (1.0f - y);
-                dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
-                if (Math.abs(dy - alpha) < 1E-5) break;
-                if (dy > alpha) y_max = y;
-                else y_min = y;
-            }
-            SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
-        }
-        SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
-
-        // This controls the viscous fluid effect (how much of it)
-        sViscousFluidScale = 8.0f;
-        // must be set to 1.0 (used in viscousFluid())
-        sViscousFluidNormalize = 1.0f;
-        sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
-
+    public LauncherScroller(Context context) {
+        this(context, new InterpolatorWrapper());
     }
 
-    private static float sViscousFluidScale;
-    private static float sViscousFluidNormalize;
+    private LauncherScroller(Context context, InterpolatorWrapper interpolatorWrapper) {
+        super(context, interpolatorWrapper);
+        mInterpolatorWrapper = interpolatorWrapper;
+    }
 
     public void setInterpolator(TimeInterpolator interpolator) {
-        mInterpolator = interpolator;
+        mInterpolatorWrapper.interpolator = interpolator;
     }
 
-    /**
-     * Create a Scroller with the default duration and interpolator.
-     */
-    public LauncherScroller(Context context) {
-        this(context, null);
-    }
+    private static class InterpolatorWrapper implements Interpolator {
 
-    /**
-     * Create a Scroller with the specified interpolator. If the interpolator is
-     * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
-     * be in effect for apps targeting Honeycomb or newer.
-     */
-    public LauncherScroller(Context context, Interpolator interpolator) {
-        this(context, interpolator,
-                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
-    }
+        public TimeInterpolator interpolator;
 
-    /**
-     * Create a Scroller with the specified interpolator. If the interpolator is
-     * null, the default (viscous) interpolator will be used. Specify whether or
-     * not to support progressive "flywheel" behavior in flinging.
-     */
-    public LauncherScroller(Context context, Interpolator interpolator, boolean flywheel) {
-        mFinished = true;
-        mInterpolator = interpolator;
-        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
-        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
-        mFlywheel = flywheel;
-
-        mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
-    }
-
-    /**
-     * The amount of friction applied to flings. The default value
-     * is {@link ViewConfiguration#getScrollFriction}.
-     *
-     * @param friction A scalar dimension-less value representing the coefficient of
-     *         friction.
-     */
-    public final void setFriction(float friction) {
-        mDeceleration = computeDeceleration(friction);
-        mFlingFriction = friction;
-    }
-
-    private float computeDeceleration(float friction) {
-        return SensorManager.GRAVITY_EARTH   // g (m/s^2)
-                      * 39.37f               // inch/meter
-                      * mPpi                 // pixels per inch
-                      * friction;
-    }
-
-    /**
-     *
-     * Returns whether the scroller has finished scrolling.
-     *
-     * @return True if the scroller has finished scrolling, false otherwise.
-     */
-    public final boolean isFinished() {
-        return mFinished;
-    }
-
-    /**
-     * Force the finished field to a particular value.
-     *
-     * @param finished The new finished value.
-     */
-    public final void forceFinished(boolean finished) {
-        mFinished = finished;
-    }
-
-    /**
-     * Returns how long the scroll event will take, in milliseconds.
-     *
-     * @return The duration of the scroll in milliseconds.
-     */
-    public final int getDuration() {
-        return mDuration;
-    }
-
-    /**
-     * Returns the current X offset in the scroll.
-     *
-     * @return The new X offset as an absolute distance from the origin.
-     */
-    public final int getCurrX() {
-        return mCurrX;
-    }
-
-    /**
-     * Returns the current Y offset in the scroll.
-     *
-     * @return The new Y offset as an absolute distance from the origin.
-     */
-    public final int getCurrY() {
-        return mCurrY;
-    }
-
-    /**
-     * Returns the current velocity.
-     *
-     * @return The original velocity less the deceleration. Result may be
-     * negative.
-     */
-    public float getCurrVelocity() {
-        return mMode == FLING_MODE ?
-                mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
-    }
-
-    /**
-     * Returns the start X offset in the scroll.
-     *
-     * @return The start X offset as an absolute distance from the origin.
-     */
-    public final int getStartX() {
-        return mStartX;
-    }
-
-    /**
-     * Returns the start Y offset in the scroll.
-     *
-     * @return The start Y offset as an absolute distance from the origin.
-     */
-    public final int getStartY() {
-        return mStartY;
-    }
-
-    /**
-     * Returns where the scroll will end. Valid only for "fling" scrolls.
-     *
-     * @return The final X offset as an absolute distance from the origin.
-     */
-    public final int getFinalX() {
-        return mFinalX;
-    }
-
-    /**
-     * Returns where the scroll will end. Valid only for "fling" scrolls.
-     *
-     * @return The final Y offset as an absolute distance from the origin.
-     */
-    public final int getFinalY() {
-        return mFinalY;
-    }
-
-    /**
-     * Call this when you want to know the new location.  If it returns true,
-     * the animation is not yet finished.
-     */
-    public boolean computeScrollOffset() {
-        if (mFinished) {
-            return false;
+        @Override
+        public float getInterpolation(float v) {
+            return interpolator == null ? v : interpolator.getInterpolation(v);
         }
-
-        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
-
-        if (timePassed < mDuration) {
-            switch (mMode) {
-            case SCROLL_MODE:
-                float x = timePassed * mDurationReciprocal;
-
-                if (mInterpolator == null)
-                    x = viscousFluid(x);
-                else
-                    x = mInterpolator.getInterpolation(x);
-
-                mCurrX = mStartX + Math.round(x * mDeltaX);
-                mCurrY = mStartY + Math.round(x * mDeltaY);
-                break;
-            case FLING_MODE:
-                final float t = (float) timePassed / mDuration;
-                final int index = (int) (NB_SAMPLES * t);
-                float distanceCoef = 1.f;
-                float velocityCoef = 0.f;
-                if (index < NB_SAMPLES) {
-                    final float t_inf = (float) index / NB_SAMPLES;
-                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
-                    final float d_inf = SPLINE_POSITION[index];
-                    final float d_sup = SPLINE_POSITION[index + 1];
-                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
-                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
-                }
-
-                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
-
-                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
-                // Pin to mMinX <= mCurrX <= mMaxX
-                mCurrX = Math.min(mCurrX, mMaxX);
-                mCurrX = Math.max(mCurrX, mMinX);
-
-                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
-                // Pin to mMinY <= mCurrY <= mMaxY
-                mCurrY = Math.min(mCurrY, mMaxY);
-                mCurrY = Math.max(mCurrY, mMinY);
-
-                if (mCurrX == mFinalX && mCurrY == mFinalY) {
-                    mFinished = true;
-                }
-
-                break;
-            }
-        }
-        else {
-            mCurrX = mFinalX;
-            mCurrY = mFinalY;
-            mFinished = true;
-        }
-        return true;
-    }
-
-    /**
-     * Start scrolling by providing a starting point and the distance to travel.
-     * The scroll will use the default value of 250 milliseconds for the
-     * duration.
-     *
-     * @param startX Starting horizontal scroll offset in pixels. Positive
-     *        numbers will scroll the content to the left.
-     * @param startY Starting vertical scroll offset in pixels. Positive numbers
-     *        will scroll the content up.
-     * @param dx Horizontal distance to travel. Positive numbers will scroll the
-     *        content to the left.
-     * @param dy Vertical distance to travel. Positive numbers will scroll the
-     *        content up.
-     */
-    public void startScroll(int startX, int startY, int dx, int dy) {
-        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
-    }
-
-    /**
-     * Start scrolling by providing a starting point, the distance to travel,
-     * and the duration of the scroll.
-     *
-     * @param startX Starting horizontal scroll offset in pixels. Positive
-     *        numbers will scroll the content to the left.
-     * @param startY Starting vertical scroll offset in pixels. Positive numbers
-     *        will scroll the content up.
-     * @param dx Horizontal distance to travel. Positive numbers will scroll the
-     *        content to the left.
-     * @param dy Vertical distance to travel. Positive numbers will scroll the
-     *        content up.
-     * @param duration Duration of the scroll in milliseconds.
-     */
-    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
-        mMode = SCROLL_MODE;
-        mFinished = false;
-        mDuration = duration;
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mStartX = startX;
-        mStartY = startY;
-        mFinalX = startX + dx;
-        mFinalY = startY + dy;
-        mDeltaX = dx;
-        mDeltaY = dy;
-        mDurationReciprocal = 1.0f / (float) mDuration;
-    }
-
-    /**
-     * Start scrolling based on a fling gesture. The distance travelled will
-     * depend on the initial velocity of the fling.
-     *
-     * @param startX Starting point of the scroll (X)
-     * @param startY Starting point of the scroll (Y)
-     * @param velocityX Initial velocity of the fling (X) measured in pixels per
-     *        second.
-     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
-     *        second
-     * @param minX Minimum X value. The scroller will not scroll past this
-     *        point.
-     * @param maxX Maximum X value. The scroller will not scroll past this
-     *        point.
-     * @param minY Minimum Y value. The scroller will not scroll past this
-     *        point.
-     * @param maxY Maximum Y value. The scroller will not scroll past this
-     *        point.
-     */
-    public void fling(int startX, int startY, int velocityX, int velocityY,
-            int minX, int maxX, int minY, int maxY) {
-        // Continue a scroll or fling in progress
-        if (mFlywheel && !mFinished) {
-            float oldVel = getCurrVelocity();
-
-            float dx = (float) (mFinalX - mStartX);
-            float dy = (float) (mFinalY - mStartY);
-            float hyp = (float) Math.hypot(dx, dy);
-
-            float ndx = dx / hyp;
-            float ndy = dy / hyp;
-
-            float oldVelocityX = ndx * oldVel;
-            float oldVelocityY = ndy * oldVel;
-            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
-                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
-                velocityX += oldVelocityX;
-                velocityY += oldVelocityY;
-            }
-        }
-
-        mMode = FLING_MODE;
-        mFinished = false;
-
-        float velocity = (float) Math.hypot(velocityX, velocityY);
-
-        mVelocity = velocity;
-        mDuration = getSplineFlingDuration(velocity);
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mStartX = startX;
-        mStartY = startY;
-
-        float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
-        float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
-
-        double totalDistance = getSplineFlingDistance(velocity);
-        mDistance = (int) (totalDistance * Math.signum(velocity));
-
-        mMinX = minX;
-        mMaxX = maxX;
-        mMinY = minY;
-        mMaxY = maxY;
-
-        mFinalX = startX + (int) Math.round(totalDistance * coeffX);
-        // Pin to mMinX <= mFinalX <= mMaxX
-        mFinalX = Math.min(mFinalX, mMaxX);
-        mFinalX = Math.max(mFinalX, mMinX);
-
-        mFinalY = startY + (int) Math.round(totalDistance * coeffY);
-        // Pin to mMinY <= mFinalY <= mMaxY
-        mFinalY = Math.min(mFinalY, mMaxY);
-        mFinalY = Math.max(mFinalY, mMinY);
-    }
-
-    private double getSplineDeceleration(float velocity) {
-        return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
-    }
-
-    private int getSplineFlingDuration(float velocity) {
-        final double l = getSplineDeceleration(velocity);
-        final double decelMinusOne = DECELERATION_RATE - 1.0;
-        return (int) (1000.0 * Math.exp(l / decelMinusOne));
-    }
-
-    private double getSplineFlingDistance(float velocity) {
-        final double l = getSplineDeceleration(velocity);
-        final double decelMinusOne = DECELERATION_RATE - 1.0;
-        return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
-    }
-
-    static float viscousFluid(float x)
-    {
-        x *= sViscousFluidScale;
-        if (x < 1.0f) {
-            x -= (1.0f - (float)Math.exp(-x));
-        } else {
-            float start = 0.36787944117f;   // 1/e == exp(-1)
-            x = 1.0f - (float)Math.exp(1.0f - x);
-            x = start + x * (1.0f - start);
-        }
-        x *= sViscousFluidNormalize;
-        return x;
-    }
-
-    /**
-     * Stops the animation. Contrary to {@link #forceFinished(boolean)},
-     * aborting the animating cause the scroller to move to the final x and y
-     * position
-     *
-     * @see #forceFinished(boolean)
-     */
-    public void abortAnimation() {
-        mCurrX = mFinalX;
-        mCurrY = mFinalY;
-        mFinished = true;
-    }
-
-    /**
-     * Extend the scroll animation. This allows a running animation to scroll
-     * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
-     *
-     * @param extend Additional time to scroll in milliseconds.
-     * @see #setFinalX(int)
-     * @see #setFinalY(int)
-     */
-    public void extendDuration(int extend) {
-        int passed = timePassed();
-        mDuration = passed + extend;
-        mDurationReciprocal = 1.0f / mDuration;
-        mFinished = false;
-    }
-
-    /**
-     * Returns the time elapsed since the beginning of the scrolling.
-     *
-     * @return The elapsed time in milliseconds.
-     */
-    public int timePassed() {
-        return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
-    }
-
-    /**
-     * Sets the final position (X) for this scroller.
-     *
-     * @param newX The new X offset as an absolute distance from the origin.
-     * @see #extendDuration(int)
-     * @see #setFinalY(int)
-     */
-    public void setFinalX(int newX) {
-        mFinalX = newX;
-        mDeltaX = mFinalX - mStartX;
-        mFinished = false;
-    }
-
-    /**
-     * Sets the final position (Y) for this scroller.
-     *
-     * @param newY The new Y offset as an absolute distance from the origin.
-     * @see #extendDuration(int)
-     * @see #setFinalX(int)
-     */
-    public void setFinalY(int newY) {
-        mFinalY = newY;
-        mDeltaY = mFinalY - mStartY;
-        mFinished = false;
-    }
-
-    /**
-     * @hide
-     */
-    public boolean isScrollingInDirection(float xvel, float yvel) {
-        return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
-                Math.signum(yvel) == Math.signum(mFinalY - mStartY);
     }
 }
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 462eadb..0028f97 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -20,14 +20,15 @@
 
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
  * Utility class to handle one time initializations of the main process
  */
-public class MainProcessInitializer {
+public class MainProcessInitializer implements ResourceBasedOverride {
 
     public static void initialize(Context context) {
-        Utilities.getOverrideObject(
+        Overrides.getObject(
                 MainProcessInitializer.class, context, R.string.main_process_initializer_class)
                 .init(context);
     }
diff --git a/src/com/android/launcher3/OverviewButtonClickListener.java b/src/com/android/launcher3/OverviewButtonClickListener.java
deleted file mode 100644
index dd670d2..0000000
--- a/src/com/android/launcher3/OverviewButtonClickListener.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.android.launcher3;
-
-import android.view.View;
-
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-
-/**
- * A specialized listener for Overview buttons where both clicks and long clicks are logged
- * handled the same via {@link #handleViewClick(View)}.
- */
-public abstract class OverviewButtonClickListener implements View.OnClickListener,
-        View.OnLongClickListener {
-
-    private int mControlType; /** ControlType enum as defined in {@link Action.Touch} */
-
-    public OverviewButtonClickListener(int controlType) {
-        mControlType = controlType;
-    }
-
-    public void attachTo(View v) {
-        v.setOnClickListener(this);
-        v.setOnLongClickListener(this);
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (shouldPerformClick(view)) {
-            handleViewClick(view, Action.Touch.TAP);
-        }
-    }
-
-    @Override
-    public boolean onLongClick(View view) {
-        if (shouldPerformClick(view)) {
-            handleViewClick(view, Action.Touch.LONGPRESS);
-        }
-        return true;
-    }
-
-    private boolean shouldPerformClick(View view) {
-        return !Launcher.getLauncher(view.getContext()).getWorkspace().isSwitchingState();
-    }
-
-    private void handleViewClick(View view, int action) {
-        handleViewClick(view);
-        Launcher.getLauncher(view.getContext()).getUserEventDispatcher()
-                .logActionOnControl(action, mControlType);
-    }
-
-    public abstract void handleViewClick(View view);
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 32c198a..8589b7e 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -24,6 +24,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
+import android.app.Fragment;
 import android.app.FragmentManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -53,7 +54,8 @@
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
  */
-public class SettingsActivity extends Activity {
+public class SettingsActivity extends Activity
+        implements PreferenceFragment.OnPreferenceStartFragmentCallback {
 
     private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
@@ -71,9 +73,10 @@
         super.onCreate(savedInstanceState);
 
         if (savedInstanceState == null) {
+            Fragment f = Fragment.instantiate(this, getString(R.string.settings_fragment_name));
             // Display the fragment as the main content.
             getFragmentManager().beginTransaction()
-                    .replace(android.R.id.content, getNewFragment())
+                    .replace(android.R.id.content, f)
                     .commit();
         }
     }
@@ -82,6 +85,22 @@
         return new LauncherSettingsFragment();
     }
 
+    @Override
+    public boolean onPreferenceStartFragment(
+            PreferenceFragment preferenceFragment, Preference pref) {
+        Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
+        if (f instanceof DialogFragment) {
+            ((DialogFragment) f).show(getFragmentManager(), pref.getKey());
+        } else {
+            getFragmentManager()
+                    .beginTransaction()
+                    .replace(android.R.id.content, f)
+                    .addToBackStack(pref.getKey())
+                    .commit();
+        }
+        return true;
+    }
+
     /**
      * This fragment shows the launcher preferences.
      */
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7fe8d35..8683b21 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -55,7 +55,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.HashSet;
@@ -581,25 +580,6 @@
                 || e.getCause() instanceof DeadObjectException;
     }
 
-    public static <T> T getOverrideObject(Class<T> clazz, Context context, int resId) {
-        String className = context.getString(resId);
-        if (!TextUtils.isEmpty(className)) {
-            try {
-                Class<?> cls = Class.forName(className);
-                return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
-            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
-                    | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
-                Log.e(TAG, "Bad overriden class", e);
-            }
-        }
-
-        try {
-            return clazz.newInstance();
-        } catch (InstantiationException|IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
     /**
      * Returns a HashSet with a single element. We use this instead of Collections.singleton()
      * because HashSet ensures all operations, such as remove, are supported.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index abba9c4..67bdd3b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3034,16 +3034,6 @@
         });
     }
 
-    public View getViewForTag(final Object tag) {
-        return getFirstMatch(new ItemOperator() {
-
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                return info == tag;
-            }
-        });
-    }
-
     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
         return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
 
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 8a1abf4..675e26d 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,7 +16,10 @@
 
 package com.android.launcher3.anim;
 
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+
 import android.graphics.Path;
+import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -45,6 +48,8 @@
     public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
     public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
 
+    public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
+
     public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
 
     public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
@@ -52,6 +57,9 @@
 
     public static final Interpolator EXAGGERATED_EASE;
 
+    private static final int MIN_SETTLE_DURATION = 200;
+    private static final float OVERSHOOT_FACTOR = 0.9f;
+
     static {
         Path exaggeratedEase = new Path();
         exaggeratedEase.moveTo(0, 0);
@@ -112,46 +120,18 @@
         }
     };
 
-    /**
-     * Interpolates using a particular section of the damped oscillation function.
-     * The section is selected by scaling and shifting the function.
-     */
-    public static final Interpolator OSCILLATE = new Interpolator() {
-
-        // Used to scale the oscillations horizontally
-        private final float horizontalScale = 1f;
-        // Used to shift the oscillations horizontally
-        private final float horizontalShift = 0.5f;
-        // Used to scale the oscillations vertically
-        private final float verticalScale = 1f;
-        // Used to shift the oscillations vertically
-        private final float verticalShift = 1f;
-
-        @Override
-        public float getInterpolation(float t) {
-            t = horizontalScale * (t + horizontalShift);
-            return (float) ((verticalScale * (Math.exp(-t) * Math.cos(2 * Math.PI * t)))
-                    + verticalShift);
-        }
-    };
-
     private static final float FAST_FLING_PX_MS = 10;
 
     public static Interpolator scrollInterpolatorForVelocity(float velocity) {
         return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC;
     }
 
-    public static Interpolator overshootInterpolatorForVelocity(float velocity) {
-        return overshootInterpolatorForVelocity(velocity, 1f);
-    }
-
     /**
      * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms).
      * @param velocity The start velocity of the animation we want to overshoot.
-     * @param dampFactor An optional factor to reduce the amount of tension (how far we overshoot).
      */
-    public static Interpolator overshootInterpolatorForVelocity(float velocity, float dampFactor) {
-        return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f) / dampFactor);
+    public static Interpolator overshootInterpolatorForVelocity(float velocity) {
+        return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f));
     }
 
     /**
@@ -183,4 +163,75 @@
             float upperBound) {
         return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
     }
-}
\ No newline at end of file
+
+    /**
+     * Computes parameters necessary for an overshoot effect.
+     */
+    public static class OvershootParams {
+        public Interpolator interpolator;
+        public float start;
+        public float end;
+        public long duration;
+
+        /**
+         * Given the input params, sets OvershootParams variables to be used by the caller.
+         * @param startProgress The progress from 0 to 1 that the overshoot starts from.
+         * @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should
+         *        either be equal to startProgress or endProgress, depending on if we want to
+         *        overshoot immediately or only once we reach the end).
+         * @param endProgress The final progress from 0 to 1 that we will settle to.
+         * @param velocityPxPerMs The initial velocity that causes this overshoot.
+         * @param totalDistancePx The distance against which progress is calculated.
+         */
+        public OvershootParams(float startProgress, float overshootPastProgress,
+                float endProgress, float velocityPxPerMs, int totalDistancePx) {
+            velocityPxPerMs = Math.abs(velocityPxPerMs);
+            start = startProgress;
+            int startPx = (int) (start * totalDistancePx);
+            // Overshoot by about half a frame.
+            float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
+                    SINGLE_FRAME_MS / totalDistancePx / 2;
+            overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
+            end = overshootPastProgress + overshootBy;
+            int endPx = (int) (end  * totalDistancePx);
+            int overshootDistance = endPx - startPx;
+            // Calculate deceleration necessary to reach overshoot distance.
+            // Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance
+            //          0 = v^2 + 2ad (velocityFinal == 0)
+            //          a = v^2 / -2d
+            float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance);
+            // Calculate time necessary to reach peak of overshoot.
+            // Formula: acceleration = velocity / time
+            //          time = velocity / acceleration
+            duration = (long) (velocityPxPerMs / decelerationPxPerMs);
+
+            // Now that we're at the top of the overshoot, need to settle back to endProgress.
+            float settleDistance = end - endProgress;
+            int settleDistancePx = (int) (settleDistance * totalDistancePx);
+            // Calculate time necessary for the settle.
+            // Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2
+            //          d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top)
+            //          t = sqrt(2d/a)
+            // Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually
+            // have acceleration to halfway then deceleration the rest. So the formula becomes:
+            //          t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel)
+            long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 4;
+
+            settleDuration = Math.max(MIN_SETTLE_DURATION, settleDuration);
+            // How much of the animation to devote to playing the overshoot (the rest is for settle).
+            float overshootFraction = (float) duration / (duration + settleDuration);
+            duration += settleDuration;
+            // Finally, create the interpolator, composed of two interpolators: an overshoot, which
+            // reaches end > 1, and then a settle to endProgress.
+            Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction);
+            // The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction
+            // such that final progress is endProgress. For example, if we overshot to 1.1 but want
+            // to end at 1, we need to map to 1/1.1.
+            Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress(
+                    ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1);
+            interpolator = t -> t <= overshootFraction
+                    ? overshoot.getInterpolation(t)
+                    : settle.getInterpolation(t);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/graphics/BitmapRenderer.java b/src/com/android/launcher3/graphics/BitmapRenderer.java
index 3d11c44..2a7f20e 100644
--- a/src/com/android/launcher3/graphics/BitmapRenderer.java
+++ b/src/com/android/launcher3/graphics/BitmapRenderer.java
@@ -23,32 +23,30 @@
 
 import com.android.launcher3.Utilities;
 
-public class BitmapRenderer {
+/**
+ * Interface representing a bitmap draw operation.
+ */
+public interface BitmapRenderer {
 
-     public static final boolean USE_HARDWARE_BITMAP = Utilities.ATLEAST_P;
+    boolean USE_HARDWARE_BITMAP = Utilities.ATLEAST_P;
 
-     public static Bitmap createSoftwareBitmap(int width, int height, Renderer renderer) {
-          Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-          renderer.draw(new Canvas(result));
-          return result;
-     }
+    static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
+        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        renderer.draw(new Canvas(result));
+        return result;
+    }
 
-     @TargetApi(Build.VERSION_CODES.P)
-     public static Bitmap createHardwareBitmap(int width, int height, Renderer renderer) {
-          if (!USE_HARDWARE_BITMAP) {
-               return createSoftwareBitmap(width, height, renderer);
-          }
+    @TargetApi(Build.VERSION_CODES.P)
+    static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
+        if (!USE_HARDWARE_BITMAP) {
+            return createSoftwareBitmap(width, height, renderer);
+        }
 
-          Picture picture = new Picture();
-          renderer.draw(picture.beginRecording(width, height));
-          picture.endRecording();
-          return Bitmap.createBitmap(picture);
-     }
+        Picture picture = new Picture();
+        renderer.draw(picture.beginRecording(width, height));
+        picture.endRecording();
+        return Bitmap.createBitmap(picture);
+    }
 
-     /**
-      * Interface representing a bitmap draw operation.
-      */
-     public interface Renderer {
-          void draw(Canvas out);
-     }
+    void draw(Canvas out);
 }
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 34a4e2d..bbc013d 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -36,11 +36,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsBackgroundDrawable;
+import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
  * Factory for creating new drawables.
  */
-public class DrawableFactory {
+public class DrawableFactory implements ResourceBasedOverride {
 
     private static final String TAG = "DrawableFactory";
 
@@ -52,7 +53,7 @@
     public static DrawableFactory get(Context context) {
         synchronized (LOCK) {
             if (sInstance == null) {
-                sInstance = Utilities.getOverrideObject(DrawableFactory.class,
+                sInstance = Overrides.getObject(DrawableFactory.class,
                         context.getApplicationContext(), R.string.drawable_factory_class);
             }
             return sInstance;
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 09ea1ad..333fe59 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -340,7 +340,7 @@
         if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
             int offset = Math.max((int) Math.ceil(BLUR_FACTOR * textureWidth), Math.max(left, top));
             int size = Math.max(width, height);
-            icon.setBounds(offset, offset, offset + size, offset + size);
+            icon.setBounds(offset, offset, size - offset, size - offset);
         } else {
             icon.setBounds(left, top, left+width, top+height);
         }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index d1e1051..d9d3f68 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -51,6 +51,7 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.LogConfig;
+import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.Locale;
 import java.util.UUID;
@@ -61,7 +62,7 @@
  *
  * $ adb shell setprop log.tag.UserEvent VERBOSE
  */
-public class UserEventDispatcher {
+public class UserEventDispatcher implements ResourceBasedOverride {
 
     private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
 
@@ -78,7 +79,7 @@
             uuidStr = UUID.randomUUID().toString();
             sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply();
         }
-        UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
+        UserEventDispatcher ued = Overrides.getObject(UserEventDispatcher.class,
                 context.getApplicationContext(), R.string.user_event_dispatcher_class);
         ued.mDelegate = delegate;
         ued.mIsInLandscapeMode = dp.isVerticalBarLayout();
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 06da843..e82c8f1 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -478,15 +478,11 @@
                                     }
                                     info = new ShortcutInfo(pinnedShortcut, context);
                                     final ShortcutInfo finalInfo = info;
-                                    Provider<Bitmap> fallbackIconProvider = new Provider<Bitmap>() {
-                                        @Override
-                                        public Bitmap get() {
-                                            // If the pinned deep shortcut is no longer published,
-                                            // use the last saved icon instead of the default.
-                                            return c.loadIcon(finalInfo)
-                                                    ? finalInfo.iconBitmap : null;
-                                        }
-                                    };
+                                    // If the pinned deep shortcut is no longer published,
+                                    // use the last saved icon instead of the default.
+                                    Provider<Bitmap> fallbackIconProvider = () ->
+                                            c.loadIcon(finalInfo) ? finalInfo.iconBitmap : null;
+
                                     LauncherIcons li = LauncherIcons.obtain(context);
                                     li.createShortcutIcon(pinnedShortcut,
                                             true /* badged */, fallbackIconProvider).applyTo(info);
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index b527b6a..ac5aaf8 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -197,7 +197,7 @@
         mNotificationBadgingObserver = new SettingsObserver.Secure(getContentResolver()) {
             @Override
             public void onSettingChanged(boolean isNotificationBadgingEnabled) {
-                if (!isNotificationBadgingEnabled) {
+                if (!isNotificationBadgingEnabled && sIsConnected) {
                     requestUnbind();
                 }
             }
diff --git a/src/com/android/launcher3/util/FloatRange.java b/src/com/android/launcher3/util/FloatRange.java
deleted file mode 100644
index 12772f3..0000000
--- a/src/com/android/launcher3/util/FloatRange.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-/**
- * A mutable class for describing the range of two int values.
- */
-public class FloatRange {
-
-    public float start, end;
-
-    public FloatRange() { }
-
-    public FloatRange(float s, float e) {
-        set(s, e);
-    }
-
-    public void set(float s, float e) {
-        start = s;
-        end = e;
-    }
-
-    public boolean contains(float value) {
-        return value >= start && value <= end;
-    }
-}
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index 4485427..5dc7af8 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -23,7 +23,6 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 
 import java.util.Collections;
 import java.util.List;
@@ -31,10 +30,10 @@
 /**
  * A wrapper class to access instant app related APIs.
  */
-public class InstantAppResolver {
+public class InstantAppResolver implements ResourceBasedOverride {
 
     public static InstantAppResolver newInstance(Context context) {
-        return Utilities.getOverrideObject(
+        return Overrides.getObject(
                 InstantAppResolver.class, context, R.string.instant_app_resolver_class);
     }
 
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index daedaef..19cf6c1 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -18,7 +18,6 @@
 
 import android.content.ComponentName;
 import android.os.UserHandle;
-import android.util.SparseLongArray;
 
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
@@ -32,14 +31,14 @@
 /**
  * A utility class to check for {@link ItemInfo}
  */
-public abstract class ItemInfoMatcher {
+public interface ItemInfoMatcher {
 
-    public abstract boolean matches(ItemInfo info, ComponentName cn);
+    boolean matches(ItemInfo info, ComponentName cn);
 
     /**
      * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}.
      */
-    public final HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
+    default HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
         HashSet<ItemInfo> filtered = new HashSet<>();
         for (ItemInfo i : infos) {
             if (i instanceof ShortcutInfo) {
@@ -70,88 +69,43 @@
     /**
      * Returns a new matcher with returns true if either this or {@param matcher} returns true.
      */
-    public ItemInfoMatcher or(final ItemInfoMatcher matcher) {
-       final ItemInfoMatcher that = this;
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return that.matches(info, cn) || matcher.matches(info, cn);
-            }
-        };
+    default ItemInfoMatcher or(ItemInfoMatcher matcher) {
+        return (info, cn) -> matches(info, cn) || matcher.matches(info, cn);
     }
 
     /**
      * Returns a new matcher with returns true if both this and {@param matcher} returns true.
      */
-    public ItemInfoMatcher and(final ItemInfoMatcher matcher) {
-        final ItemInfoMatcher that = this;
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return that.matches(info, cn) && matcher.matches(info, cn);
-            }
-        };
+    default ItemInfoMatcher and(ItemInfoMatcher matcher) {
+        return (info, cn) -> matches(info, cn) && matcher.matches(info, cn);
     }
 
     /**
      * Returns a new matcher which returns the opposite boolean value of the provided
      * {@param matcher}.
      */
-    public static ItemInfoMatcher not(final ItemInfoMatcher matcher) {
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return !matcher.matches(info, cn);
-            }
-        };
+    static ItemInfoMatcher not(ItemInfoMatcher matcher) {
+        return (info, cn) -> !matcher.matches(info, cn);
     }
 
-    public static ItemInfoMatcher ofUser(final UserHandle user) {
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return info.user.equals(user);
-            }
-        };
+    static ItemInfoMatcher ofUser(UserHandle user) {
+        return (info, cn) -> info.user.equals(user);
     }
 
-    public static ItemInfoMatcher ofComponents(
-            final HashSet<ComponentName> components, final UserHandle user) {
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return components.contains(cn) && info.user.equals(user);
-            }
-        };
+    static ItemInfoMatcher ofComponents(HashSet<ComponentName> components, UserHandle user) {
+        return (info, cn) -> components.contains(cn) && info.user.equals(user);
     }
 
-    public static ItemInfoMatcher ofPackages(
-            final HashSet<String> packageNames, final UserHandle user) {
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return packageNames.contains(cn.getPackageName()) && info.user.equals(user);
-            }
-        };
+    static ItemInfoMatcher ofPackages(HashSet<String> packageNames, UserHandle user) {
+        return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user);
     }
 
-    public static ItemInfoMatcher ofShortcutKeys(final HashSet<ShortcutKey> keys) {
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+    static ItemInfoMatcher ofShortcutKeys(HashSet<ShortcutKey> keys) {
+        return  (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
                         keys.contains(ShortcutKey.fromItemInfo(info));
-            }
-        };
     }
 
-    public static ItemInfoMatcher ofItemIds(
-            final LongArrayMap<Boolean> ids, final Boolean matchDefault) {
-        return new ItemInfoMatcher() {
-            @Override
-            public boolean matches(ItemInfo info, ComponentName cn) {
-                return ids.get(info.id, matchDefault);
-            }
-        };
+    static ItemInfoMatcher ofItemIds(LongArrayMap<Boolean> ids, Boolean matchDefault) {
+        return (info, cn) -> ids.get(info.id, matchDefault);
     }
 }
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
new file mode 100644
index 0000000..5747db1
--- /dev/null
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -0,0 +1,60 @@
+/*
+ * 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.content.Context;
+import android.os.Looper;
+
+import com.android.launcher3.MainThreadExecutor;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Utility class for defining singletons which are initiated on main thread.
+ */
+public class MainThreadInitializedObject<T> {
+
+    private final ObjectProvider<T> mProvider;
+    private T mValue;
+
+    public MainThreadInitializedObject(ObjectProvider<T> provider) {
+        mProvider = provider;
+    }
+
+    public T get(Context context) {
+        if (mValue == null) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                mValue = mProvider.get(context.getApplicationContext());
+            } else {
+                try {
+                    return new MainThreadExecutor().submit(() -> get(context)).get();
+                } catch (InterruptedException|ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return mValue;
+    }
+
+    public T getNoCreate() {
+        return mValue;
+    }
+
+    public interface ObjectProvider<T> {
+
+        T get(Context context);
+    }
+}
diff --git a/src/com/android/launcher3/util/Provider.java b/src/com/android/launcher3/util/Provider.java
index 1cdd8d6..4a54c0f 100644
--- a/src/com/android/launcher3/util/Provider.java
+++ b/src/com/android/launcher3/util/Provider.java
@@ -19,20 +19,15 @@
 /**
  * Utility class to allow lazy initialization of objects.
  */
-public abstract class Provider<T> {
+public interface Provider<T> {
 
     /**
      * Initializes and returns the object. This may contain expensive operations not suitable
      * to UI thread.
      */
-    public abstract T get();
+    T get();
 
-    public static <T> Provider<T> of (final T value) {
-        return new Provider<T>() {
-            @Override
-            public T get() {
-                return value;
-            }
-        };
+    static <T> Provider<T> of (T value) {
+        return() -> value;
     }
 }
diff --git a/src/com/android/launcher3/util/ResourceBasedOverride.java b/src/com/android/launcher3/util/ResourceBasedOverride.java
new file mode 100644
index 0000000..e2c4992
--- /dev/null
+++ b/src/com/android/launcher3/util/ResourceBasedOverride.java
@@ -0,0 +1,54 @@
+/*
+ * 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.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * An interface to indicate that a class is dynamically loaded using resource overlay, hence its
+ * class name and constructor should be preserved by proguard
+ */
+public interface ResourceBasedOverride {
+
+    class Overrides {
+
+        private static final String TAG = "Overrides";
+
+        public static <T extends ResourceBasedOverride> T getObject(
+                Class<T> clazz, Context context, int resId) {
+            String className = context.getString(resId);
+            if (!TextUtils.isEmpty(className)) {
+                try {
+                    Class<?> cls = Class.forName(className);
+                    return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
+                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                        | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+                    Log.e(TAG, "Bad overriden class", e);
+                }
+            }
+
+            try {
+                return clazz.newInstance();
+            } catch (InstantiationException|IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java
index 2107094..56e3260 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java
@@ -56,7 +56,7 @@
     private WallpaperColorInfo(Context context) {
         mWallpaperManager = WallpaperManagerCompat.getInstance(context);
         mWallpaperManager.addOnColorsChangedListener(this);
-        mExtractionType = ColorExtractionAlgorithm.newInstance(context);
+        mExtractionType = new ColorExtractionAlgorithm();
         update(mWallpaperManager.getWallpaperColors(FLAG_SYSTEM));
     }
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
index 0444212..21b324f 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.uioverrides.dynamicui;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -25,7 +24,6 @@
 import android.util.Pair;
 import android.util.Range;
 
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
 import java.util.Arrays;
@@ -37,11 +35,6 @@
  **/
 public class ColorExtractionAlgorithm {
 
-    public static ColorExtractionAlgorithm newInstance(Context context) {
-        return Utilities.getOverrideObject(ColorExtractionAlgorithm.class,
-                context.getApplicationContext(), R.string.color_extraction_impl_class);
-    }
-
     private static final String TAG = "Tonal";
 
     // Used for tonal palette fitting