Simplifying Launcher binding callbacks

> Making all methods as default
> Removing obsolete logic around synchronous binding
> Removing some UI dependencies from bind callbacks

Bug: 187353581
Test: Manual
Change-Id: I0d2bbb060af2cab7c64541d7695055629dfaf0b8
This commit is contained in:
Sunny Goyal 2021-06-17 15:15:46 -07:00
parent ab4fb243b7
commit 4a48a988c8
8 changed files with 99 additions and 278 deletions

View File

@ -41,20 +41,11 @@ import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
import com.android.launcher3.model.data.AppInfo;
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.shadows.ShadowDeviceFlag;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import org.junit.Before;
import org.junit.Test;
@ -68,9 +59,6 @@ import org.robolectric.shadows.ShadowAppWidgetManager;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@ -243,60 +231,5 @@ public final class WidgetsPredicationUpdateTaskTest {
public IntSet getPagesToBindSynchronously() {
return IntSet.wrap(0);
}
@Override
public void clearPendingBinds() { }
@Override
public void startBinding() { }
@Override
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
@Override
public void bindScreens(IntArray orderedScreenIds) { }
@Override
public void finishFirstPageBind(ViewOnDrawExecutor executor) { }
@Override
public void finishBindingItems(IntSet pagesBoundFirst) { }
@Override
public void preAddApps() { }
@Override
public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated) { }
@Override
public void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
@Override
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
@Override
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
@Override
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
@Override
public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
@Override
public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
@Override
public void onPagesBoundSynchronously(IntSet pages) { }
@Override
public void executeOnNextDraw(ViewOnDrawExecutor executor) { }
@Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
@Override
public void bindAllApplications(AppInfo[] apps, int flags) { }
}
}

View File

@ -35,7 +35,7 @@ import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.util.RunnableList;
import org.junit.Before;
import org.junit.Test;
@ -106,14 +106,14 @@ public class ModelMultiCallbacksTest {
// No effect on callbacks when removing an callback
mModelHelper.getModel().removeCallbacks(cb2);
waitForLoaderAndTempMainThread();
assertNull(cb1.mDeferredExecutor);
assertNull(cb2.mDeferredExecutor);
assertNull(cb1.mPendingTasks);
assertNull(cb2.mPendingTasks);
// Reloading only loads registered callbacks
mModelHelper.getModel().startLoader();
waitForLoaderAndTempMainThread();
cb1.verifySynchronouslyBound(3);
assertNull(cb2.mDeferredExecutor);
assertNull(cb2.mPendingTasks);
}
@Test
@ -180,19 +180,15 @@ public class ModelMultiCallbacksTest {
final List<ItemInfo> mItems = new ArrayList<>();
IntSet mPageToBindSync = IntSet.wrap(0);
IntSet mPageBoundSync = new IntSet();
ViewOnDrawExecutor mDeferredExecutor;
RunnableList mPendingTasks;
AppInfo[] mAppInfos;
MyCallbacks() { }
@Override
public void onPagesBoundSynchronously(IntSet pages) {
mPageBoundSync = pages;
}
@Override
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
mDeferredExecutor = executor;
public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
mPageBoundSync = boundPages;
mPendingTasks = pendingTasks;
}
@Override
@ -213,19 +209,19 @@ public class ModelMultiCallbacksTest {
public void reset() {
mItems.clear();
mPageBoundSync = new IntSet();
mDeferredExecutor = null;
mPendingTasks = null;
mAppInfos = null;
}
public void verifySynchronouslyBound(int totalItems) {
// Verify that the requested page is bound synchronously
assertEquals(mPageBoundSync, mPageToBindSync);
assertEquals(mPageToBindSync, mPageBoundSync);
assertEquals(mItems.size(), 1);
assertEquals(mItems.get(0).screenId, mPageBoundSync);
assertNotNull(mDeferredExecutor);
assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync);
assertNotNull(mPendingTasks);
// Verify that all other pages are bound properly
mDeferredExecutor.runAllTasks();
mPendingTasks.executeAllAndDestroy();
assertEquals(mItems.size(), totalItems);
}

View File

@ -81,7 +81,7 @@ public class LauncherUIHelper {
doLayout(launcher);
ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
if (executor != null) {
executor.runAllTasks();
executor.markCompleted();
}
return launcher;
}

View File

