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

Commit c12d31c3 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Adding API to specify a dark text specific layout in app-widgets

Bug: 109954539
Test: atest CtsAppWidgetTestCases
Change-Id: I785931469888a09685c45949afcf2e3633233c60
parent 0d7a9a27
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -7690,6 +7690,7 @@ package android.appwidget {
    method protected void prepareView(android.view.View);
    method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo);
    method public void setExecutor(java.util.concurrent.Executor);
    method public void setOnLightBackground(boolean);
    method public void updateAppWidget(android.widget.RemoteViews);
    method public void updateAppWidgetOptions(android.os.Bundle);
    method public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
@@ -54939,6 +54940,7 @@ package android.widget {
    method public void setInt(int, java.lang.String, int);
    method public void setIntent(int, java.lang.String, android.content.Intent);
    method public void setLabelFor(int, int);
    method public void setLightBackgroundLayoutId(int);
    method public void setLong(int, java.lang.String, long);
    method public void setOnClickFillInIntent(int, android.content.Intent);
    method public void setOnClickPendingIntent(int, android.app.PendingIntent);
+2 −2
Original line number Diff line number Diff line
@@ -8232,7 +8232,7 @@ public class Notification implements Parcelable
                customContent = customContent.clone();
                remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
                remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
                remoteViews.setReapplyDisallowed();
                remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
            }
            // also update the end margin if there is an image
            Resources resources = mBuilder.mContext.getResources();
@@ -8363,7 +8363,7 @@ public class Notification implements Parcelable
                customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
                remoteViews.removeAllViews(id);
                remoteViews.addView(id, customContent);
                remoteViews.setReapplyDisallowed();
                remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
            }
            return remoteViews;
        }
