Merge "Add PageIndicator interface and custom PageIndicatorLine view." into ub-launcher3-calgary
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 0dfe525..9a99852 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -21,7 +21,6 @@
 
 import com.android.launcher3.compat.UserHandleCompat;
 
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
@@ -58,11 +57,7 @@
      */
     public ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
 
-    /**
-     * A collection of listeners for folder info changes. Since this listeners are implemented by
-     * the UI objects, using a WeakReference prevents context leaks.
-     */
-    private  WeakReference<FolderListener> mListener;
+    ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
 
     public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
@@ -76,9 +71,8 @@
      */
     public void add(ShortcutInfo item, boolean animate) {
         contents.add(item);
-        FolderListener listener = mListener == null ? null : mListener.get();
-        if (listener != null) {
-            listener.onAdd(item);
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onAdd(item);
         }
         itemsChanged(animate);
     }
@@ -90,13 +84,19 @@
      */
     public void remove(ShortcutInfo item, boolean animate) {
         contents.remove(item);
-        FolderListener listener = mListener == null ? null : mListener.get();
-        if (listener != null) {
-            listener.onRemove(item);
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onRemove(item);
         }
         itemsChanged(animate);
     }
 
+    public void setTitle(CharSequence title) {
+        this.title = title;
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onTitleChanged(title);
+        }
+    }
+
     @Override
     void onAddToDatabase(Context context, ContentValues values) {
         super.onAddToDatabase(context, values);
@@ -105,30 +105,25 @@
 
     }
 
-    /**
-     * Registers a listener for info change events.
-     */
-    public void setListener(FolderListener listener) {
-        mListener = new WeakReference<>(listener);
+    public void addListener(FolderListener listener) {
+        listeners.add(listener);
+    }
+
+    public void removeListener(FolderListener listener) {
+        listeners.remove(listener);
     }
 
     public void itemsChanged(boolean animate) {
-        FolderListener listener = mListener == null ? null : mListener.get();
-        if (listener != null) {
-            listener.onItemsChanged(animate);
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onItemsChanged(animate);
         }
     }
 
-    @Override
-    void unbind() {
-        super.unbind();
-        mListener = null;
-    }
-
     public interface FolderListener {
-        void onAdd(ShortcutInfo item);
-        void onRemove(ShortcutInfo item);
-        void onItemsChanged(boolean animate);
+        public void onAdd(ShortcutInfo item);
+        public void onRemove(ShortcutInfo item);
+        public void onTitleChanged(CharSequence title);
+        public void onItemsChanged(boolean animate);
     }
 
     @Override
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 191becf..259370c 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -18,7 +18,9 @@
 
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.widget.Toast;
@@ -92,7 +94,12 @@
     }
 
     public static boolean supportsDrop(ItemInfo info) {
-        return info instanceof AppInfo || info instanceof ShortcutInfo
-                || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo;
+        // Only show the App Info drop target if developer settings are enabled.
+        ContentResolver resolver = LauncherAppState.getInstance().getContext().getContentResolver();
+        boolean developmentSettingsEnabled = Settings.Global.getInt(resolver,
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
+        return developmentSettingsEnabled
+                && (info instanceof AppInfo || info instanceof ShortcutInfo
+                || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo);
     }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 1ba09e1..286a7f1 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -183,15 +183,6 @@
         }
     }
 
-    /**
-     * It is very important that sub-classes implement this if they contain any references
-     * to the activity (anything in the view hierarchy etc.). If not, leaks can result since
-     * ItemInfo objects persist across rotation and can hence leak by holding stale references
-     * to the old view hierarchy / activity.
-     */
-    void unbind() {
-    }
-
     @Override
     public String toString() {
         return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 03b921b..e93068e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1970,6 +1970,7 @@
         mHandler.removeMessages(ADVANCE_MSG);
         mHandler.removeMessages(0);
         mWorkspace.removeCallbacks(mBuildLayersRunnable);
+        mWorkspace.removeFolderListeners();
 
         // Stop callbacks from LauncherModel
         // It's possible to receive onDestroy after a new Launcher activity has
@@ -2368,6 +2369,9 @@
             }
         } else if (itemInfo instanceof FolderInfo) {
             final FolderInfo folderInfo = (FolderInfo) itemInfo;
+            if (v instanceof FolderIcon) {
+                ((FolderIcon) v).removeListeners();
+            }
             mWorkspace.removeWorkspaceItem(v);
             if (deleteFromDb) {
                 LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index eaeb1ac..3d31b4e 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -65,6 +65,7 @@
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.StringFilter;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -578,38 +579,6 @@
         runOnWorkerThread(r);
     }
 
-    private void unbindItemInfosAndClearQueuedBindRunnables() {
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
-                    "main thread");
-        }
-
-        // Remove any queued UI runnables
-        mHandler.cancelAll();
-        // Unbind all the workspace items
-        unbindWorkspaceItemsOnMainThread();
-    }
-
-    /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
-    void unbindWorkspaceItemsOnMainThread() {
-        // Ensure that we don't use the same workspace items data structure on the main thread
-        // by making a copy of workspace items first.
-        final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
-        synchronized (sBgLock) {
-            tmpItems.addAll(sBgWorkspaceItems);
-            tmpItems.addAll(sBgAppWidgets);
-        }
-        Runnable r = new Runnable() {
-                @Override
-                public void run() {
-                   for (ItemInfo item : tmpItems) {
-                       item.unbind();
-                   }
-                }
-            };
-        runOnMainThread(r);
-    }
-
     /**
      * Adds an item to the DB if it was not created previously, or move it to a new
      * <container, screen, cellX, cellY>
@@ -1137,10 +1106,10 @@
      */
     public void initialize(Callbacks callbacks) {
         synchronized (mLock) {
-            // Disconnect any of the callbacks and drawables associated with ItemInfos on the
-            // workspace to prevent leaking Launcher activities on orientation change.
-            unbindItemInfosAndClearQueuedBindRunnables();
-            mCallbacks = new WeakReference<Callbacks>(callbacks);
+            Preconditions.assertUIThread();
+            // Remove any queued UI runnables
+            mHandler.cancelAll();
+            mCallbacks = new WeakReference<>(callbacks);
         }
     }
 