@ -118,6 +118,7 @@ import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@ -174,6 +175,7 @@ import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
@ -2060,7 +2062,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
@Override
public void clearPendingBinds() {
if (mPendingExecutor != null) {
mPendingExecutor.markCompleted();
mPendingExecutor.cancel();
mPendingExecutor = null;
// We might have set this flag previously and forgot to clear it.
@ -2482,25 +2484,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
return info;
}
public void onPagesBoundSynchronously(IntSet pages) {
mSynchronouslyBoundPages = pages;
mWorkspace.setCurrentPage(pages.getArray().get(0));
mPagesToBindSynchronously = new IntSet();
}
@Override
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
clearPendingBinds();
mPendingExecutor = executor;
if (!isInState(ALL_APPS)) {
mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
mPendingExecutor.execute(() -> mAppsView.getAppsStore().disableDeferUpdates(
AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
}
executor.attachTo(this);
}
public void clearPendingExecutor(ViewOnDrawExecutor executor) {
if (mPendingExecutor == executor) {
mPendingExecutor = null;
@ -2508,22 +2491,31 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
}
@Override
public void finishFirstPageBind(final ViewOnDrawExecutor executor) {
public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
mSynchronouslyBoundPages = boundPages;
if (!boundPages.isEmpty()) {
mWorkspace.setCurrentPage(boundPages.getArray().get(0));
}
mPagesToBindSynchronously = new IntSet();
clearPendingBinds();
ViewOnDrawExecutor executor = new ViewOnDrawExecutor(pendingTasks);
mPendingExecutor = executor;
if (!isInState(ALL_APPS)) {
mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
pendingTasks.add(() -> mAppsView.getAppsStore().disableDeferUpdates(
AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
}
AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
if (property.getValue() < 1) {
ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
if (executor != null) {
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
executor.onLoadAnimationCompleted();
}
});
}
anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
anim.start();
} else if (executor != null) {
} else {
executor.onLoadAnimationCompleted();
}
executor.attachTo(this);
}
/**

View File

@ -18,7 +18,9 @@ package com.android.launcher3.model;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.os.Process;
import android.util.Log;
import com.android.launcher3.InvariantDeviceProfile;
@ -33,7 +35,7 @@ import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.util.RunnableList;
import java.util.ArrayList;
import java.util.Collections;
@ -175,7 +177,6 @@ public abstract class BaseLoaderResults {
currentScreenIndices = screenIndices;
}
final boolean validFirstPage = !currentScreenIndices.isEmpty();
IntSet currentScreenIds = new IntSet();
currentScreenIndices.forEach(
@ -204,40 +205,25 @@ public abstract class BaseLoaderResults {
// Bind workspace screens
executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
Executor mainExecutor = mUiExecutor;
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
bindAppWidgets(currentAppWidgets, mainExecutor);
bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
bindAppWidgets(currentAppWidgets, mUiExecutor);
mExtraItems.forEach(item ->
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
RunnableList pendingTasks = new RunnableList();
Executor pendingExecutor = pendingTasks::add;
bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
bindAppWidgets(otherAppWidgets, pendingExecutor);
executeCallbacksTask(c -> c.finishBindingItems(currentScreenIndices), pendingExecutor);
pendingExecutor.execute(
() -> MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT));
final Executor deferredExecutor =
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
executeCallbacksTask(c -> c.finishFirstPageBind(
validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
bindAppWidgets(otherAppWidgets, deferredExecutor);
// Tell the workspace that we're done binding items
executeCallbacksTask(c -> c.finishBindingItems(currentScreenIndices), deferredExecutor);
if (validFirstPage) {
executeCallbacksTask(c -> {
// We are loading synchronously, which means, some of the pages will be
// bound after first draw. Inform the mCallbacks that page binding is
// not complete, and schedule the remaining pages.
c.onPagesBoundSynchronously(currentScreenIndices);
c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
}, mUiExecutor);
}
executeCallbacksTask(
c -> {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
c.onInitialBindComplete(currentScreenIndices, pendingTasks);
}, mUiExecutor);
}
private void bindWorkspaceItems(

View File

@ -49,7 +49,7 @@ import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.io.FileDescriptor;
@ -462,35 +462,41 @@ public class BgDataModel {
* Returns an IntSet of page numbers to bind first, synchronously if possible
* or an empty IntSet
*/
IntSet getPagesToBindSynchronously();
void clearPendingBinds();
void startBinding();
void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
void bindScreens(IntArray orderedScreenIds);
void finishFirstPageBind(ViewOnDrawExecutor executor);
void finishBindingItems(IntSet pagesBoundFirst);
void preAddApps();
void bindAppsAdded(IntArray newScreens,
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
default IntSet getPagesToBindSynchronously() {
return new IntSet();
}
default void clearPendingBinds() { }
default void startBinding() { }
default void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
default void bindScreens(IntArray orderedScreenIds) { }
default void finishBindingItems(IntSet pagesBoundFirst) { }
default void preAddApps() { }
default void bindAppsAdded(IntArray newScreens,
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated) { }
/**
* Binds updated incremental download progress
*/
void bindIncrementalDownloadProgressUpdated(AppInfo app);
void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
void bindRestoreItemsChange(HashSet<ItemInfo> updates);
void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
void bindAllWidgets(List<WidgetsListBaseEntry> widgets);
void onPagesBoundSynchronously(IntSet pages);
void executeOnNextDraw(ViewOnDrawExecutor executor);
void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
default void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
pendingTasks.executeAllAndDestroy();
}
default void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
/**
* Binds extra item provided any external source
*/
default void bindExtraContainerItems(FixedContainerItems item) { }
void bindAllApplications(AppInfo[] apps, int flags);
default void bindAllApplications(AppInfo[] apps, int flags) { }
}
}

View File

@ -35,23 +35,13 @@ import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Launcher activity for secondary displays
@ -175,68 +165,11 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity
return mDragLayer;
}
@Override
public IntSet getPagesToBindSynchronously() {
return new IntSet();
}
@Override
public void clearPendingBinds() { }
@Override
public void startBinding() { }
@Override
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
@Override
public void bindScreens(IntArray orderedScreenIds) { }
@Override
public void finishFirstPageBind(ViewOnDrawExecutor executor) {
if (executor != null) {
executor.onLoadAnimationCompleted();
}
}
@Override
public void finishBindingItems(IntSet pagesBoundFirst) { }
@Override
public void preAddApps() { }
@Override
public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated) { }
@Override
public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
mAppsView.getAppsStore().updateProgressBar(app);
}
@Override
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
@Override
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
@Override
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
@Override
public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
@Override
public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
@Override
public void onPagesBoundSynchronously(IntSet pages) { }
@Override
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
executor.attachTo(getDragLayer(), false, null);
}
/**
* Called when apps-button is clicked
*/

View File

@ -16,28 +16,21 @@
package com.android.launcher3.util;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.os.Process;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Launcher;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* An executor which runs all the tasks after the first onDraw is called on the target view.
*/
public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
public class ViewOnDrawExecutor implements OnDrawListener, Runnable,
OnAttachStateChangeListener {
private final ArrayList<Runnable> mTasks = new ArrayList<>();
private final RunnableList mTasks;
private Consumer<ViewOnDrawExecutor> mOnClearCallback;
private View mAttachedView;
@ -46,22 +39,16 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
private boolean mLoadAnimationCompleted;
private boolean mFirstDrawCompleted;
public void attachTo(Launcher launcher) {
attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
launcher::clearPendingExecutor);
private boolean mCancelled;
public ViewOnDrawExecutor(RunnableList tasks) {
mTasks = tasks;
}
/**
* Attached the executor to the existence of the view
*/
public void attachTo(View attachedView, boolean waitForLoadAnimation,
Consumer<ViewOnDrawExecutor> onClearCallback) {
mOnClearCallback = onClearCallback;
mAttachedView = attachedView;
public void attachTo(Launcher launcher) {
mOnClearCallback = launcher::clearPendingExecutor;
mAttachedView = launcher.getWorkspace();
mAttachedView.addOnAttachStateChangeListener(this);
if (!waitForLoadAnimation) {
mLoadAnimationCompleted = true;
}
if (mAttachedView.isAttachedToWindow()) {
attachObserver();
@ -74,12 +61,6 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
}
}
@Override
public void execute(Runnable command) {
mTasks.add(command);
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
@Override
public void onViewAttachedToWindow(View v) {
attachObserver();
@ -105,12 +86,17 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
public void run() {
// Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called.
if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) {
runAllTasks();
markCompleted();
}
}
/**
* Executes all tasks immediately
*/
public void markCompleted() {
mTasks.clear();
if (!mCancelled) {
mTasks.executeAllAndDestroy();
}
mCompleted = true;
if (mAttachedView != null) {
mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
@ -119,21 +105,10 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
if (mOnClearCallback != null) {
mOnClearCallback.accept(this);
}
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
protected boolean isCompleted() {
return mCompleted;
}
/**
* Executes all tasks immediately
*/
@VisibleForTesting
public void runAllTasks() {
for (final Runnable r : mTasks) {
r.run();
}
public void cancel() {
mCancelled = true;
markCompleted();
}
}