diff options
5 files changed, 130 insertions, 49 deletions
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 13dc4efb374d..a732717e3042 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1105,6 +1105,7 @@ public class RemoteViews implements Parcelable, Filter { SetRemoteCollectionItemListAdapterAction(Parcel parcel) { mViewId = parcel.readInt(); mIntentId = parcel.readInt(); + mIsReplacedIntoAction = parcel.readBoolean(); mServiceIntent = parcel.readTypedObject(Intent.CREATOR); mItems = mServiceIntent != null ? null @@ -1128,6 +1129,7 @@ public class RemoteViews implements Parcelable, Filter { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mViewId); dest.writeInt(mIntentId); + dest.writeBoolean(mIsReplacedIntoAction); dest.writeTypedObject(mServiceIntent, flags); if (mItems != null) { mItems.writeToParcel(dest, flags, /* attached= */ true); @@ -1209,6 +1211,19 @@ public class RemoteViews implements Parcelable, Filter { } /** + * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter. + * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size + * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit + * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer + * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the + * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews + * directly to the parcel as we did in RemoteViewsService) + * + * @hide + */ + private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8); + + /** * @hide */ public CompletableFuture<Void> collectAllIntents() { @@ -1260,17 +1275,47 @@ public class RemoteViews implements Parcelable, Filter { return mUriToCollectionMapping.get(uri); } - CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) { - CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null); + public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete( + @NonNull RemoteViews inViews) { + 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 + // the parcel to better estimate the space left for reallocation. + collectAllIntentsInternal(inViews, idToIntentMapping); + + // Calculate the individual size here + int numOfIntents = idToIntentMapping.size(); + if (numOfIntents == 0) { + Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id"); + return CompletableFuture.completedFuture(null); + } + + Parcel sizeTestParcel = Parcel.obtain(); + // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection + // cache to see how much space is left for the RemoteCollectionItems that are to be + // updated. + RemoteViews.this.writeToParcel(sizeTestParcel, + /* flags= */ 0, + /* intentsToIgnore= */ idToIntentMapping); + int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize(); + sizeTestParcel.recycle(); + + int individualSize = remainingSize < 0 + ? 0 + : remainingSize / numOfIntents; + + return connectAllUniqueIntents(individualSize, idToIntentMapping); + } + + private void collectAllIntentsInternal(@NonNull RemoteViews inViews, + @NonNull SparseArray<Intent> idToIntentMapping) { if (inViews.hasSizedRemoteViews()) { for (RemoteViews remoteViews : inViews.mSizedRemoteViews) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(remoteViews)); + collectAllIntentsInternal(remoteViews, idToIntentMapping); } } else if (inViews.hasLandscapeAndPortraitLayouts()) { - collectionFuture = CompletableFuture.allOf( - collectAllIntentsNoComplete(inViews.mLandscape), - collectAllIntentsNoComplete(inViews.mPortrait)); + collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping); + collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping); } else if (inViews.mActions != null) { for (Action action : inViews.mActions) { if (action instanceof SetRemoteCollectionItemListAdapterAction rca) { @@ -1280,13 +1325,16 @@ public class RemoteViews implements Parcelable, Filter { } if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) { - final String uri = mIdToUriMapping.get(rca.mIntentId); - collectionFuture = CompletableFuture.allOf(collectionFuture, - getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) - .thenAccept(rc -> { - rc.setHierarchyRootData(getHierarchyRootData()); - mUriToCollectionMapping.put(uri, rc); - })); + rca.mIsReplacedIntoAction = false; + + // Avoid redundant connections for the same intent. Also making sure + // that the number of connections we are making is always equal to the + // nmuber of unique intents that are being used for the updates. + if (idToIntentMapping.contains(rca.mIntentId)) { + continue; + } + + idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); rca.mItems = null; continue; } @@ -1295,7 +1343,7 @@ public class RemoteViews implements Parcelable, Filter { // intents. if (rca.mServiceIntent != null) { final String uri = rca.mServiceIntent.toUri(0); - int index = mIdToUriMapping.indexOfValue(uri); + int index = mIdToUriMapping.indexOfValueByValue(uri); if (index == -1) { int newIntentId = mIdToUriMapping.size(); rca.mIntentId = newIntentId; @@ -1305,41 +1353,50 @@ public class RemoteViews implements Parcelable, Filter { rca.mItems = null; continue; } - collectionFuture = CompletableFuture.allOf(collectionFuture, - getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) - .thenAccept(rc -> { - rc.setHierarchyRootData(getHierarchyRootData()); - mUriToCollectionMapping.put(uri, rc); - })); + + idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); rca.mItems = null; } else { for (RemoteViews views : rca.mItems.mViews) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(views)); + collectAllIntentsInternal(views, idToIntentMapping); } } } else if (action instanceof ViewGroupActionAdd vgaa && vgaa.mNestedViews != null) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(vgaa.mNestedViews)); + collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping); } } } + } - return collectionFuture; + private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize, + @NonNull SparseArray<Intent> idToIntentMapping) { + 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) + .thenAccept(items -> { + items.setHierarchyRootData(getHierarchyRootData()); + mUriToCollectionMapping.put(currentIntentUri, items); + })); + } + + return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new)); } private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( - Intent intent) { + Intent intent, int individualSize) { if (intent == null) { Log.e(LOG_TAG, "Null intent received when generating adapter future"); return CompletableFuture.completedFuture(new RemoteCollectionItems - .Builder().build()); + .Builder().build()); } final Context context = ActivityThread.currentApplication(); - final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); + final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), result.defaultExecutor(), new ServiceConnection() { @Override @@ -1348,11 +1405,11 @@ public class RemoteViews implements Parcelable, Filter { RemoteCollectionItems items; try { items = IRemoteViewsFactory.Stub.asInterface(iBinder) - .getRemoteCollectionItems(); + .getRemoteCollectionItems(individualSize); } catch (RemoteException re) { items = new RemoteCollectionItems.Builder().build(); - Log.e(LOG_TAG, "Error getting collection items from the factory", - re); + Log.e(LOG_TAG, "Error getting collection items from the" + + " factory", re); } finally { context.unbindService(this); } @@ -1371,10 +1428,17 @@ public class RemoteViews implements Parcelable, Filter { return result; } - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(Parcel out, int flags, + @Nullable SparseArray<Intent> intentsToIgnore) { out.writeInt(mIdToUriMapping.size()); for (int i = 0; i < mIdToUriMapping.size(); i++) { - out.writeInt(mIdToUriMapping.keyAt(i)); + int currentIntentId = mIdToUriMapping.keyAt(i); + if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) { + // Skip writing collections that are to be updated in the following steps to + // better estimate the RemoteViews size. + continue; + } + out.writeInt(currentIntentId); String intentUri = mIdToUriMapping.valueAt(i); out.writeString8(intentUri); mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true); @@ -6725,7 +6789,13 @@ public class RemoteViews implements Parcelable, Filter { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcel(dest, flags, /* intentsToIgnore= */ null); + } + + private void writeToParcel(Parcel dest, int flags, + @Nullable SparseArray<Intent> intentsToIgnore) { boolean prevSquashingAllowed = dest.allowSquashing(); if (!hasMultipleLayouts()) { @@ -6734,7 +6804,7 @@ public class RemoteViews implements Parcelable, Filter { // is shared by all children. if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } mApplication.writeToParcel(dest, flags); if (mIsRoot || mIdealSize == null) { @@ -6751,7 +6821,7 @@ public class RemoteViews implements Parcelable, Filter { dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } dest.writeInt(mSizedRemoteViews.size()); for (RemoteViews view : mSizedRemoteViews) { @@ -6763,7 +6833,7 @@ public class RemoteViews implements Parcelable, Filter { // is shared by all children. if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } mLandscape.writeToParcel(dest, flags); // Both RemoteViews already share the same package and user diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index a250a867b9de..d4a5bbd79d31 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -19,6 +19,7 @@ package android.widget; import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.os.Parcel; import com.android.internal.widget.IRemoteViewsFactory; @@ -43,13 +44,6 @@ public abstract class RemoteViewsService extends Service { 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 = 10; - - /** * 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 * for each item in the data set. This interface is a thin wrapper around {@link Adapter}. @@ -235,9 +229,10 @@ public abstract class RemoteViewsService extends Service { } @Override - public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); + Parcel capSizeTestParcel = Parcel.obtain(); try { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = @@ -245,15 +240,25 @@ public abstract class RemoteViewsService extends Service { mFactory.onDataSetChanged(); itemsBuilder.setHasStableIds(mFactory.hasStableIds()); - final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY); + final int numOfEntries = mFactory.getCount(); + for (int i = 0; i < numOfEntries; i++) { - itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i)); + final long currentItemId = mFactory.getItemId(i); + final RemoteViews currentView = mFactory.getViewAt(i); + currentView.writeToParcel(capSizeTestParcel, 0); + if (capSizeTestParcel.dataSize() > capSize) { + break; + } + itemsBuilder.addItem(currentItemId, currentView); } items = itemsBuilder.build(); } catch (Exception ex) { Thread t = Thread.currentThread(); Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); + } finally { + // Recycle the parcel + capSizeTestParcel.recycle(); } return items; } diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl index 918d9c029ef5..1d0e97295571 100644 --- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl @@ -39,6 +39,6 @@ interface IRemoteViewsFactory { boolean hasStableIds(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isCreated(); - RemoteViews.RemoteCollectionItems getRemoteCollectionItems(); + RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize); } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java index 4f722cefcf9f..6ab77dc9d535 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java @@ -355,7 +355,7 @@ public class RemoteViewsAdapterTest { } @Override - public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = new RemoteViews.RemoteCollectionItems.Builder(); itemsBuilder.setHasStableIds(hasStableIds()) diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 6f45f60f6dfa..fb55af985c21 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -2074,6 +2074,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks, int appWidgetId, int viewId, long requestId) { try { + Slog.d(TAG, "Trying to notify widget view data changed"); callbacks.viewDataChanged(appWidgetId, viewId); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2158,6 +2159,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks, int appWidgetId, RemoteViews views, long requestId) { try { + Slog.d(TAG, "Trying to notify widget update for package " + + views.getPackage()); callbacks.updateAppWidget(appWidgetId, views); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2196,6 +2199,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks, int appWidgetId, AppWidgetProviderInfo info, long requestId) { try { + Slog.d(TAG, "Trying to notify provider update"); callbacks.providerChanged(appWidgetId, info); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2239,6 +2243,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId, long requestId) { try { + Slog.d(TAG, "Trying to notify widget removed"); callbacks.appWidgetRemoved(appWidgetId); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2286,6 +2291,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) { try { + Slog.d(TAG, "Trying to notify widget providers changed"); callbacks.providersChanged(); } catch (RemoteException re) { synchronized (mLock) { |