Wrapping folderLisners with weak reference, and storing it as an array

Storing as an array allows the listeners to be overriten on next bind.
These changes remove the need to unbind the item

Bug: 28740269
Change-Id: Ibbe4b760d64784fbe3075d18e2b946b366d631c5
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 6c9d969..0dfe525 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -21,6 +21,7 @@
 
 import com.android.launcher3.compat.UserHandleCompat;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
@@ -57,7 +58,11 @@
      */
     public ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
 
-    ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
+    /**
+     * 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;
 
     public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
@@ -71,8 +76,9 @@
      */
     public void add(ShortcutInfo item, boolean animate) {
         contents.add(item);
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onAdd(item);
+        FolderListener listener = mListener == null ? null : mListener.get();
+        if (listener != null) {
+            listener.onAdd(item);
         }
         itemsChanged(animate);
     }
@@ -84,19 +90,13 @@
      */
     public void remove(ShortcutInfo item, boolean animate) {
         contents.remove(item);
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onRemove(item);
+        FolderListener listener = mListener == null ? null : mListener.get();
+        if (listener != null) {
+            listener.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,33 +105,30 @@
 
     }
 
-    public void addListener(FolderListener listener) {
-        listeners.add(listener);
-    }
-
-    void removeListener(FolderListener listener) {
-        if (listeners.contains(listener)) {
-            listeners.remove(listener);
-        }
+    /**
+     * Registers a listener for info change events.
+     */
+    public void setListener(FolderListener listener) {
+        mListener = new WeakReference<>(listener);
     }
 
     public void itemsChanged(boolean animate) {
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onItemsChanged(animate);
+        FolderListener listener = mListener == null ? null : mListener.get();
+        if (listener != null) {
+            listener.onItemsChanged(animate);
         }
     }
 
     @Override
     void unbind() {
         super.unbind();
-        listeners.clear();
+        mListener = null;
     }
 
     public interface FolderListener {
-        public void onAdd(ShortcutInfo item);
-        public void onRemove(ShortcutInfo item);
-        public void onTitleChanged(CharSequence title);
-        public void onItemsChanged(boolean animate);
+        void onAdd(ShortcutInfo item);
+        void onRemove(ShortcutInfo item);
+        void onItemsChanged(boolean animate);
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 1ebe8fd..2ea1986 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -348,13 +348,14 @@
         mFolderName.setHint(sHintText);
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
-        String newTitle = mFolderName.getText().toString();
-        mInfo.setTitle(newTitle);
+        mInfo.title = mFolderName.getText().toString();
+        mFolderIcon.onTitleChanged(mInfo.title);
+
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
         if (commit) {
             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    getContext().getString(R.string.folder_renamed, newTitle));
+                    getContext().getString(R.string.folder_renamed, mInfo.title));
         }
 
         // This ensures that focus is gained every time the field is clicked, which selects all
@@ -448,7 +449,6 @@
 
         mItemsInvalidated = true;
         updateTextViewFocus();
-        mInfo.addListener(this);
 
         if (!sDefaultFolderName.contentEquals(mInfo.title)) {
             mFolderName.setText(mInfo.title);
@@ -1349,6 +1349,7 @@
                 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
@@ -1385,9 +1386,6 @@
         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 1e4eb7f..4a4f7cf 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -183,10 +183,9 @@
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.setFolder(folder);
-
-        folderInfo.addListener(icon);
-
         icon.setOnFocusChangeListener(launcher.mFocusHandler);
+
+        folderInfo.setListener(new MultiFolderListener(folder, icon));
         return icon;
     }
 
@@ -944,11 +943,13 @@
         requestLayout();
     }
 
+    @Override
     public void onAdd(ShortcutInfo item) {
         invalidate();
         requestLayout();
     }
 
+    @Override
     public void onRemove(ShortcutInfo item) {
         invalidate();
         requestLayout();
diff --git a/src/com/android/launcher3/folder/MultiFolderListener.java b/src/com/android/launcher3/folder/MultiFolderListener.java
new file mode 100644
index 0000000..1030112
--- /dev/null
+++ b/src/com/android/launcher3/folder/MultiFolderListener.java
@@ -0,0 +1,52 @@
+/*
+ * 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);
+    }
+}