Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b908c01c authored by Sihua Ma's avatar Sihua Ma
Browse files

Cap the size for RemoteCollectionItemsAdapter

Bug: 245950570
Test: Manual
Change-Id: I8f4a6d94c1b728c2f1e8c4371481ee9f9db2ecc5
parent 12f3dd35
Loading
Loading
Loading
Loading
+107 −37
Original line number Diff line number Diff line
@@ -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);
@@ -1208,6 +1210,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
     */
@@ -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,32 +1353,41 @@ 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
@@ -1338,8 +1395,8 @@ public class RemoteViews implements Parcelable, Filter {
            }

            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);
@@ -6724,7 +6788,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()) {
@@ -6733,7 +6803,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) {
@@ -6750,7 +6820,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) {
@@ -6762,7 +6832,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
+15 −10
Original line number Diff line number Diff line
@@ -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;

@@ -42,13 +43,6 @@ 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 = 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
@@ -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;
        }
+1 −1
Original line number Diff line number Diff line
@@ -39,6 +39,6 @@ interface IRemoteViewsFactory {
    boolean hasStableIds();
    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
    boolean isCreated();
    RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
    RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize);
}
+1 −1
Original line number Diff line number Diff line
@@ -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())
+7 −0
Original line number Diff line number Diff line
@@ -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,9 @@ 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 == null ? "null" : views.getPackage())
                    + " with widget id: " + appWidgetId);
            callbacks.updateAppWidget(appWidgetId, views);
            host.lastWidgetUpdateSequenceNo = requestId;
        } catch (RemoteException re) {
@@ -2196,6 +2200,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 +2244,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 +2292,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) {