From 24c3cc53af1d059da0ec36e33508adcac9051b53 Mon Sep 17 00:00:00 2001 From: Sihua Ma Date: Wed, 7 Jun 2023 19:45:48 +0000 Subject: Deprecating RemoteViewsAdapter in RemoteViews Also hiding the feature behind a flag Bug: 245950570 Test: Manual Change-Id: I52420b2c4cb5a0691257c08870d7446102beabb1 --- core/java/android/widget/RemoteViews.aidl | 1 + core/java/android/widget/RemoteViews.java | 112 +++++++++++++++++++-- core/java/android/widget/RemoteViewsService.java | 31 ++++++ .../config/sysui/SystemUiDeviceConfigFlags.java | 11 ++ .../internal/widget/IRemoteViewsFactory.aidl | 1 + .../src/android/widget/RemoteViewsAdapterTest.java | 17 ++++ 6 files changed, 163 insertions(+), 10 deletions(-) diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl index ec86410cf89c..6a5fc03fcc6e 100644 --- a/core/java/android/widget/RemoteViews.aidl +++ b/core/java/android/widget/RemoteViews.aidl @@ -17,3 +17,4 @@ package android.widget; parcelable RemoteViews; +parcelable RemoteViews.RemoteCollectionItems; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index bd7f5a0924cc..d9e76fefad7f 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -33,16 +33,19 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; +import android.app.AppGlobals; import android.app.Application; import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentSender; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; @@ -65,10 +68,12 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Process; +import android.os.RemoteException; import android.os.StrictMode; import android.os.UserHandle; import android.system.Os; @@ -98,8 +103,10 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.CompoundButton.OnCheckedChangeListener; import com.android.internal.R; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.Preconditions; +import com.android.internal.widget.IRemoteViewsFactory; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -124,7 +131,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Stack; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; @@ -323,6 +332,13 @@ public class RemoteViews implements Parcelable, Filter { private static final LayoutInflater.Filter INFLATER_FILTER = (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); + /** + * The maximum waiting time for remote adapter conversion in milliseconds + * + * @hide + */ + private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000; + /** * Application that hosts the remote views. * @@ -1042,28 +1058,96 @@ public class RemoteViews implements Parcelable, Filter { } private class SetRemoteCollectionItemListAdapterAction extends Action { - private final RemoteCollectionItems mItems; + private @NonNull CompletableFuture mItemsFuture; - SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) { + SetRemoteCollectionItemListAdapterAction(@IdRes int id, + @NonNull RemoteCollectionItems items) { viewId = id; - mItems = items; - mItems.setHierarchyRootData(getHierarchyRootData()); + items.setHierarchyRootData(getHierarchyRootData()); + mItemsFuture = CompletableFuture.completedFuture(items); + } + + SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) { + viewId = id; + mItemsFuture = getItemsFutureFromIntentWithTimeout(intent); + setHierarchyRootData(getHierarchyRootData()); + } + + private static CompletableFuture getItemsFutureFromIntentWithTimeout( + Intent intent) { + if (intent == null) { + Log.e(LOG_TAG, "Null intent received when generating adapter future"); + return CompletableFuture.completedFuture(new RemoteCollectionItems + .Builder().build()); + } + + final Context context = ActivityThread.currentApplication(); + final CompletableFuture result = new CompletableFuture<>(); + + context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), + result.defaultExecutor(), new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, + IBinder iBinder) { + RemoteCollectionItems items; + try { + items = IRemoteViewsFactory.Stub.asInterface(iBinder) + .getRemoteCollectionItems(); + } catch (RemoteException re) { + items = new RemoteCollectionItems.Builder().build(); + Log.e(LOG_TAG, "Error getting collection items from the factory", + re); + } finally { + context.unbindService(this); + } + + result.complete(items); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { } + }); + + result.completeOnTimeout( + new RemoteCollectionItems.Builder().build(), + MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS); + + return result; } SetRemoteCollectionItemListAdapterAction(Parcel parcel) { viewId = parcel.readInt(); - mItems = new RemoteCollectionItems(parcel, getHierarchyRootData()); + mItemsFuture = CompletableFuture.completedFuture( + new RemoteCollectionItems(parcel, getHierarchyRootData())); } @Override public void setHierarchyRootData(HierarchyRootData rootData) { - mItems.setHierarchyRootData(rootData); + mItemsFuture = mItemsFuture + .thenApply(rc -> { + rc.setHierarchyRootData(rootData); + return rc; + }); + } + + private static RemoteCollectionItems getCollectionItemsFromFuture( + CompletableFuture itemsFuture) { + RemoteCollectionItems items; + try { + items = itemsFuture.get(); + } catch (Exception e) { + Log.e(LOG_TAG, "Error getting collection items from future", e); + items = new RemoteCollectionItems.Builder().build(); + } + + return items; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); - mItems.writeToParcel(dest, flags, /* attached= */ true); + RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture); + items.writeToParcel(dest, flags, /* attached= */ true); } @Override @@ -1072,6 +1156,8 @@ public class RemoteViews implements Parcelable, Filter { View target = root.findViewById(viewId); if (target == null) return; + RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture); + // Ensure that we are applying to an AppWidget root if (!(rootParent instanceof AppWidgetHostView)) { Log.e(LOG_TAG, "setRemoteAdapter can only be used for " @@ -1092,10 +1178,10 @@ public class RemoteViews implements Parcelable, Filter { // recycling in setAdapter, so we must call setAdapter again if the number of view types // increases. if (adapter instanceof RemoteCollectionItemsAdapter - && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) { + && adapter.getViewTypeCount() >= items.getViewTypeCount()) { try { ((RemoteCollectionItemsAdapter) adapter).setData( - mItems, params.handler, params.colorResources); + items, params.handler, params.colorResources); } catch (Throwable throwable) { // setData should never failed with the validation in the items builder, but if // it does, catch and rethrow. @@ -1105,7 +1191,7 @@ public class RemoteViews implements Parcelable, Filter { } try { - adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems, + adapterView.setAdapter(new RemoteCollectionItemsAdapter(items, params.handler, params.colorResources)); } catch (Throwable throwable) { // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to @@ -4679,6 +4765,12 @@ public class RemoteViews implements Parcelable, Filter { * providing data to the RemoteViewsAdapter */ public void setRemoteAdapter(@IdRes int viewId, Intent intent) { + if (AppGlobals.getIntCoreSetting( + SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION, + SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1) { + addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent)); + return; + } addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); } diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index 214e5cc01b9e..d4f4d19e9bad 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -42,6 +42,13 @@ public abstract class RemoteViewsService extends Service { new HashMap(); private static final Object sLock = new Object(); + /** + * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory + * + * @hide + */ + private static final int MAX_NUM_ENTRY = 25; + /** * An interface for an adapter between a remote collection view (ListView, GridView, etc) and * the underlying data for that view. The implementor is responsible for making a RemoteView @@ -227,6 +234,30 @@ public abstract class RemoteViewsService extends Service { } } + @Override + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems + .Builder().build(); + + try { + RemoteViews.RemoteCollectionItems.Builder itemsBuilder = + new RemoteViews.RemoteCollectionItems.Builder(); + mFactory.onDataSetChanged(); + + itemsBuilder.setHasStableIds(mFactory.hasStableIds()); + final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY); + for (int i = 0; i < numOfEntries; i++) { + itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i)); + } + + items = itemsBuilder.build(); + } catch (Exception ex) { + Thread t = Thread.currentThread(); + Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); + } + return items; + } + private RemoteViewsFactory mFactory; private boolean mIsCreated; } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 9ffccb34f44d..0ba271f253fb 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -531,6 +531,17 @@ public final class SystemUiDeviceConfigFlags { */ public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot"; + /** + * (boolean) Whether to enable the adapter conversion in RemoteViews + */ + public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion"; + + /** + * Default value for whether the adapter conversion is enabled or not. This is set for + * RemoteViews and should not be a common practice. + */ + public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false; + /** * (boolean) Whether the task manager should show a stop button if the app is allowlisted * by the user. diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl index c06dab9f75d6..918d9c029ef5 100644 --- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl @@ -39,5 +39,6 @@ interface IRemoteViewsFactory { boolean hasStableIds(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isCreated(); + RemoteViews.RemoteCollectionItems getRemoteCollectionItems(); } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java index 184b9eac24f3..4f722cefcf9f 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java @@ -353,6 +353,23 @@ public class RemoteViewsAdapterTest { public boolean isCreated() { return false; } + + @Override + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + RemoteViews.RemoteCollectionItems.Builder itemsBuilder = + new RemoteViews.RemoteCollectionItems.Builder(); + itemsBuilder.setHasStableIds(hasStableIds()) + .setViewTypeCount(getViewTypeCount()); + try { + for (int i = 0; i < mCount; i++) { + itemsBuilder.addItem(getItemId(i), getViewAt(i)); + } + } catch (RemoteException e) { + // No-op + } + + return itemsBuilder.build(); + } } private static class DistinctIntent extends Intent { -- cgit v1.2.3-59-g8ed1b