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

Commit 385b6196 authored by Sihua Ma's avatar Sihua Ma
Browse files

Update RemoteViews in background

This helps ensure that the client doesn't block when calling expensive
updating functions in AppWidgetManager

Test: Manual
Bug: 245950570
Change-Id: Ia90d8e276567d5f7156c53c3d32be80420b4cff1
parent 770f7217
Loading
Loading
Loading
Loading
+108 −19
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.IServiceConnection;
import android.app.PendingIntent;
@@ -39,6 +40,10 @@ import android.content.pm.ShortcutInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
@@ -53,6 +58,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

/**
 * Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -475,6 +481,8 @@ public class AppWidgetManager {

    private static final String TAG = "AppWidgetManager";

    private static Executor sUpdateExecutor;

    /**
     * An intent extra that contains multiple appWidgetIds.  These are id values as
     * they were provided to the application during a recent restore from backup.  It is
@@ -510,6 +518,8 @@ public class AppWidgetManager {
    private final IAppWidgetService mService;
    private final DisplayMetrics mDisplayMetrics;

    private boolean mHasPostedLegacyLists = false;

    /**
     * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
     * Context} object.
@@ -552,6 +562,13 @@ public class AppWidgetManager {
        });
    }

    private boolean isPostingTaskToBackground(@Nullable RemoteViews views) {
        return Looper.myLooper() == Looper.getMainLooper()
                && RemoteViews.isAdapterConversionEnabled()
                && (mHasPostedLegacyLists = mHasPostedLegacyLists
                        || (views != null && views.hasLegacyLists()));
    }

    /**
     * Set the RemoteViews to use for the specified appWidgetIds.
     * <p>
@@ -575,6 +592,19 @@ public class AppWidgetManager {
        if (mService == null) {
            return;
        }

        if (isPostingTaskToBackground(views)) {
            createUpdateExecutorIfNull().execute(() -> {
                try {
                    mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error updating app widget views in background", e);
                }
            });

            return;
        }

        try {
            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
        } catch (RemoteException e) {
@@ -683,6 +713,19 @@ public class AppWidgetManager {
        if (mService == null) {
            return;
        }

        if (isPostingTaskToBackground(views)) {
            createUpdateExecutorIfNull().execute(() -> {
                try {
                    mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error partially updating app widget views in background", e);
                }
            });

            return;
        }

        try {
            mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
        } catch (RemoteException e) {
@@ -738,6 +781,19 @@ public class AppWidgetManager {
        if (mService == null) {
            return;
        }

        if (isPostingTaskToBackground(views)) {
            createUpdateExecutorIfNull().execute(() -> {
                try {
                    mService.updateAppWidgetProvider(provider, views);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error updating app widget view using provider in background", e);
                }
            });

            return;
        }

        try {
            mService.updateAppWidgetProvider(provider, views);
        } catch (RemoteException e) {
@@ -786,8 +842,28 @@ public class AppWidgetManager {
        if (mService == null) {
            return;
        }

        if (!RemoteViews.isAdapterConversionEnabled()) {
            try {
                mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }

            return;
        }

        if (Looper.myLooper() == Looper.getMainLooper()) {
            mHasPostedLegacyLists = true;
            createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds,
                    viewId));
        } else {
            notifyCollectionWidgetChange(appWidgetIds, viewId);
        }
    }

    private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) {
        try {
            if (RemoteViews.isAdapterConversionEnabled()) {
            List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
            for (int i = 0; i < appWidgetIds.length; i++) {
                final int widgetId = appWidgetIds[i];
@@ -803,11 +879,8 @@ public class AppWidgetManager {
                }));
            }
            CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
            } else {
                mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (Exception e) {
            Log.e(TAG, "Error notifying changes for all widgets", e);
        }
    }

@@ -1338,4 +1411,20 @@ public class AppWidgetManager {
            throw e.rethrowFromSystemServer();
        }
    }

    @UiThread
    private static @NonNull Executor createUpdateExecutorIfNull() {
        if (sUpdateExecutor == null) {
            sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler(
                    "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND));
        }

        return sUpdateExecutor;
    }

    private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) {
        HandlerThread thread = new HandlerThread(name, priority);
        thread.start();
        return thread.getThreadHandler();
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -801,6 +801,11 @@ public class RemoteViews implements Parcelable, Filter {
                    mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId,
                            itemsAction.mServiceIntent));
                    isActionReplaced = true;
                } else if (action instanceof SetRemoteViewsAdapterIntent intentAction
                        && intentAction.viewId == viewId) {
                    mActions.set(i, new SetRemoteCollectionItemListAdapterAction(
                            intentAction.viewId, intentAction.intent));
                    isActionReplaced = true;
                } else if (action instanceof ViewGroupActionAdd groupAction
                        && groupAction.mNestedViews != null) {
                    isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId);
@@ -822,6 +827,42 @@ public class RemoteViews implements Parcelable, Filter {
        return isActionReplaced;
    }

    /**
     * @return True if has set remote adapter using service intent
     * @hide
     */
    public boolean hasLegacyLists() {
        if (mActions != null) {
            for (int i = 0; i < mActions.size(); i++) {
                Action action = mActions.get(i);
                if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
                        && itemsAction.mServiceIntent != null)
                        || (action instanceof SetRemoteViewsAdapterIntent intentAction
                                && intentAction.intent != null)
                        || (action instanceof ViewGroupActionAdd groupAction
                                && groupAction.mNestedViews != null
                                && groupAction.mNestedViews.hasLegacyLists())) {
                    return true;
                }
            }
        }
        if (mSizedRemoteViews != null) {
            for (int i = 0; i < mSizedRemoteViews.size(); i++) {
                if (mSizedRemoteViews.get(i).hasLegacyLists()) {
                    return true;
                }
            }
        }
        if (mLandscape != null && mLandscape.hasLegacyLists()) {
            return true;
        }
        if (mPortrait != null && mPortrait.hasLegacyLists()) {
            return true;
        }

        return false;
    }

    private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
        if (icon != null && (icon.getType() == Icon.TYPE_URI
                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {