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);
- }
-}