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

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

Merge "Create cache for service connections in AppWidgetManager" into main

parents a3762e3a e06786cb
Loading
Loading
Loading
Loading
+114 −1
Original line number Diff line number Diff line
@@ -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);
            }
        }
    }
}
+30 −41
Original line number Diff line number Diff line
@@ -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,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)
@@ -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) {
@@ -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(),