@@ -2482,10 +2451,6 @@
             final long currentScreenId = currentScreen < 0
                     ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
 
-            // Load all the items that are on the current page first (and in the process, unbind
-            // all the existing workspace items before we call startBinding() below.
-            unbindWorkspaceItemsOnMainThread();
-
             // Separate the items that are on the current screen, and all the other remaining items
             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 56b83bb..386e016 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3691,6 +3691,21 @@
         }
     }
 
+    /**
+     * Removes all folder listeners
+     */
+    public void removeFolderListeners() {
+        mapOverItems(false, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                if (view instanceof FolderIcon) {
+                    ((FolderIcon) view).removeListeners();
+                }
+                return false;
+            }
+        });
+    }
+
     @Override
     public void deferCompleteDropAfterUninstallActivity() {
         mDeferDropAfterUninstall = true;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2ea1986..1ebe8fd 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -348,14 +348,13 @@
         mFolderName.setHint(sHintText);
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
-        mInfo.title = mFolderName.getText().toString();
-        mFolderIcon.onTitleChanged(mInfo.title);
-
+        String newTitle = mFolderName.getText().toString();
+        mInfo.setTitle(newTitle);
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
         if (commit) {
             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    getContext().getString(R.string.folder_renamed, mInfo.title));
+                    getContext().getString(R.string.folder_renamed, newTitle));
         }
 
         // This ensures that focus is gained every time the field is clicked, which selects all
@@ -449,6 +448,7 @@
 
         mItemsInvalidated = true;
         updateTextViewFocus();
+        mInfo.addListener(this);
 
         if (!sDefaultFolderName.contentEquals(mInfo.title)) {
             mFolderName.setText(mInfo.title);
@@ -1349,7 +1349,6 @@
                 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
     }
 
-    @Override
     public void onRemove(ShortcutInfo item) {
         mItemsInvalidated = true;
         // If this item is being dragged from this open folder, we have already handled
@@ -1386,6 +1385,9 @@
         updateTextViewFocus();
     }
 
+    public void onTitleChanged(CharSequence title) {
+    }
+
     public ArrayList<View> getItemsInReadingOrder() {
         if (mItemsInvalidated) {
             mItemsInReadingOrder.clear();
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 4a4f7cf..157a970 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -183,9 +183,10 @@
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.setFolder(folder);
-        icon.setOnFocusChangeListener(launcher.mFocusHandler);
 
-        folderInfo.setListener(new MultiFolderListener(folder, icon));
+        folderInfo.addListener(icon);
+
+        icon.setOnFocusChangeListener(launcher.mFocusHandler);
         return icon;
     }
 
@@ -943,13 +944,11 @@
         requestLayout();
     }
 
-    @Override
     public void onAdd(ShortcutInfo item) {
         invalidate();
         requestLayout();
     }
 
-    @Override
     public void onRemove(ShortcutInfo item) {
         invalidate();
         requestLayout();
@@ -1001,6 +1000,11 @@
         mLongPressHelper.cancelLongPress();
     }
 
+    public void removeListeners() {
+        mInfo.removeListener(this);
+        mInfo.removeListener(mFolder);
+    }
+
     public interface PreviewLayoutRule {
         public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params);
diff --git a/src/com/android/launcher3/folder/MultiFolderListener.java b/src/com/android/launcher3/folder/MultiFolderListener.java
deleted file mode 100644
index 1030112..0000000
--- a/src/com/android/launcher3/folder/MultiFolderListener.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.folder;
-
-import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.ShortcutInfo;
-
-/**
- * An implementation of {@link FolderListener} which passes the events to 2 children.
- */
-public class MultiFolderListener implements FolderListener {
-
-    private final FolderListener mListener1;
-    private final FolderListener mListener2;
-
-    public MultiFolderListener(FolderListener listener1, FolderListener listener2) {
-        mListener1 = listener1;
-        mListener2 = listener2;
-    }
-
-    @Override
-    public void onAdd(ShortcutInfo item) {
-        mListener1.onAdd(item);
-        mListener2.onAdd(item);
-    }
-
-    @Override
-    public void onRemove(ShortcutInfo item) {
-        mListener1.onRemove(item);
-        mListener2.onRemove(item);
-    }
-
-    @Override
-    public void onItemsChanged(boolean animate) {
-        mListener1.onItemsChanged(animate);
-        mListener2.onItemsChanged(animate);
-    }
-}