summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sihua Ma <sihua@google.com> 2023-08-08 17:40:37 +0000
committer Sihua Ma <sihua@google.com> 2023-08-22 21:04:36 +0000
commit385b6196e7eb766557d259ae8592ac7ec6c088cb (patch)
tree95d34e18236abdf29a62f324290339695bb5dc61
parent770f721742d0dd406b6922c3539cd043a25c06a2 (diff)
Update RemoteViews in background
This helps ensure that the client doesn't block when calling expensive updating functions in AppWidgetManager Test: Manual Bug: 245950570 Change-Id: Ia90d8e276567d5f7156c53c3d32be80420b4cff1
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java127
-rw-r--r--core/java/android/widget/RemoteViews.java41
2 files changed, 149 insertions, 19 deletions
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index b159321af837..85d669ed7907 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -24,6 +24,7 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.IServiceConnection;
import android.app.PendingIntent;
@@ -39,6 +40,10 @@ import android.content.pm.ShortcutInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
@@ -53,6 +58,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
/**
* Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -475,6 +481,8 @@ public class AppWidgetManager {
private static final String TAG = "AppWidgetManager";
+ private static Executor sUpdateExecutor;
+
/**
* An intent extra that contains multiple appWidgetIds. These are id values as
* they were provided to the application during a recent restore from backup. It is
@@ -510,6 +518,8 @@ public class AppWidgetManager {
private final IAppWidgetService mService;
private final DisplayMetrics mDisplayMetrics;
+ private boolean mHasPostedLegacyLists = false;
+
/**
* Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
* Context} object.
@@ -552,6 +562,13 @@ public class AppWidgetManager {
});
}
+ private boolean isPostingTaskToBackground(@Nullable RemoteViews views) {
+ return Looper.myLooper() == Looper.getMainLooper()
+ && RemoteViews.isAdapterConversionEnabled()
+ && (mHasPostedLegacyLists = mHasPostedLegacyLists
+ || (views != null && views.hasLegacyLists()));
+ }
+
/**
* Set the RemoteViews to use for the specified appWidgetIds.
* <p>
@@ -575,6 +592,19 @@ public class AppWidgetManager {
if (mService == null) {
return;
}
+
+ if (isPostingTaskToBackground(views)) {
+ createUpdateExecutorIfNull().execute(() -> {
+ try {
+ mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating app widget views in background", e);
+ }
+ });
+
+ return;
+ }
+
try {
mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
} catch (RemoteException e) {
@@ -683,6 +713,19 @@ public class AppWidgetManager {
if (mService == null) {
return;
}
+
+ if (isPostingTaskToBackground(views)) {
+ createUpdateExecutorIfNull().execute(() -> {
+ try {
+ mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error partially updating app widget views in background", e);
+ }
+ });
+
+ return;
+ }
+
try {
mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
} catch (RemoteException e) {
@@ -738,6 +781,19 @@ public class AppWidgetManager {
if (mService == null) {
return;
}
+
+ if (isPostingTaskToBackground(views)) {
+ createUpdateExecutorIfNull().execute(() -> {
+ try {
+ mService.updateAppWidgetProvider(provider, views);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating app widget view using provider in background", e);
+ }
+ });
+
+ return;
+ }
+
try {
mService.updateAppWidgetProvider(provider, views);
} catch (RemoteException e) {
@@ -786,28 +842,45 @@ public class AppWidgetManager {
if (mService == null) {
return;
}
+
+ if (!RemoteViews.isAdapterConversionEnabled()) {
+ try {
+ mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+
+ return;
+ }
+
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ mHasPostedLegacyLists = true;
+ createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds,
+ viewId));
+ } else {
+ notifyCollectionWidgetChange(appWidgetIds, viewId);
+ }
+ }
+
+ private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) {
try {
- if (RemoteViews.isAdapterConversionEnabled()) {
- List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
- for (int i = 0; i < appWidgetIds.length; i++) {
- final int widgetId = appWidgetIds[i];
- updateFutures.add(CompletableFuture.runAsync(() -> {
- try {
- RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId);
- if (views.replaceRemoteCollections(viewId)) {
- updateAppWidget(widgetId, views);
- }
- } catch (Exception e) {
- Log.e(TAG, "Error notifying changes in RemoteViews", e);
+ List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
+ for (int i = 0; i < appWidgetIds.length; i++) {
+ final int widgetId = appWidgetIds[i];
+ updateFutures.add(CompletableFuture.runAsync(() -> {
+ try {
+ RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId);
+ if (views.replaceRemoteCollections(viewId)) {
+ updateAppWidget(widgetId, views);
}
- }));
- }
- CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
- } else {
- mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+ } catch (Exception e) {
+ Log.e(TAG, "Error notifying changes in RemoteViews", e);
+ }
+ }));
}
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
+ } catch (Exception e) {
+ Log.e(TAG, "Error notifying changes for all widgets", e);
}
}
@@ -1338,4 +1411,20 @@ public class AppWidgetManager {
throw e.rethrowFromSystemServer();
}
}
+
+ @UiThread
+ private static @NonNull Executor createUpdateExecutorIfNull() {
+ if (sUpdateExecutor == null) {
+ sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler(
+ "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND));
+ }
+
+ return sUpdateExecutor;
+ }
+
+ private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) {
+ HandlerThread thread = new HandlerThread(name, priority);
+ thread.start();
+ return thread.getThreadHandler();
+ }
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fea3b78f27b4..aa8aac1c174f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -801,6 +801,11 @@ public class RemoteViews implements Parcelable, Filter {
mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId,
itemsAction.mServiceIntent));
isActionReplaced = true;
+ } else if (action instanceof SetRemoteViewsAdapterIntent intentAction
+ && intentAction.viewId == viewId) {
+ mActions.set(i, new SetRemoteCollectionItemListAdapterAction(
+ intentAction.viewId, intentAction.intent));
+ isActionReplaced = true;
} else if (action instanceof ViewGroupActionAdd groupAction
&& groupAction.mNestedViews != null) {
isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId);
@@ -822,6 +827,42 @@ public class RemoteViews implements Parcelable, Filter {
return isActionReplaced;
}
+ /**
+ * @return True if has set remote adapter using service intent
+ * @hide
+ */
+ public boolean hasLegacyLists() {
+ if (mActions != null) {
+ for (int i = 0; i < mActions.size(); i++) {
+ Action action = mActions.get(i);
+ if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
+ && itemsAction.mServiceIntent != null)
+ || (action instanceof SetRemoteViewsAdapterIntent intentAction
+ && intentAction.intent != null)
+ || (action instanceof ViewGroupActionAdd groupAction
+ && groupAction.mNestedViews != null
+ && groupAction.mNestedViews.hasLegacyLists())) {
+ return true;
+ }
+ }
+ }
+ if (mSizedRemoteViews != null) {
+ for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+ if (mSizedRemoteViews.get(i).hasLegacyLists()) {
+ return true;
+ }
+ }
+ }
+ if (mLandscape != null && mLandscape.hasLegacyLists()) {
+ return true;
+ }
+ if (mPortrait != null && mPortrait.hasLegacyLists()) {
+ return true;
+ }
+
+ return false;
+ }
+
private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
if (icon != null && (icon.getType() == Icon.TYPE_URI
|| icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {