Simplifying pending widget inflation path
Instead of adding a PendingView in appWidgetHost, attaching a
listener in PendingView for widget-updates.
This moves the view inflation as the last stage of widget
binding. Eventually we can separate the model update and view
inflation to different threads
Bug: 318539160
Test: atest TaplBinderTests
Flag: None
Change-Id: I124edaa9622fa367302b80969036d128a978396e
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e0e35a4..5c49b89 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1011,7 +1011,7 @@
AppWidgetHostView boundWidget = null;
if (resultCode == RESULT_OK) {
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
- final AppWidgetHostView layout = mAppWidgetHolder.createView(this, appWidgetId,
+ final AppWidgetHostView layout = mAppWidgetHolder.createView(appWidgetId,
requestArgs.getWidgetHandler().getProviderInfo(this));
boundWidget = layout;
onCompleteRunnable = () -> {
@@ -1464,7 +1464,7 @@
if (hostView == null) {
// Perform actual inflation because we're live
- hostView = mAppWidgetHolder.createView(this, appWidgetId, appWidgetInfo);
+ hostView = mAppWidgetHolder.createView(appWidgetId, appWidgetInfo);
}
LauncherAppWidgetInfo launcherInfo;
@@ -2319,7 +2319,7 @@
}
final AppWidgetHostView view;
if (mIsSafeModeEnabled) {
- view = new PendingAppWidgetHostView(this, item, mIconCache, true);
+ view = new PendingAppWidgetHostView(this, item, null, true);
prepareAppWidget(view, item);
return view;
}
@@ -2450,14 +2450,9 @@
item.minSpanX = appWidgetInfo.minSpanX;
item.minSpanY = appWidgetInfo.minSpanY;
- view = mAppWidgetHolder.createView(this, item.appWidgetId, appWidgetInfo);
- } else if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
- && appWidgetInfo != null) {
- mAppWidgetHolder.addPendingView(item.appWidgetId,
- new PendingAppWidgetHostView(this, item, mIconCache, false));
- view = mAppWidgetHolder.createView(this, item.appWidgetId, appWidgetInfo);
+ view = mAppWidgetHolder.createView(item.appWidgetId, appWidgetInfo);
} else {
- view = new PendingAppWidgetHostView(this, item, mIconCache, false);
+ view = new PendingAppWidgetHostView(this, item, appWidgetInfo, false);
}
prepareAppWidget(view, item);
} finally {
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 739e204..b1c477c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -21,13 +21,22 @@
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
import java.util.function.IntConsumer;
/**
@@ -37,8 +46,7 @@
*/
class LauncherAppWidgetHost extends AppWidgetHost {
@NonNull
- private final ArrayList<LauncherWidgetHolder.ProviderChangedListener>
- mProviderChangeListeners = new ArrayList<>();
+ private final List<ProviderChangedListener> mProviderChangeListeners;
@NonNull
private final Context mContext;
@@ -46,33 +54,13 @@
@Nullable
private final IntConsumer mAppWidgetRemovedCallback;
- @NonNull
- private final LauncherWidgetHolder mHolder;
-
public LauncherAppWidgetHost(@NonNull Context context,
- @Nullable IntConsumer appWidgetRemovedCallback, @NonNull LauncherWidgetHolder holder) {
+ @Nullable IntConsumer appWidgetRemovedCallback,
+ List<ProviderChangedListener> providerChangeListeners) {
super(context, APPWIDGET_HOST_ID);
mContext = context;
mAppWidgetRemovedCallback = appWidgetRemovedCallback;
- mHolder = holder;
- }
-
- /**
- * Add a listener that is triggered when the providers of the widgets are changed
- * @param listener The listener that notifies when the providers changed
- */
- public void addProviderChangeListener(
- @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
- mProviderChangeListeners.add(listener);
- }
-
- /**
- * Remove the specified listener from the host
- * @param listener The listener that is to be removed from the host
- */
- public void removeProviderChangeListener(
- LauncherWidgetHolder.ProviderChangedListener listener) {
- mProviderChangeListeners.remove(listener);
+ mProviderChangeListeners = providerChangeListeners;
}
@Override
@@ -89,7 +77,7 @@
@NonNull
public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- return mHolder.onCreateView(context, appWidgetId);
+ return new ListenableHostView(context);
}
/**
@@ -115,7 +103,10 @@
if (mAppWidgetRemovedCallback == null) {
return;
}
- mAppWidgetRemovedCallback.accept(appWidgetId);
+ // Route the call via model thread, in case it comes while a loader-bind is in progress
+ Executors.MODEL_EXECUTOR.execute(
+ () -> Executors.MAIN_EXECUTOR.execute(
+ () -> mAppWidgetRemovedCallback.accept(appWidgetId)));
}
/**
@@ -126,4 +117,36 @@
super.clearViews();
}
+ public static class ListenableHostView extends LauncherAppWidgetHostView {
+
+ private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;
+
+ ListenableHostView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ super.updateAppWidget(remoteViews);
+ mUpdateListeners.forEach(Runnable::run);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(LauncherAppWidgetHostView.class.getName());
+ }
+
+ /**
+ * Adds a callback to be run everytime the provided app widget updates.
+ * @return a closable to remove this callback
+ */
+ public SafeCloseable addUpdateListener(Runnable callback) {
+ if (mUpdateListeners == Collections.EMPTY_SET) {
+ mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
+ }
+ mUpdateListeners.add(callback);
+ return () -> mUpdateListeners.remove(callback);
+ }
+ }
}
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index fbd48cf..23127b3 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -42,8 +42,12 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.IntConsumer;
/**
@@ -61,15 +65,14 @@
FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
@NonNull
- private final Context mContext;
+ protected final Context mContext;
@NonNull
private final AppWidgetHost mWidgetHost;
@NonNull
protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
- @NonNull
- private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
+ protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
protected int mFlags = FLAG_STATE_IS_NORMAL;
@@ -86,7 +89,8 @@
protected AppWidgetHost createHost(
Context context, @Nullable IntConsumer appWidgetRemovedCallback) {
- return new LauncherAppWidgetHost(context, appWidgetRemovedCallback, this);
+ return new LauncherAppWidgetHost(
+ context, appWidgetRemovedCallback, mProviderChangedListeners);
}
/**
@@ -158,28 +162,6 @@
}
/**
- * Add the pending view to the host for complete configuration in further steps
- * @param appWidgetId The ID of the specified app widget
- * @param view The {@link PendingAppWidgetHostView} of the app widget
- */
- public void addPendingView(int appWidgetId, @NonNull PendingAppWidgetHostView view) {
- mPendingViews.put(appWidgetId, view);
- }
-
- /**
- * @param appWidgetId The app widget id of the specified widget
- * @return The {@link PendingAppWidgetHostView} of the widget if it exists, null otherwise
- */
- @Nullable
- protected PendingAppWidgetHostView getPendingView(int appWidgetId) {
- return mPendingViews.get(appWidgetId);
- }
-
- protected void removePendingView(int appWidgetId) {
- mPendingViews.remove(appWidgetId);
- }
-
- /**
* Called when the launcher is destroyed
*/
public void destroy() {
@@ -201,18 +183,18 @@
* Add a listener that is triggered when the providers of the widgets are changed
* @param listener The listener that notifies when the providers changed
*/
- public void addProviderChangeListener(@NonNull ProviderChangedListener listener) {
- LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
- tempHost.addProviderChangeListener(listener);
+ public void addProviderChangeListener(
+ @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
+ MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
}
/**
* Remove the specified listener from the host
* @param listener The listener that is to be removed from the host
*/
- public void removeProviderChangeListener(ProviderChangedListener listener) {
- LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
- tempHost.removeProviderChangeListener(listener);
+ public void removeProviderChangeListener(
+ LauncherWidgetHolder.ProviderChangedListener listener) {
+ MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
}
/**
@@ -316,32 +298,51 @@
}
/**
+ * Adds a callback to be run everytime the provided app widget updates.
+ * @return a closable to remove this callback
+ */
+ public SafeCloseable addOnUpdateListener(
+ int appWidgetId, LauncherAppWidgetProviderInfo appWidget, Runnable callback) {
+ if (createView(appWidgetId, appWidget) instanceof ListenableHostView lhv) {
+ return lhv.addUpdateListener(callback);
+ }
+ return () -> { };
+ }
+
+ /**
* Create a view for the specified app widget
- * @param context The activity context for which the view is created
+ *
* @param appWidgetId The ID of the widget
- * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
+ * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
* @return A view for the widget
*/
@NonNull
- public AppWidgetHostView createView(@NonNull Context context, int appWidgetId,
- @NonNull LauncherAppWidgetProviderInfo appWidget) {
-
+ public AppWidgetHostView createView(
+ int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
if (appWidget.isCustomWidget()) {
- LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
+ LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(mContext);
lahv.setAppWidget(0, appWidget);
- CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
+ CustomWidgetManager.INSTANCE.get(mContext).onViewCreated(lahv);
return lahv;
- } else if ((mFlags & FLAG_LISTENING) == 0) {
+ }
+
+ LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget);
+ mViews.put(appWidgetId, view);
+ return view;
+ }
+
+ @NonNull
+ protected LauncherAppWidgetHostView createViewInternal(
+ int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
+ if ((mFlags & FLAG_LISTENING) == 0) {
// Since the launcher hasn't started listening to widget updates, we can't simply call
- // super.createView here because the later will make a binder call to retrieve
+ // host.createView here because the later will make a binder call to retrieve
// RemoteViews from system process.
- LauncherAppWidgetHostView view =
- new PendingAppWidgetHostView(context, appWidgetId, appWidget);
- mViews.put(appWidgetId, view);
- return view;
+ return new PendingAppWidgetHostView(mContext, appWidgetId, appWidget);
} else {
try {
- return mWidgetHost.createView(context, appWidgetId, appWidget);
+ return (LauncherAppWidgetHostView) mWidgetHost.createView(
+ mContext, appWidgetId, appWidget);
} catch (Exception e) {
if (!Utilities.isBinderSizeError(e)) {
throw new RuntimeException(e);
@@ -352,7 +353,7 @@
// will update.
LauncherAppWidgetHostView view = mViews.get(appWidgetId);
if (view == null) {
- view = onCreateView(mContext, appWidgetId);
+ view = new ListenableHostView(mContext);
}
view.setAppWidget(appWidgetId, appWidget);
view.switchToErrorView();
@@ -372,26 +373,6 @@
}
/**
- * Called to return a proper view when creating a view
- *
- * @param context The context for which the widget view is created
- * @param appWidgetId The ID of the added widget
- * @return A view for the specified app widget
- */
- @NonNull
- public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId) {
- final LauncherAppWidgetHostView view;
- if (getPendingView(appWidgetId) != null) {
- view = getPendingView(appWidgetId);
- removePendingView(appWidgetId);
- } else {
- view = new LauncherAppWidgetHostView(context);
- }
- mViews.put(appWidgetId, view);
- return view;
- }
-
- /**
* Clears all the views from the host
*/
public void clearViews() {
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 2bd4c7e..6ad913e 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.graphics.Canvas;
@@ -38,16 +39,18 @@
import android.view.View.OnClickListener;
import android.widget.RemoteViews;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -64,12 +67,16 @@
private static final int DEFERRED_ALPHA = 0x77;
private final Rect mRect = new Rect();
- private OnClickListener mClickListener;
+
+ private final LauncherAppWidgetProviderInfo mAppwidget;
private final LauncherAppWidgetInfo mInfo;
private final int mStartState;
private final boolean mDisabledForSafeMode;
private final CharSequence mLabel;
+ private OnClickListener mClickListener;
+ private SafeCloseable mOnDetachCleanup;
+
private int mDragFlags;
private Drawable mCenterDrawable;
@@ -81,8 +88,8 @@
private Layout mSetupTextLayout;
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
- IconCache cache, boolean disabledForSafeMode) {
- this(context, info, disabledForSafeMode,
+ @Nullable LauncherAppWidgetProviderInfo appWidget, boolean disabledForSafeMode) {
+ this(context, info, disabledForSafeMode, appWidget,
context.getResources().getText(R.string.gadget_complete_setup_text));
super.updateAppWidget(null);
@@ -91,16 +98,17 @@
if (info.pendingItemInfo == null) {
info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName(),
info.user);
- cache.updateIconInBackground(this, info.pendingItemInfo);
+ LauncherAppState.getInstance(context).getIconCache()
+ .updateIconInBackground(this, info.pendingItemInfo);
} else {
reapplyItemInfo(info.pendingItemInfo);
}
}
public PendingAppWidgetHostView(
- Context context, int appWidgetId, LauncherAppWidgetProviderInfo appWidget) {
+ Context context, int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
this(context, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider), false,
- appWidget.label);
+ appWidget, appWidget.label);
getBackground().mutate().setAlpha(DEFERRED_ALPHA);
mCenterDrawable = new ColorDrawable(Color.TRANSPARENT);
@@ -109,9 +117,11 @@
}
private PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
- boolean disabledForSafeMode, CharSequence label) {
+ boolean disabledForSafeMode, LauncherAppWidgetProviderInfo appwidget,
+ CharSequence label) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
+ mAppwidget = appwidget;
mInfo = info;
mStartState = info.restoreStatus;
mDisabledForSafeMode = disabledForSafeMode;
@@ -128,9 +138,40 @@
@Override
public void updateAppWidget(RemoteViews remoteViews) {
+ checkIfRestored();
+ }
+
+ private void checkIfRestored() {
WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(getContext());
if (widgetManagerHelper.isAppWidgetRestored(mInfo.appWidgetId)) {
- reInflate();
+ MAIN_EXECUTOR.getHandler().post(this::reInflate);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if ((mAppwidget != null)
+ && !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
+ && mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
+ // If the widget is not completely restored, but has a valid ID, then listen of
+ // updates from provider app for potential restore complete.
+ if (mOnDetachCleanup != null) {
+ mOnDetachCleanup.close();
+ }
+ mOnDetachCleanup = mLauncher.getAppWidgetHolder()
+ .addOnUpdateListener(mInfo.appWidgetId, mAppwidget, this::checkIfRestored);
+ checkIfRestored();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mOnDetachCleanup != null) {
+ mOnDetachCleanup.close();
+ mOnDetachCleanup = null;
}
}
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index b18cd47..1cc00ef 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -1,7 +1,6 @@
package com.android.launcher3.widget;
import android.appwidget.AppWidgetHostView;
-import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -117,7 +116,7 @@
return;
}
AppWidgetHostView hostView = mLauncher.getAppWidgetHolder().createView(
- (Context) mLauncher, mWidgetLoadingId, pInfo);
+ mWidgetLoadingId, pInfo);
mInfo.boundWidget = hostView;
// We used up the widget Id in binding the above view.