Merge "Remove USER_EVENT_DISPATCHER feature flag Bug: 165675920" into ub-launcher3-master
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f84a82e..f36439d 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -46,6 +46,12 @@
             tools:node="replace" >
         </activity>
 
+        <service
+            android:name="com.android.launcher3.notification.NotificationListener"
+            android:label="@string/notification_dots_service_title"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+            android:enabled="false"
+            tools:node="replace" />
     </application>
 
 </manifest>
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 3b3dc01..89b3831 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -41,6 +41,7 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = true;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
 
     private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
 
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index d64967b..02c6162 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -24,6 +24,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.UiThreadHelper;
 
 
@@ -130,6 +131,10 @@
     public void reset() {
         if (!TextUtils.isEmpty(getText())) {
             setText("");
+        } else {
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                return;
+            }
         }
         if (isFocused()) {
             View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 99fff4d..0684fe0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -44,6 +44,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.core.os.BuildCompat;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -110,7 +111,7 @@
 
     private final MultiValueAlpha mMultiValueAlpha;
 
-    Rect mInsets = new Rect();
+    private Rect mInsets = new Rect();
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -206,6 +207,17 @@
         mAH[AdapterHolder.WORK].applyPadding();
     }
 
+    private void hideInput() {
+        if (!BuildCompat.isAtLeastR() || !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
+
+        WindowInsets insets = getRootWindowInsets();
+        if (insets == null) return;
+
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            getWindowInsetsController().hide(WindowInsets.Type.ime());
+        }
+    }
+
     /**
      * Returns whether the view itself will handle the touch event or not.
      */
@@ -221,9 +233,14 @@
         }
         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
+            hideInput();
             return false;
         }
-        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        if (shouldScroll) {
+            hideInput();
+        }
+        return shouldScroll;
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 7e45021..cf32514 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -297,8 +297,7 @@
                         .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                         .map(ShortcutKey::fromItemInfo),
                     // Pending shortcuts
-                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts()
-                        .stream().filter(si -> si.user.equals(user)))
+                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
                 .collect(groupingBy(ShortcutKey::getPackageName,
                         mapping(ShortcutKey::getId, Collectors.toSet())));
 
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 6238db3..5e48a0f 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
 import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -30,11 +29,9 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
-import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
@@ -47,27 +44,19 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PersistedItemArray;
 import com.android.launcher3.util.Preconditions;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Class to maintain a queue of pending items to be added to the workspace.
@@ -79,7 +68,6 @@
     public static final int FLAG_DRAG_AND_DROP = 4;
 
     private static final String TAG = "InstallShortcutReceiver";
-    private static final boolean DBG = false;
 
     // The set of shortcuts that are pending install
     private static final String APPS_PENDING_INSTALL = "apps_to_install";
@@ -90,24 +78,34 @@
     public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
             new MainThreadInitializedObject<>(ItemInstallQueue::new);
 
+    private final PersistedItemArray<PendingInstallShortcutInfo> mStorage =
+            new PersistedItemArray<>(APPS_PENDING_INSTALL);
     private final Context mContext;
 
     // Determines whether to defer installing shortcuts immediately until
     // processAllPendingInstalls() is called.
     private int mInstallQueueDisabledFlags = 0;
 
+    // Only accessed on worker thread
+    private List<PendingInstallShortcutInfo> mItems;
+
     private ItemInstallQueue(Context context) {
         mContext = context;
     }
 
     @WorkerThread
+    private void ensureQueueLoaded() {
+        Preconditions.assertWorkerThread();
+        if (mItems == null) {
+            mItems = mStorage.read(mContext, this::decode);
+        }
+    }
+
+    @WorkerThread
     private void addToQueue(PendingInstallShortcutInfo info) {
-        String encoded = info.encodeToString(mContext);
-        SharedPreferences prefs = Utilities.getPrefs(mContext);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
-        strings.add(encoded);
-        prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+        ensureQueueLoaded();
+        mItems.add(info);
+        mStorage.write(mContext, mItems);
     }
 
     @WorkerThread
@@ -117,28 +115,21 @@
             // Launcher not loaded
             return;
         }
