summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java115
-rw-r--r--core/java/android/widget/RemoteViews.java71
2 files changed, 144 insertions, 42 deletions
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 40de2985f68a..67ad4594599f 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -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
@@ -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.
@@ -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;
}
@@ -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);
@@ -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);
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2cd390113040..9c2833b91a2b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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 {
@@ -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
@@ -1478,7 +1480,7 @@ public class RemoteViews implements Parcelable, Filter {
/ numOfIntents;
return connectAllUniqueIntents(individualSize, individualBitmapSizeLimit,
- idToIntentMapping);
+ idToIntentMapping, collectionCache);
}
private void collectAllIntentsInternal(@NonNull RemoteViews inViews,
@@ -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);
@@ -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
@@ -1581,39 +1585,24 @@ 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) {
- RemoteCollectionItems items;
- try {
- items = IRemoteViewsFactory.Stub.asInterface(iBinder)
- .getRemoteCollectionItems(individualSize,
- individualBitmapSize);
- } catch (RemoteException re) {
- items = new RemoteCollectionItems.Builder().build();
- Log.e(LOG_TAG, "Error getting collection items from the"
- + " factory", re);
- } finally {
- context.unbindService(this);
- }
-
- if (items == null) {
- items = new RemoteCollectionItems.Builder().build();
- }
-
- result.complete(items);
- }
+ collectionCache.connectAndConsume(intent, iBinder -> {
+ RemoteCollectionItems items;
+ try {
+ items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+ .getRemoteCollectionItems(individualSize,
+ individualBitmapSize);
+ } catch (RemoteException re) {
+ items = new RemoteCollectionItems.Builder().build();
+ Log.e(LOG_TAG, "Error getting collection items from the"
+ + " factory", re);
+ }
- @Override
- public void onNullBinding(ComponentName name) {
- context.unbindService(this);
- }
+ if (items == null) {
+ items = new RemoteCollectionItems.Builder().build();
+ }
- @Override
- public void onServiceDisconnected(ComponentName componentName) { }
- });
+ result.complete(items);
+ }, result.defaultExecutor());
result.completeOnTimeout(
new RemoteCollectionItems.Builder().build(),