[Launcher Jank] Avoid LauncherWidgetHolder and QuickstepWidgetHolder calling IAppWidgetService$Stub$Proxy.setAppWidgetHidden from main thread
Fix: 347991067
Test: manual - jank fix
Flag: NONE - jank fix
Change-Id: I881e5961ee4422d89e761c95ee8371902ded7ae6
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index 01d5ff0..56fc4d1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
@@ -100,7 +99,7 @@
// for concurrent modification.
new ArrayList<>(h.mProviderChangedListeners).forEach(
ProviderChangedListener::notifyWidgetProvidersChanged))),
- UI_HELPER_EXECUTOR.getLooper());
+ getWidgetHolderExecutor().getLooper());
if (WIDGETS_ENABLED) {
sWidgetHost.startListening();
}
@@ -199,8 +198,10 @@
return;
}
- sWidgetHost.setAppWidgetHidden();
- setListeningFlag(false);
+ getWidgetHolderExecutor().execute(() -> {
+ sWidgetHost.setAppWidgetHidden();
+ setListeningFlag(false);
+ });
}
@Override
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index a5e22c5..1fb8c83 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
import android.appwidget.AppWidgetHost;
@@ -36,6 +37,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
@@ -44,6 +46,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
@@ -51,6 +54,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntConsumer;
/**
@@ -77,7 +81,7 @@
protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
- protected int mFlags = FLAG_STATE_IS_NORMAL;
+ protected AtomicInteger mFlags = new AtomicInteger(FLAG_STATE_IS_NORMAL);
// TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden
private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle";
@@ -96,6 +100,10 @@
context, appWidgetRemovedCallback, mProviderChangedListeners);
}
+ protected LooperExecutor getWidgetHolderExecutor() {
+ return UI_HELPER_EXECUTOR;
+ }
+
/**
* Starts listening to the widget updates from the server side
*/
@@ -104,21 +112,23 @@
return;
}
- try {
- mWidgetHost.startListening();
- } catch (Exception e) {
- if (!Utilities.isBinderSizeError(e)) {
- throw new RuntimeException(e);
+ getWidgetHolderExecutor().execute(() -> {
+ try {
+ mWidgetHost.startListening();
+ } catch (Exception e) {
+ if (!Utilities.isBinderSizeError(e)) {
+ throw new RuntimeException(e);
+ }
+ // We're willing to let this slide. The exception is being caused by the list of
+ // RemoteViews which is being passed back. The startListening relationship will
+ // have been established by this point, and we will end up populating the
+ // widgets upon bind anyway. See issue 14255011 for more context.
}
- // We're willing to let this slide. The exception is being caused by the list of
- // RemoteViews which is being passed back. The startListening relationship will
- // have been established by this point, and we will end up populating the
- // widgets upon bind anyway. See issue 14255011 for more context.
- }
- // TODO: Investigate why widgetHost.startListening() always return non-empty updates
- setListeningFlag(true);
+ // TODO: Investigate why widgetHost.startListening() always return non-empty updates
+ setListeningFlag(true);
- updateDeferredView();
+ MAIN_EXECUTOR.execute(() -> updateDeferredView());
+ });
}
/**
@@ -282,16 +292,23 @@
if (!WIDGETS_ENABLED) {
return;
}
- mWidgetHost.stopListening();
- setListeningFlag(false);
+ getWidgetHolderExecutor().execute(() -> {
+ mWidgetHost.stopListening();
+ setListeningFlag(false);
+ });
}
+ /**
+ * Update {@link FLAG_LISTENING} on {@link mFlags} after making binder calls from
+ * {@link sWidgetHost}.
+ */
+ @WorkerThread
protected void setListeningFlag(final boolean isListening) {
if (isListening) {
- mFlags |= FLAG_LISTENING;
+ mFlags.updateAndGet(old -> old | FLAG_LISTENING);
return;
}
- mFlags &= ~FLAG_LISTENING;
+ mFlags.updateAndGet(old -> old & ~FLAG_LISTENING);
}
/**
@@ -373,7 +390,7 @@
* as a result of using the same flow.
*/
protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) {
- if ((mFlags & FLAG_LISTENING) == 0) {
+ if ((mFlags.get() & FLAG_LISTENING) == 0) {
if (view instanceof PendingAppWidgetHostView pv && pv.isDeferredWidget()) {
return view;
} else {
@@ -395,7 +412,7 @@
@NonNull
protected LauncherAppWidgetHostView createViewInternal(
int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
- if ((mFlags & FLAG_LISTENING) == 0) {
+ if ((mFlags.get() & FLAG_LISTENING) == 0) {
// Since the launcher hasn't started listening to widget updates, we can't simply call
// host.createView here because the later will make a binder call to retrieve
// RemoteViews from system process.
@@ -460,7 +477,7 @@
* @return True if the host is listening to the updates, false otherwise
*/
public boolean isListening() {
- return (mFlags & FLAG_LISTENING) != 0;
+ return (mFlags.get() & FLAG_LISTENING) != 0;
}
/**
@@ -469,16 +486,17 @@
*/
private void setShouldListenFlag(int flag, boolean on) {
if (on) {
- mFlags |= flag;
+ mFlags.updateAndGet(old -> old | flag);
} else {
- mFlags &= ~flag;
+ mFlags.updateAndGet(old -> old & ~flag);
}
final boolean listening = isListening();
- if (!listening && shouldListen(mFlags)) {
+ int currentFlag = mFlags.get();
+ if (!listening && shouldListen(currentFlag)) {
// Postpone starting listening until all flags are on.
startListening();
- } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) {
+ } else if (listening && (currentFlag & FLAG_ACTIVITY_STARTED) == 0) {
// Postpone stopping listening until the activity is stopped.
stopListening();
}