-
-        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-        SharedPreferences prefs = Utilities.getPrefs(mContext);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-        if (strings == null) {
+        ensureQueueLoaded();
+        if (mItems.isEmpty()) {
             return;
         }
 
-        for (String encoded : strings) {
-            PendingInstallShortcutInfo info = decode(encoded, mContext);
-            if (info == null) {
-                continue;
-            }
+        List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
+                .map(info -> info.getItemInfo(mContext))
+                .collect(Collectors.toList());
 
-            // Generate a shortcut info to add into the model
-            installQueue.add(info.getItemInfo(mContext));
-        }
-        prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+        // Add the items and clear queue
         if (!installQueue.isEmpty()) {
             launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
         }
+        mItems.clear();
+        mStorage.getFile(mContext).delete();
     }
 
     /**
@@ -149,33 +140,11 @@
         if (packageNames.isEmpty()) {
             return;
         }
-        Preconditions.assertWorkerThread();
-
-        SharedPreferences sp = Utilities.getPrefs(mContext);
-        Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) {
-            Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
-                    + ", removing packages: " + packageNames);
+        ensureQueueLoaded();
+        if (mItems.removeIf(item ->
+                item.user.equals(user) && packageNames.contains(getIntentPackage(item.intent)))) {
+            mStorage.write(mContext, mItems);
         }
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            return;
-        }
-        Set<String> newStrings = new HashSet<>(strings);
-        Iterator<String> newStringsIter = newStrings.iterator();
-        while (newStringsIter.hasNext()) {
-            String encoded = newStringsIter.next();
-            try {
-                Decoder decoder = new Decoder(encoded, mContext);
-                if (packageNames.contains(getIntentPackage(decoder.intent))
-                        && user.equals(decoder.user)) {
-                    newStringsIter.remove();
-                }
-            } catch (JSONException | URISyntaxException e) {
-                Log.d(TAG, "Exception reading shortcut to add: " + e);
-                newStringsIter.remove();
-            }
-        }
-        sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
     }
 
     /**
@@ -200,28 +169,14 @@
     }
 
     /**
-     * Returns all pending shorts in the queue
+     * Returns a stream of all pending shortcuts in the queue
      */
     @WorkerThread
-    public HashSet<ShortcutKey> getPendingShortcuts() {
-        HashSet<ShortcutKey> result = new HashSet<>();
-
-        Set<String> strings = Utilities.getPrefs(mContext).getStringSet(APPS_PENDING_INSTALL, null);
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            return result;
-        }
-
-        for (String encoded : strings) {
-            try {
-                Decoder decoder = new Decoder(encoded, mContext);
-                if (decoder.optInt(Favorites.ITEM_TYPE, -1) == ITEM_TYPE_DEEP_SHORTCUT) {
-                    result.add(ShortcutKey.fromIntent(decoder.intent, decoder.user));
-                }
-            } catch (JSONException | URISyntaxException e) {
-                Log.d(TAG, "Exception reading shortcut to add: " + e);
-            }
-        }
-        return result;
+    public Stream<ShortcutKey> getPendingShortcuts(UserHandle user) {
+        ensureQueueLoaded();
+        return mItems.stream()
+                .filter(item -> item.itemType == ITEM_TYPE_DEEP_SHORTCUT && user.equals(item.user))
+                .map(item -> ShortcutKey.fromIntent(item.intent, user));
     }
 
     private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
@@ -293,19 +248,9 @@
             providerInfo = info;
         }
 
-        public String encodeToString(Context context) {
-            try {
-                return new JSONStringer()
-                        .object()
-                        .key(Favorites.ITEM_TYPE).value(itemType)
-                        .key(Favorites.INTENT).value(intent.toUri(0))
-                        .key(PROFILE_ID).value(
-                                UserCache.INSTANCE.get(context).getSerialNumberForUser(user))
-                        .endObject().toString();
-            } catch (JSONException e) {
-                Log.d(TAG, "Exception when adding shortcut: " + e);
-                return null;
-            }
+        @Override
+        public Intent getIntent() {
+            return intent;
         }
 
         public Pair<ItemInfo, Object> getItemInfo(Context context) {
@@ -365,55 +310,33 @@
                 ? intent.getPackage() : intent.getComponent().getPackageName();
     }
 
-    private static PendingInstallShortcutInfo decode(String encoded, Context context) {
-        try {
-            Decoder decoder = new Decoder(encoded, context);
-            switch (decoder.optInt(Favorites.ITEM_TYPE, -1)) {
-                case Favorites.ITEM_TYPE_APPLICATION:
-                    return new PendingInstallShortcutInfo(
-                            decoder.intent.getPackage(), decoder.user);
-                case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
-                    List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.intent, decoder.user)
-                            .buildRequest(context)
-                            .query(ShortcutRequest.ALL);
-                    if (si.isEmpty()) {
-                        return null;
-                    } else {
-                        return new PendingInstallShortcutInfo(si.get(0));
-                    }
+    private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
+        switch (itemType) {
+            case Favorites.ITEM_TYPE_APPLICATION:
+                return new PendingInstallShortcutInfo(intent.getPackage(), user);
+            case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                List<ShortcutInfo> si = ShortcutKey.fromIntent(intent, user)
+                        .buildRequest(mContext)
+                        .query(ShortcutRequest.ALL);
+                if (si.isEmpty()) {
+                    return null;
+                } else {
+                    return new PendingInstallShortcutInfo(si.get(0));
                 }
-                case Favorites.ITEM_TYPE_APPWIDGET: {
-                    int widgetId = decoder.intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
-                    AppWidgetProviderInfo info =
-                            AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId);
-                    if (info == null || !info.provider.equals(decoder.intent.getComponent())
-                            || !info.getProfile().equals(decoder.user)) {
-                        return null;
-                    }
-                    return new PendingInstallShortcutInfo(info, widgetId);
-                }
-                default:
-                    Log.e(TAG, "Unknown item type");
             }
-        } catch (JSONException | URISyntaxException e) {
-            Log.d(TAG, "Exception reading shortcut to add: " + e);
+            case Favorites.ITEM_TYPE_APPWIDGET: {
+                int widgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info =
+                        AppWidgetManager.getInstance(mContext).getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(intent.getComponent())
+                        || !info.getProfile().equals(user)) {
+                    return null;
+                }
+                return new PendingInstallShortcutInfo(info, widgetId);
+            }
+            default:
+                Log.e(TAG, "Unknown item type");
         }
         return null;
     }