+14 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ public class AppWidgetHostView extends FrameLayout {
    int mViewMode = VIEW_MODE_NOINIT;
    int mLayoutId = -1;
    private OnClickHandler mOnClickHandler;
    private boolean mOnLightBackground;

    private Executor mAsyncExecutor;
    private CancellationSignal mLastExecutionSignal;
@@ -373,6 +374,15 @@ public class AppWidgetHostView extends FrameLayout {
        mAsyncExecutor = executor;
    }

    /**
     * Sets whether the widget should is being displayed on a light/white background and use an
     * alternate UI if available.
     * @see RemoteViews#setLightBackgroundLayoutId(int)
     */
    public void setOnLightBackground(boolean useDarkTextLayout) {
        mOnLightBackground = useDarkTextLayout;
    }

    /**
     * Update the AppWidgetProviderInfo for this view, and reset it to the
     * initial layout.
@@ -413,6 +423,10 @@ public class AppWidgetHostView extends FrameLayout {
            mLayoutId = -1;
            mViewMode = VIEW_MODE_DEFAULT;
        } else {
            if (mOnLightBackground) {
                remoteViews = remoteViews.getDarkTextViews();
            }

            if (mAsyncExecutor != null && useAsyncIfPossible) {
                inflateAsync(remoteViews);
                return;
+97 −47
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.widget;

import android.annotation.ColorInt;
import android.annotation.DimenRes;
import android.annotation.IntDef;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.StyleRes;
import android.annotation.UnsupportedAppUsage;
@@ -130,6 +132,12 @@ public class RemoteViews implements Parcelable, Filter {
     */
    static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";

    /**
     * The intent extra that contains {@code true} if inflating as dak text theme.
     * @hide
     */
    static final String EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND = "remoteAdapterOnLightBackground";

    /**
     * The intent extra that contains the bounds for all shared elements.
     */
@@ -163,6 +171,36 @@ public class RemoteViews implements Parcelable, Filter {
    private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21;
    private static final int SET_INT_TAG_TAG = 22;

    /** @hide **/
    @IntDef(flag = true, value = {
            FLAG_REAPPLY_DISALLOWED,
            FLAG_WIDGET_IS_COLLECTION_CHILD,
            FLAG_USE_LIGHT_BACKGROUND_LAYOUT
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ApplyFlags {}
    /**
     * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify
     * the layout in a way that isn't recoverable, since views are being removed.
     * @hide
     */
    public static final int FLAG_REAPPLY_DISALLOWED = 1;
    /**
     * This flag indicates whether this RemoteViews object is being created from a
     * RemoteViewsService for use as a child of a widget collection. This flag is used
     * to determine whether or not certain features are available, in particular,
     * setting on click extras and setting on click pending intents. The former is enabled,
     * and the latter disabled when this flag is true.
     * @hide
     */
    public static final int FLAG_WIDGET_IS_COLLECTION_CHILD = 2;
    /**
     * When this flag is set, the views is inflated with {@link #mLightBackgroundLayoutId} instead
     * of {link #mLayoutId}
     * @hide
     */
    public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;

    /**
     * Application that hosts the remote views.
     *
@@ -177,6 +215,11 @@ public class RemoteViews implements Parcelable, Filter {
    @UnsupportedAppUsage
    private final int mLayoutId;

    /**
     * The resource ID of the layout file in dark text mode. (Added to the parcel)
     */
    private int mLightBackgroundLayoutId = 0;

    /**
     * An array of actions to perform on the view tree once it has been
     * inflated
@@ -196,12 +239,6 @@ public class RemoteViews implements Parcelable, Filter {
     */
    private boolean mIsRoot = true;

    /**
     * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify
     * the layout in a way that isn't recoverable, since views are being removed.
     */
    private boolean mReapplyDisallowed;

    /**
     * Constants to whether or not this RemoteViews is composed of a landscape and portrait
     * RemoteViews.
@@ -218,14 +255,8 @@ public class RemoteViews implements Parcelable, Filter {
    @UnsupportedAppUsage
    private RemoteViews mPortrait = null;

    /**
     * This flag indicates whether this RemoteViews object is being created from a
     * RemoteViewsService for use as a child of a widget collection. This flag is used
     * to determine whether or not certain features are available, in particular,
     * setting on click extras and setting on click pending intents. The former is enabled,
     * and the latter disabled when this flag is true.
     */
    private boolean mIsWidgetCollectionChild = false;
    @ApplyFlags
    private int mApplyFlags = 0;

    /** Class cookies of the Parcel this instance was read from. */
    private final Map<Class, Object> mClassCookies;
@@ -289,18 +320,15 @@ public class RemoteViews implements Parcelable, Filter {
     *
     * @hide
     */
    public void setReapplyDisallowed() {
        mReapplyDisallowed = true;
    public void addFlags(@ApplyFlags int flags) {
        mApplyFlags = mApplyFlags | flags;
    }

    /**
     * @return Whether it is disallowed to reapply another remoteview with the same layout as this
     * view. True if this remoteview has actions that destroyed view tree of the base layout.
     *
     * @hide
     */
    public boolean isReapplyDisallowed() {
        return mReapplyDisallowed;
    public boolean hasFlags(@ApplyFlags int flag) {
        return (mApplyFlags & flag) == flag;
    }

    /**
@@ -768,7 +796,10 @@ public class RemoteViews implements Parcelable, Filter {
            // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
            // RemoteViewsService
            AppWidgetHostView host = (AppWidgetHostView) rootParent;
            intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
            intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId())
                    .putExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND,
                            hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT));

            if (target instanceof AbsListView) {
                AbsListView v = (AbsListView) target;
                v.setRemoteViewsAdapter(intent, isAsync);
@@ -829,7 +860,7 @@ public class RemoteViews implements Parcelable, Filter {
                // If the view is an AdapterView, setting a PendingIntent on click doesn't make
                // much sense, do they mean to set a PendingIntent template for the
                // AdapterView's children?
                if (mIsWidgetCollectionChild) {
                if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
                    Log.w(LOG_TAG, "Cannot SetOnClickResponse for collection item "
                            + "(id: " + viewId + ")");
                    ApplicationInfo appInfo = root.getContext().getApplicationInfo();
@@ -843,7 +874,7 @@ public class RemoteViews implements Parcelable, Filter {
                }
                target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent);
            } else if (mResponse.mFillIntent != null) {
                if (!mIsWidgetCollectionChild) {
                if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
                    Log.e(LOG_TAG, "The method setOnClickFillInIntent is available "
                            + "only from RemoteViewsFactory (ie. on collection items).");
                    return;
@@ -1545,6 +1576,7 @@ public class RemoteViews implements Parcelable, Filter {
            viewId = parcel.readInt();
            mIndex = parcel.readInt();
            mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
            mNestedViews.addFlags(mApplyFlags);
        }

        public void writeToParcel(Parcel dest, int flags) {
@@ -2190,7 +2222,7 @@ public class RemoteViews implements Parcelable, Filter {
     *
     * @hide
     */
    public RemoteViews(String packageName, int userId, int layoutId) {
    public RemoteViews(String packageName, int userId, @LayoutRes int layoutId) {
        this(getApplicationInfo(packageName, userId), layoutId);
    }

@@ -2203,7 +2235,7 @@ public class RemoteViews implements Parcelable, Filter {
     *
     * @hide
     */
    protected RemoteViews(ApplicationInfo application, int layoutId) {
    protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) {
        mApplication = application;
        mLayoutId = layoutId;
        mBitmapCache = new BitmapCache();
@@ -2229,7 +2261,8 @@ public class RemoteViews implements Parcelable, Filter {
            throw new RuntimeException("Both RemoteViews must share the same package and user");
        }
        mApplication = portrait.mApplication;
        mLayoutId = portrait.getLayoutId();
        mLayoutId = portrait.mLayoutId;
        mLightBackgroundLayoutId = portrait.mLightBackgroundLayoutId;

        mLandscape = landscape;
        mPortrait = portrait;
@@ -2250,8 +2283,8 @@ public class RemoteViews implements Parcelable, Filter {
        mApplication = src.mApplication;
        mIsRoot = src.mIsRoot;
        mLayoutId = src.mLayoutId;
        mIsWidgetCollectionChild = src.mIsWidgetCollectionChild;
        mReapplyDisallowed = src.mReapplyDisallowed;
        mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
        mApplyFlags = src.mApplyFlags;
        mClassCookies = src.mClassCookies;

        if (src.hasLandscapeAndPortraitLayouts()) {
@@ -2309,7 +2342,7 @@ public class RemoteViews implements Parcelable, Filter {
            mApplication = parcel.readInt() == 0 ? info :
                    ApplicationInfo.CREATOR.createFromParcel(parcel);
            mLayoutId = parcel.readInt();
            mIsWidgetCollectionChild = parcel.readInt() == 1;
            mLightBackgroundLayoutId = parcel.readInt();

            readActionsFromParcel(parcel, depth);
        } else {
@@ -2318,9 +2351,10 @@ public class RemoteViews implements Parcelable, Filter {
            mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
                    mClassCookies);
            mApplication = mPortrait.mApplication;
            mLayoutId = mPortrait.getLayoutId();
            mLayoutId = mPortrait.mLayoutId;
            mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId;
        }
        mReapplyDisallowed = parcel.readInt() == 0;
        mApplyFlags = parcel.readInt();
    }

    private void readActionsFromParcel(Parcel parcel, int depth) {
@@ -2409,19 +2443,8 @@ public class RemoteViews implements Parcelable, Filter {
     * @return the layout id.
     */
    public int getLayoutId() {
        return mLayoutId;
    }

    /*
     * This flag indicates whether this RemoteViews object is being created from a
     * RemoteViewsService for use as a child of a widget collection. This flag is used
     * to determine whether or not certain features are available, in particular,
     * setting on click extras and setting on click pending intents. The former is enabled,
     * and the latter disabled when this flag is true.
     */
    @UnsupportedAppUsage
    void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
        mIsWidgetCollectionChild = isWidgetCollectionChild;
        return hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT) && (mLightBackgroundLayoutId != 0)
                ? mLightBackgroundLayoutId : mLayoutId;
    }

    /**
@@ -3292,6 +3315,33 @@ public class RemoteViews implements Parcelable, Filter {
        setInt(viewId, "setLabelFor", labeledId);
    }

    /**
     * Provides an alternate layout ID, which can be used to inflate this view. This layout will be
     * used by the host when the widgets displayed on a light-background where foreground elements
     * and text can safely draw using a dark color without any additional background protection.
     */
    public void setLightBackgroundLayoutId(@LayoutRes int layoutId) {
        mLightBackgroundLayoutId = layoutId;
    }

    /**
     * If this view supports dark text versions, creates a copy representing that version,
     * otherwise returns itself.
     * @hide
     */
    public RemoteViews getDarkTextViews() {
        if (hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)) {
            return this;
        }

        try {
            addFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
            return new RemoteViews(this);
        } finally {
            mApplyFlags &= ~FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
        }
    }

    private RemoteViews getRemoteViewsToApply(Context context) {
        if (hasLandscapeAndPortraitLayouts()) {
            int orientation = context.getResources().getConfiguration().orientation;
@@ -3652,7 +3702,7 @@ public class RemoteViews implements Parcelable, Filter {
                mApplication.writeToParcel(dest, flags);
            }
            dest.writeInt(mLayoutId);
            dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
            dest.writeInt(mLightBackgroundLayoutId);
            writeActionsToParcel(dest);
        } else {
            dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
@@ -3665,7 +3715,7 @@ public class RemoteViews implements Parcelable, Filter {
            // Both RemoteViews already share the same package and user
            mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
        }
        dest.writeInt(mReapplyDisallowed ? 1 : 0);
        dest.writeInt(mApplyFlags);
    }

    private void writeActionsToParcel(Parcel parcel) {
+9 −4
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package android.widget;

import static android.widget.RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID;
import static android.widget.RemoteViews.EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND;

import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.app.IServiceConnection;
@@ -97,6 +100,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
    private final Context mContext;
    private final Intent mIntent;
    private final int mAppWidgetId;
    private final boolean mOnLightBackground;
    private final Executor mAsyncViewLoadExecutor;

    private OnClickHandler mRemoteViewsOnClickHandler;
@@ -817,13 +821,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
            throw new IllegalArgumentException("Non-null Intent must be specified.");
        }

        mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
        mAppWidgetId = intent.getIntExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
        mRequestedViews = new RemoteViewsFrameLayoutRefSet();
        mOnLightBackground = intent.getBooleanExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, false);

        // Strip the previously injected app widget id from service intent
        if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
            intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
        }
        intent.removeExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID);
        intent.removeExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND);

        // Initialize the worker thread
        mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
@@ -1107,6 +1111,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
            } else {
                layout = new RemoteViewsFrameLayout(parent.getContext(), mCache);
                layout.setExecutor(mAsyncViewLoadExecutor);
                layout.setOnLightBackground(mOnLightBackground);
            }

            if (isInCache) {
Loading