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

Commit e9ea65ba authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Cap the size for RemoteCollectionItemsAdapter" into main

parents f44362bd b908c01c
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) {