Loading core/java/android/widget/RemoteViews.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -17,3 +17,4 @@ package android.widget; parcelable RemoteViews; parcelable RemoteViews.RemoteCollectionItems; core/java/android/widget/RemoteViews.java +102 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -1042,28 +1058,96 @@ public class RemoteViews implements Parcelable, Filter { } private class SetRemoteCollectionItemListAdapterAction extends Action { private final RemoteCollectionItems mItems; private @NonNull CompletableFuture<RemoteCollectionItems> 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<RemoteCollectionItems> 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<RemoteCollectionItems> 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<RemoteCollectionItems> 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 Loading @@ -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 " Loading @@ -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. Loading @@ -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 Loading Loading @@ -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)); } Loading core/java/android/widget/RemoteViewsService.java +31 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,13 @@ public abstract class RemoteViewsService extends Service { new HashMap<Intent.FilterComparison, RemoteViewsFactory>(); 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 Loading Loading @@ -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; } Loading core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -39,5 +39,6 @@ interface IRemoteViewsFactory { boolean hasStableIds(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isCreated(); RemoteViews.RemoteCollectionItems getRemoteCollectionItems(); } Loading
core/java/android/widget/RemoteViews.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -17,3 +17,4 @@ package android.widget; parcelable RemoteViews; parcelable RemoteViews.RemoteCollectionItems;
core/java/android/widget/RemoteViews.java +102 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -1042,28 +1058,96 @@ public class RemoteViews implements Parcelable, Filter { } private class SetRemoteCollectionItemListAdapterAction extends Action { private final RemoteCollectionItems mItems; private @NonNull CompletableFuture<RemoteCollectionItems> 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<RemoteCollectionItems> 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<RemoteCollectionItems> 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<RemoteCollectionItems> 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 Loading @@ -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 " Loading @@ -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. Loading @@ -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 Loading Loading @@ -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)); } Loading
core/java/android/widget/RemoteViewsService.java +31 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,13 @@ public abstract class RemoteViewsService extends Service { new HashMap<Intent.FilterComparison, RemoteViewsFactory>(); 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 Loading Loading @@ -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; } Loading
core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -39,5 +39,6 @@ interface IRemoteViewsFactory { boolean hasStableIds(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isCreated(); RemoteViews.RemoteCollectionItems getRemoteCollectionItems(); }