Loading core/java/android/appwidget/AppWidgetManager.java +114 −1 Original line number Diff line number Diff line Loading @@ -37,36 +37,44 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.Intent.FilterComparison; import android.content.IntentSender; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FunctionalUtils; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Updates AppWidget state; gets information about installed AppWidget providers and other Loading Loading @@ -592,6 +600,8 @@ public class AppWidgetManager { private boolean mHasPostedLegacyLists = false; private @NonNull ServiceCollectionCache mServiceCollectionCache; /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Context} object. Loading @@ -612,6 +622,7 @@ public class AppWidgetManager { mPackageName = context.getOpPackageName(); mService = service; mDisplayMetrics = context.getResources().getDisplayMetrics(); mServiceCollectionCache = new ServiceCollectionCache(context, /* timeout= */ 5000L); if (mService == null) { return; } Loading Loading @@ -649,7 +660,7 @@ public class AppWidgetManager { final RemoteViews viewsCopy = new RemoteViews(original); Runnable updateWidgetWithTask = () -> { try { viewsCopy.collectAllIntents(mMaxBitmapMemory).get(); viewsCopy.collectAllIntents(mMaxBitmapMemory, mServiceCollectionCache).get(); action.acceptOrThrow(viewsCopy); } catch (Exception e) { Log.e(TAG, failureMsg, e); Loading Loading @@ -1629,4 +1640,106 @@ public class AppWidgetManager { thread.start(); return thread.getThreadHandler(); } /** * @hide */ public static class ServiceCollectionCache { private final Context mContext; private final Handler mHandler; private final long mTimeOut; private final Map<FilterComparison, ConnectionTask> mActiveConnections = new ArrayMap<>(); public ServiceCollectionCache(Context context, long timeOut) { mContext = context; mHandler = new Handler(BackgroundThread.getHandler().getLooper()); mTimeOut = timeOut; } /** * Connect to the service indicated by the {@code Intent}, and consume the binder on the * specified executor */ public void connectAndConsume(Intent intent, Consumer<IBinder> task, Executor executor) { mHandler.post(() -> connectAndConsumeInner(intent, task, executor)); } private void connectAndConsumeInner(Intent intent, Consumer<IBinder> task, Executor executor) { ConnectionTask activeConnection = mActiveConnections.computeIfAbsent( new FilterComparison(intent), ConnectionTask::new); activeConnection.add(task, executor); } private class ConnectionTask implements ServiceConnection { private final Runnable mDestroyAfterTimeout = this::onDestroyTimeout; private final ArrayDeque<Pair<Consumer<IBinder>, Executor>> mTaskQueue = new ArrayDeque<>(); private boolean mOnDestroyTimeout = false; private IBinder mIBinder; ConnectionTask(@NonNull FilterComparison filter) { mContext.bindService(filter.getIntent(), Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), mHandler::post, this); } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mIBinder = iBinder; mHandler.post(this::handleNext); } @Override public void onNullBinding(ComponentName name) { // Use an empty binder, follow up tasks will handle the failure onServiceConnected(name, new Binder()); } @Override public void onServiceDisconnected(ComponentName componentName) { } void add(Consumer<IBinder> task, Executor executor) { mTaskQueue.add(Pair.create(task, executor)); if (mOnDestroyTimeout) { // If we are waiting for timeout, cancel it and execute the next task handleNext(); } } private void handleNext() { mHandler.removeCallbacks(mDestroyAfterTimeout); Pair<Consumer<IBinder>, Executor> next = mTaskQueue.pollFirst(); if (next != null) { mOnDestroyTimeout = false; next.second.execute(() -> { next.first.accept(mIBinder); mHandler.post(this::handleNext); }); } else { // Finished all tasks, start a timeout to unbind this service mOnDestroyTimeout = true; mHandler.postDelayed(mDestroyAfterTimeout, mTimeOut); } } /** * Called after we have waited for {@link #mTimeOut} after the last task is finished */ private void onDestroyTimeout() { if (!mTaskQueue.isEmpty()) { handleNext(); return; } mContext.unbindService(this); mActiveConnections.values().remove(this); } } } } core/java/android/widget/RemoteViews.java +30 −41 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager.ServiceCollectionCache; import android.appwidget.flags.Flags; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; Loading @@ -54,7 +55,6 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentSender; import android.content.ServiceConnection; import android.content.om.FabricatedOverlay; import android.content.om.OverlayInfo; import android.content.om.OverlayManager; Loading Loading @@ -82,7 +82,6 @@ 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; Loading Loading @@ -127,8 +126,8 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import com.android.internal.R; import com.android.internal.util.Preconditions; import com.android.internal.widget.IRemoteViewsFactory; import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.player.RemoteComposeDocument; import com.android.internal.widget.remotecompose.player.RemoteComposePlayer; Loading Loading @@ -1391,8 +1390,10 @@ public class RemoteViews implements Parcelable, Filter { /** * @hide */ public CompletableFuture<Void> collectAllIntents(int bitmapSizeLimit) { return mCollectionCache.collectAllIntentsNoComplete(this, bitmapSizeLimit); public CompletableFuture<Void> collectAllIntents(int bitmapSizeLimit, @NonNull ServiceCollectionCache collectionCache) { return mCollectionCache.collectAllIntentsNoComplete(this, bitmapSizeLimit, collectionCache); } private class RemoteCollectionCache { Loading Loading @@ -1446,7 +1447,8 @@ public class RemoteViews implements Parcelable, Filter { } public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete( @NonNull RemoteViews inViews, int bitmapSizeLimit) { @NonNull RemoteViews inViews, int bitmapSizeLimit, @NonNull ServiceCollectionCache collectionCache) { SparseArray<Intent> idToIntentMapping = new SparseArray<>(); // Collect the number of uinque Intent (which is equal to the number of new connections // to make) for size allocation and exclude certain collections from being written to Loading Loading @@ -1478,7 +1480,7 @@ public class RemoteViews implements Parcelable, Filter { / numOfIntents; return connectAllUniqueIntents(individualSize, individualBitmapSizeLimit, idToIntentMapping); idToIntentMapping, collectionCache); } private void collectAllIntentsInternal(@NonNull RemoteViews inViews, Loading Loading @@ -1544,13 +1546,14 @@ public class RemoteViews implements Parcelable, Filter { } private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize, int individualBitmapSize, @NonNull SparseArray<Intent> idToIntentMapping) { int individualBitmapSize, @NonNull SparseArray<Intent> idToIntentMapping, @NonNull ServiceCollectionCache collectionCache) { List<CompletableFuture<Void>> intentFutureList = new ArrayList<>(); for (int i = 0; i < idToIntentMapping.size(); i++) { String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i)); Intent currentIntent = idToIntentMapping.valueAt(i); intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent, individualSize, individualBitmapSize) individualSize, individualBitmapSize, collectionCache) .thenAccept(items -> { items.setHierarchyRootData(getHierarchyRootData()); mUriToCollectionMapping.put(currentIntentUri, items); Loading @@ -1561,7 +1564,8 @@ public class RemoteViews implements Parcelable, Filter { } private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( Intent intent, int individualSize, int individualBitmapSize) { Intent intent, int individualSize, int individualBitmapSize, @NonNull ServiceCollectionCache collectionCache) { if (intent == null) { Log.e(LOG_TAG, "Null intent received when generating adapter future"); return CompletableFuture.completedFuture(new RemoteCollectionItems Loading @@ -1581,11 +1585,7 @@ public class RemoteViews implements Parcelable, Filter { return result; } context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), result.defaultExecutor(), new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { collectionCache.connectAndConsume(intent, iBinder -> { RemoteCollectionItems items; try { items = IRemoteViewsFactory.Stub.asInterface(iBinder) Loading @@ -1595,8 +1595,6 @@ public class RemoteViews implements Parcelable, Filter { items = new RemoteCollectionItems.Builder().build(); Log.e(LOG_TAG, "Error getting collection items from the" + " factory", re); } finally { context.unbindService(this); } if (items == null) { Loading @@ -1604,16 +1602,7 @@ public class RemoteViews implements Parcelable, Filter { } result.complete(items); } @Override public void onNullBinding(ComponentName name) { context.unbindService(this); } @Override public void onServiceDisconnected(ComponentName componentName) { } }); }, result.defaultExecutor()); result.completeOnTimeout( new RemoteCollectionItems.Builder().build(), Loading Loading
core/java/android/appwidget/AppWidgetManager.java +114 −1 Original line number Diff line number Diff line Loading @@ -37,36 +37,44 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.Intent.FilterComparison; import android.content.IntentSender; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FunctionalUtils; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Updates AppWidget state; gets information about installed AppWidget providers and other Loading Loading @@ -592,6 +600,8 @@ public class AppWidgetManager { private boolean mHasPostedLegacyLists = false; private @NonNull ServiceCollectionCache mServiceCollectionCache; /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Context} object. Loading @@ -612,6 +622,7 @@ public class AppWidgetManager { mPackageName = context.getOpPackageName(); mService = service; mDisplayMetrics = context.getResources().getDisplayMetrics(); mServiceCollectionCache = new ServiceCollectionCache(context, /* timeout= */ 5000L); if (mService == null) { return; } Loading Loading @@ -649,7 +660,7 @@ public class AppWidgetManager { final RemoteViews viewsCopy = new RemoteViews(original); Runnable updateWidgetWithTask = () -> { try { viewsCopy.collectAllIntents(mMaxBitmapMemory).get(); viewsCopy.collectAllIntents(mMaxBitmapMemory, mServiceCollectionCache).get(); action.acceptOrThrow(viewsCopy); } catch (Exception e) { Log.e(TAG, failureMsg, e); Loading Loading @@ -1629,4 +1640,106 @@ public class AppWidgetManager { thread.start(); return thread.getThreadHandler(); } /** * @hide */ public static class ServiceCollectionCache { private final Context mContext; private final Handler mHandler; private final long mTimeOut; private final Map<FilterComparison, ConnectionTask> mActiveConnections = new ArrayMap<>(); public ServiceCollectionCache(Context context, long timeOut) { mContext = context; mHandler = new Handler(BackgroundThread.getHandler().getLooper()); mTimeOut = timeOut; } /** * Connect to the service indicated by the {@code Intent}, and consume the binder on the * specified executor */ public void connectAndConsume(Intent intent, Consumer<IBinder> task, Executor executor) { mHandler.post(() -> connectAndConsumeInner(intent, task, executor)); } private void connectAndConsumeInner(Intent intent, Consumer<IBinder> task, Executor executor) { ConnectionTask activeConnection = mActiveConnections.computeIfAbsent( new FilterComparison(intent), ConnectionTask::new); activeConnection.add(task, executor); } private class ConnectionTask implements ServiceConnection { private final Runnable mDestroyAfterTimeout = this::onDestroyTimeout; private final ArrayDeque<Pair<Consumer<IBinder>, Executor>> mTaskQueue = new ArrayDeque<>(); private boolean mOnDestroyTimeout = false; private IBinder mIBinder; ConnectionTask(@NonNull FilterComparison filter) { mContext.bindService(filter.getIntent(), Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), mHandler::post, this); } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mIBinder = iBinder; mHandler.post(this::handleNext); } @Override public void onNullBinding(ComponentName name) { // Use an empty binder, follow up tasks will handle the failure onServiceConnected(name, new Binder()); } @Override public void onServiceDisconnected(ComponentName componentName) { } void add(Consumer<IBinder> task, Executor executor) { mTaskQueue.add(Pair.create(task, executor)); if (mOnDestroyTimeout) { // If we are waiting for timeout, cancel it and execute the next task handleNext(); } } private void handleNext() { mHandler.removeCallbacks(mDestroyAfterTimeout); Pair<Consumer<IBinder>, Executor> next = mTaskQueue.pollFirst(); if (next != null) { mOnDestroyTimeout = false; next.second.execute(() -> { next.first.accept(mIBinder); mHandler.post(this::handleNext); }); } else { // Finished all tasks, start a timeout to unbind this service mOnDestroyTimeout = true; mHandler.postDelayed(mDestroyAfterTimeout, mTimeOut); } } /** * Called after we have waited for {@link #mTimeOut} after the last task is finished */ private void onDestroyTimeout() { if (!mTaskQueue.isEmpty()) { handleNext(); return; } mContext.unbindService(this); mActiveConnections.values().remove(this); } } } }
core/java/android/widget/RemoteViews.java +30 −41 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager.ServiceCollectionCache; import android.appwidget.flags.Flags; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; Loading @@ -54,7 +55,6 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentSender; import android.content.ServiceConnection; import android.content.om.FabricatedOverlay; import android.content.om.OverlayInfo; import android.content.om.OverlayManager; Loading Loading @@ -82,7 +82,6 @@ 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; Loading Loading @@ -127,8 +126,8 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import com.android.internal.R; import com.android.internal.util.Preconditions; import com.android.internal.widget.IRemoteViewsFactory; import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.player.RemoteComposeDocument; import com.android.internal.widget.remotecompose.player.RemoteComposePlayer; Loading Loading @@ -1391,8 +1390,10 @@ public class RemoteViews implements Parcelable, Filter { /** * @hide */ public CompletableFuture<Void> collectAllIntents(int bitmapSizeLimit) { return mCollectionCache.collectAllIntentsNoComplete(this, bitmapSizeLimit); public CompletableFuture<Void> collectAllIntents(int bitmapSizeLimit, @NonNull ServiceCollectionCache collectionCache) { return mCollectionCache.collectAllIntentsNoComplete(this, bitmapSizeLimit, collectionCache); } private class RemoteCollectionCache { Loading Loading @@ -1446,7 +1447,8 @@ public class RemoteViews implements Parcelable, Filter { } public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete( @NonNull RemoteViews inViews, int bitmapSizeLimit) { @NonNull RemoteViews inViews, int bitmapSizeLimit, @NonNull ServiceCollectionCache collectionCache) { SparseArray<Intent> idToIntentMapping = new SparseArray<>(); // Collect the number of uinque Intent (which is equal to the number of new connections // to make) for size allocation and exclude certain collections from being written to Loading Loading @@ -1478,7 +1480,7 @@ public class RemoteViews implements Parcelable, Filter { / numOfIntents; return connectAllUniqueIntents(individualSize, individualBitmapSizeLimit, idToIntentMapping); idToIntentMapping, collectionCache); } private void collectAllIntentsInternal(@NonNull RemoteViews inViews, Loading Loading @@ -1544,13 +1546,14 @@ public class RemoteViews implements Parcelable, Filter { } private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize, int individualBitmapSize, @NonNull SparseArray<Intent> idToIntentMapping) { int individualBitmapSize, @NonNull SparseArray<Intent> idToIntentMapping, @NonNull ServiceCollectionCache collectionCache) { List<CompletableFuture<Void>> intentFutureList = new ArrayList<>(); for (int i = 0; i < idToIntentMapping.size(); i++) { String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i)); Intent currentIntent = idToIntentMapping.valueAt(i); intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent, individualSize, individualBitmapSize) individualSize, individualBitmapSize, collectionCache) .thenAccept(items -> { items.setHierarchyRootData(getHierarchyRootData()); mUriToCollectionMapping.put(currentIntentUri, items); Loading @@ -1561,7 +1564,8 @@ public class RemoteViews implements Parcelable, Filter { } private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( Intent intent, int individualSize, int individualBitmapSize) { Intent intent, int individualSize, int individualBitmapSize, @NonNull ServiceCollectionCache collectionCache) { if (intent == null) { Log.e(LOG_TAG, "Null intent received when generating adapter future"); return CompletableFuture.completedFuture(new RemoteCollectionItems Loading @@ -1581,11 +1585,7 @@ public class RemoteViews implements Parcelable, Filter { return result; } context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), result.defaultExecutor(), new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { collectionCache.connectAndConsume(intent, iBinder -> { RemoteCollectionItems items; try { items = IRemoteViewsFactory.Stub.asInterface(iBinder) Loading @@ -1595,8 +1595,6 @@ public class RemoteViews implements Parcelable, Filter { items = new RemoteCollectionItems.Builder().build(); Log.e(LOG_TAG, "Error getting collection items from the" + " factory", re); } finally { context.unbindService(this); } if (items == null) { Loading @@ -1604,16 +1602,7 @@ public class RemoteViews implements Parcelable, Filter { } result.complete(items); } @Override public void onNullBinding(ComponentName name) { context.unbindService(this); } @Override public void onServiceDisconnected(ComponentName componentName) { } }); }, result.defaultExecutor()); result.completeOnTimeout( new RemoteCollectionItems.Builder().build(), Loading