-
-    private static class Decoder extends JSONObject {
-        public final Intent intent;
-        public final UserHandle user;
-
-        private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
-            super(encoded);
-            intent = Intent.parseUri(getString(Favorites.INTENT), 0);
-            user = has(PROFILE_ID)
-                    ? UserCache.INSTANCE.get(context).getUserForSerialNumber(getLong(PROFILE_ID))
-                    : Process.myUserHandle();
-            if (user == null || intent == null) {
-                throw new JSONException("Invalid data");
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index ec3a467..922425f 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
@@ -171,6 +172,10 @@
         protected boolean initPreference(Preference preference) {
             switch (preference.getKey()) {
                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
+                    if (WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS) {
+                        return false;
+                    }
+
                     // Listen to system notification dot settings while this UI is active.
                     mNotificationDotsObserver = newNotificationSettingsObserver(
                             getActivity(), (NotificationDotsPreference) preference);
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index e5c8441..355c949 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -50,7 +50,7 @@
     private final ArrayList<DisplayListChangeListener> mListListeners = new ArrayList<>();
 
     private DisplayController(Context context) {
-        mDefaultDisplay = new DisplayHolder(context, DEFAULT_DISPLAY);
+        mDefaultDisplay = DisplayHolder.create(context, DEFAULT_DISPLAY);
 
         DisplayManager dm = context.getSystemService(DisplayManager.class);
         dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
@@ -58,7 +58,11 @@
 
     @Override
     public final void onDisplayAdded(int displayId) {
-        DisplayHolder holder = new DisplayHolder(mDefaultDisplay.mDisplayContext, displayId);
+        DisplayHolder holder = DisplayHolder.create(mDefaultDisplay.mDisplayContext, displayId);
+        if (holder == null) {
+            // Display is already removed by the time we dot this.
+            return;
+        }
         synchronized (mOtherDisplays) {
             mOtherDisplays.put(displayId, holder);
         }
@@ -153,12 +157,8 @@
         private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
         private DisplayController.Info mInfo;
 
-        public DisplayHolder(Context context, int id) {
-            DisplayManager dm = context.getSystemService(DisplayManager.class);
-            // Use application context to create display context so that it can have its own
-            // Resources.
-            mDisplayContext = context.getApplicationContext()
-                    .createDisplayContext(dm.getDisplay(id));
+        private DisplayHolder(Context displayContext) {
+            mDisplayContext = displayContext;
             // Note that the Display object must be obtained from DisplayManager which is
             // associated to the display context, so the Display is isolated from Activity and
             // Application to provide the actual state of device that excludes the additional
@@ -207,6 +207,17 @@
             }
         }
 
+        private static DisplayHolder create(Context context, int id) {
+            DisplayManager dm = context.getSystemService(DisplayManager.class);
+            Display display = dm.getDisplay(id);
+            if (display == null) {
+                return null;
+            }
+            // Use application context to create display context so that it can have its own
+            // Resources.
+            Context displayContext = context.getApplicationContext().createDisplayContext(display);
+            return new DisplayHolder(displayContext);
+        }
     }
 
     public static class Info {
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
index ae20638..7ff2abb 100644
--- a/src/com/android/launcher3/util/PersistedItemArray.java
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -68,8 +68,7 @@
      */
     @WorkerThread
     public void write(Context context, List<T> items) {
-        AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
-
+        AtomicFile file = getFile(context);
         FileOutputStream fos;
         try {
             fos = file.startWrite();
@@ -124,9 +123,7 @@
     @WorkerThread
     public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
         List<T> result = new ArrayList<>();
-        AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
-
-        try (FileInputStream fis = file.openRead()) {
+        try (FileInputStream fis = getFile(context).openRead()) {
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
 
@@ -167,6 +164,13 @@
     }
 
     /**
+     * Returns the underlying file used for persisting data
+     */
+    public AtomicFile getFile(Context context) {
+        return new AtomicFile(context.getFileStreamPath(mFileName));
+    }
+
+    /**
      * Interface to create an ItemInfo during parsing
      */
     public interface ItemFactory<T extends ItemInfo> {
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 9d87788..a64df62 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -51,6 +51,7 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = false;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
 
     private static final String TAG = "WidgetsModel";
     private static final boolean DEBUG = false;