/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.annotation.ColorInt; import android.annotation.DimenRes; import android.annotation.DrawableRes; import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.annotation.StyleRes; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.graphics.drawable.RippleDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.StrictMode; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Pair; import android.util.TypedValue; import android.util.TypedValue.ComplexDimensionUnit; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewManager; import android.view.ViewParent; import android.view.ViewStub; import android.widget.AdapterView.OnItemClickListener; import com.android.internal.R; import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.Preconditions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Stack; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * A class that describes a view hierarchy that can be displayed in * another process. The hierarchy is inflated from a layout resource * file, and this class provides some basic operations for modifying * the content of the inflated hierarchy. * *
{@code RemoteViews} is limited to support for the following layouts:
*And the following widgets:
*Descendants of these classes are not supported.
*/ public class RemoteViews implements Parcelable, Filter { private static final String LOG_TAG = "RemoteViews"; /** * The intent extra that contains the appWidgetId. * @hide */ 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. */ public static final String EXTRA_SHARED_ELEMENT_BOUNDS = "android.widget.extra.SHARED_ELEMENT_BOUNDS"; /** * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and * {@link #RemoteViews(RemoteViews, RemoteViews)}. */ private static final int MAX_NESTED_VIEWS = 10; // The unique identifiers for each custom {@link Action}. private static final int SET_ON_CLICK_RESPONSE_TAG = 1; private static final int REFLECTION_ACTION_TAG = 2; private static final int SET_DRAWABLE_TINT_TAG = 3; private static final int VIEW_GROUP_ACTION_ADD_TAG = 4; private static final int VIEW_CONTENT_NAVIGATION_TAG = 5; private static final int SET_EMPTY_VIEW_ACTION_TAG = 6; private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7; private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8; private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10; private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11; private static final int BITMAP_REFLECTION_ACTION_TAG = 12; private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13; private static final int VIEW_PADDING_ACTION_TAG = 14; private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15; private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18; private static final int LAYOUT_PARAM_ACTION_TAG = 19; private static final int OVERRIDE_TEXT_COLORS_TAG = 20; private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21; private static final int SET_INT_TAG_TAG = 22; private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23; /** @hide **/ @IntDef(prefix = "MARGIN_", value = { MARGIN_LEFT, MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM, MARGIN_START, MARGIN_END }) @Retention(RetentionPolicy.SOURCE) public @interface MarginType {} /** * The value will apply to the marginLeft. * @hide */ public static final int MARGIN_LEFT = 0; /** * The value will apply to the marginTop. * @hide */ public static final int MARGIN_TOP = 1; /** * The value will apply to the marginRight. * @hide */ public static final int MARGIN_RIGHT = 2; /** * The value will apply to the marginBottom. * @hide */ public static final int MARGIN_BOTTOM = 3; /** * The value will apply to the marginStart. * @hide */ public static final int MARGIN_START = 4; /** * The value will apply to the marginEnd. * @hide */ public static final int MARGIN_END = 5; /** @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; /** * Used to restrict the views which can be inflated * * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) */ private static final LayoutInflater.Filter INFLATER_FILTER = (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); /** * Application that hosts the remote views. * * @hide */ @UnsupportedAppUsage public ApplicationInfo mApplication; /** * The resource ID of the layout file. (Added to the parcel) */ @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 */ @UnsupportedAppUsage private ArrayList* The operation will be performed on the {@link Drawable} returned by the * target {@link View#getBackground()} by default. If targetBackground is false, * we assume the target is an {@link ImageView} and try applying the operations * to {@link ImageView#getDrawable()}. *
*/ private class SetDrawableTint extends Action { SetDrawableTint(@IdRes int id, boolean targetBackground, @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { this.viewId = id; this.targetBackground = targetBackground; this.colorFilter = colorFilter; this.filterMode = mode; } SetDrawableTint(Parcel parcel) { viewId = parcel.readInt(); targetBackground = parcel.readInt() != 0; colorFilter = parcel.readInt(); filterMode = PorterDuff.intToMode(parcel.readInt()); } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); dest.writeInt(targetBackground ? 1 : 0); dest.writeInt(colorFilter); dest.writeInt(PorterDuff.modeToInt(filterMode)); } @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final View target = root.findViewById(viewId); if (target == null) return; // Pick the correct drawable to modify for this view Drawable targetDrawable = null; if (targetBackground) { targetDrawable = target.getBackground(); } else if (target instanceof ImageView) { ImageView imageView = (ImageView) target; targetDrawable = imageView.getDrawable(); } if (targetDrawable != null) { targetDrawable.mutate().setColorFilter(colorFilter, filterMode); } } @Override public int getActionTag() { return SET_DRAWABLE_TINT_TAG; } boolean targetBackground; @ColorInt int colorFilter; PorterDuff.Mode filterMode; } /** * Equivalent to calling * {@link RippleDrawable#setColor(ColorStateList)}, * on the {@link Drawable} of a given view. *
* The operation will be performed on the {@link Drawable} returned by the * target {@link View#getBackground()}. *
*/
private class SetRippleDrawableColor extends Action {
ColorStateList mColorStateList;
SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) {
this.viewId = id;
this.mColorStateList = colorStateList;
}
SetRippleDrawableColor(Parcel parcel) {
viewId = parcel.readInt();
mColorStateList = parcel.readParcelable(null);
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
dest.writeParcelable(mColorStateList, 0);
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View target = root.findViewById(viewId);
if (target == null) return;
// Pick the correct drawable to modify for this view
Drawable targetDrawable = target.getBackground();
if (targetDrawable instanceof RippleDrawable) {
((RippleDrawable) targetDrawable.mutate()).setColor(mColorStateList);
}
}
@Override
public int getActionTag() {
return SET_RIPPLE_DRAWABLE_COLOR_TAG;
}
}
private final class ViewContentNavigation extends Action {
final boolean mNext;
ViewContentNavigation(@IdRes int viewId, boolean next) {
this.viewId = viewId;
this.mNext = next;
}
ViewContentNavigation(Parcel in) {
this.viewId = in.readInt();
this.mNext = in.readBoolean();
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(this.viewId);
out.writeBoolean(this.mNext);
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View view = root.findViewById(viewId);
if (view == null) return;
try {
getMethod(view,
mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view);
} catch (Throwable ex) {
throw new ActionException(ex);
}
}
public int mergeBehavior() {
return MERGE_IGNORE;
}
@Override
public int getActionTag() {
return VIEW_CONTENT_NAVIGATION_TAG;
}
}
private static class BitmapCache {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
ArrayList Using -2 because the default id is -1. This avoids accidentally matching that.
*/
private static final int REMOVE_ALL_VIEWS_ID = -2;
private int mViewIdToKeep;
ViewGroupActionRemove(@IdRes int viewId) {
this(viewId, REMOVE_ALL_VIEWS_ID);
}
ViewGroupActionRemove(@IdRes int viewId, @IdRes int viewIdToKeep) {
this.viewId = viewId;
mViewIdToKeep = viewIdToKeep;
}
ViewGroupActionRemove(Parcel parcel) {
viewId = parcel.readInt();
mViewIdToKeep = parcel.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
dest.writeInt(mViewIdToKeep);
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final ViewGroup target = root.findViewById(viewId);
if (target == null) {
return;
}
if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
target.removeAllViews();
return;
}
removeAllViewsExceptIdToKeep(target);
}
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
// In the async implementation, update the view tree so that subsequent calls to
// findViewById return the current view.
root.createTree();
ViewTree target = root.findViewTreeById(viewId);
if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
return ACTION_NOOP;
}
final ViewGroup targetVg = (ViewGroup) target.mRoot;
if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
// Clear all children when there's no excepted view
target.mChildren = null;
} else {
// Remove just the children which don't match the excepted view
target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep);
if (target.mChildren.isEmpty()) {
target.mChildren = null;
}
}
return new RuntimeAction() {
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
throws ActionException {
if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
targetVg.removeAllViews();
return;
}
removeAllViewsExceptIdToKeep(targetVg);
}
};
}
/**
* Iterates through the children in the given ViewGroup and removes all the views that
* do not have an id of {@link #mViewIdToKeep}.
*/
private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) {
// Otherwise, remove all the views that do not match the id to keep.
int index = viewGroup.getChildCount() - 1;
while (index >= 0) {
if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) {
viewGroup.removeViewAt(index);
}
index--;
}
}
@Override
public int getActionTag() {
return VIEW_GROUP_ACTION_REMOVE_TAG;
}
@Override
public int mergeBehavior() {
return MERGE_APPEND;
}
}
/**
* Action to remove a view from its parent.
*/
private class RemoveFromParentAction extends Action {
RemoveFromParentAction(@IdRes int viewId) {
this.viewId = viewId;
}
RemoveFromParentAction(Parcel parcel) {
viewId = parcel.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View target = root.findViewById(viewId);
if (target == null || target == root) {
return;
}
ViewParent parent = target.getParent();
if (parent instanceof ViewManager) {
((ViewManager) parent).removeView(target);
}
}
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
// In the async implementation, update the view tree so that subsequent calls to
// findViewById return the correct view.
root.createTree();
ViewTree target = root.findViewTreeById(viewId);
if (target == null || target == root) {
return ACTION_NOOP;
}
ViewTree parent = root.findViewTreeParentOf(target);
if (parent == null || !(parent.mRoot instanceof ViewManager)) {
return ACTION_NOOP;
}
final ViewManager parentVg = (ViewManager) parent.mRoot;
parent.mChildren.remove(target);
return new RuntimeAction() {
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
throws ActionException {
parentVg.removeView(target.mRoot);
}
};
}
@Override
public int getActionTag() {
return REMOVE_FROM_PARENT_ACTION_TAG;
}
@Override
public int mergeBehavior() {
return MERGE_APPEND;
}
}
/**
* Helper action to set compound drawables on a TextView. Supports relative
* (s/t/e/b) or cardinal (l/t/r/b) arrangement.
*/
private class TextViewDrawableAction extends Action {
public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1,
@DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) {
this.viewId = viewId;
this.isRelative = isRelative;
this.useIcons = false;
this.d1 = d1;
this.d2 = d2;
this.d3 = d3;
this.d4 = d4;
}
public TextViewDrawableAction(@IdRes int viewId, boolean isRelative,
Icon i1, Icon i2, Icon i3, Icon i4) {
this.viewId = viewId;
this.isRelative = isRelative;
this.useIcons = true;
this.i1 = i1;
this.i2 = i2;
this.i3 = i3;
this.i4 = i4;
}
public TextViewDrawableAction(Parcel parcel) {
viewId = parcel.readInt();
isRelative = (parcel.readInt() != 0);
useIcons = (parcel.readInt() != 0);
if (useIcons) {
i1 = parcel.readTypedObject(Icon.CREATOR);
i2 = parcel.readTypedObject(Icon.CREATOR);
i3 = parcel.readTypedObject(Icon.CREATOR);
i4 = parcel.readTypedObject(Icon.CREATOR);
} else {
d1 = parcel.readInt();
d2 = parcel.readInt();
d3 = parcel.readInt();
d4 = parcel.readInt();
}
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
dest.writeInt(isRelative ? 1 : 0);
dest.writeInt(useIcons ? 1 : 0);
if (useIcons) {
dest.writeTypedObject(i1, 0);
dest.writeTypedObject(i2, 0);
dest.writeTypedObject(i3, 0);
dest.writeTypedObject(i4, 0);
} else {
dest.writeInt(d1);
dest.writeInt(d2);
dest.writeInt(d3);
dest.writeInt(d4);
}
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final TextView target = root.findViewById(viewId);
if (target == null) return;
if (drawablesLoaded) {
if (isRelative) {
target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
} else {
target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
}
} else if (useIcons) {
final Context ctx = target.getContext();
final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx);
final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx);
if (isRelative) {
target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
} else {
target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
}
} else {
if (isRelative) {
target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
} else {
target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
}
}
}
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
final TextView target = root.findViewById(viewId);
if (target == null) return ACTION_NOOP;
TextViewDrawableAction copy = useIcons ?
new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
// Load the drawables on the background thread.
copy.drawablesLoaded = true;
final Context ctx = target.getContext();
if (useIcons) {
copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
} else {
copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
}
return copy;
}
@Override
public boolean prefersAsyncApply() {
return useIcons;
}
@Override
public int getActionTag() {
return TEXT_VIEW_DRAWABLE_ACTION_TAG;
}
@Override
public void visitUris(@NonNull Consumer
*
* @param viewId The id of the view that contains the target
* {@link Drawable}
* @param targetBackground If true, apply these parameters to the
* {@link Drawable} returned by
* {@link android.view.View#getBackground()}. Otherwise, assume
* the target view is an {@link ImageView} and apply them to
* {@link ImageView#getDrawable()}.
* @param colorFilter Specify a color for a
* {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
* {@code mode} is {@code null}.
* @param mode Specify a PorterDuff mode for this drawable, or null to leave
* unchanged.
*/
public void setDrawableTint(@IdRes int viewId, boolean targetBackground,
@ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) {
addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
}
/**
* @hide
* Equivalent to calling
* {@link RippleDrawable#setColor(ColorStateList)} on the {@link Drawable} of a given view,
* assuming it's a {@link RippleDrawable}.
*
*
* @param viewId The id of the view that contains the target
* {@link RippleDrawable}
* @param colorStateList Specify a color for a
* {@link ColorStateList} for this drawable.
*/
public void setRippleDrawableColor(@IdRes int viewId, ColorStateList colorStateList) {
addAction(new SetRippleDrawableColor(viewId, colorStateList));
}
/**
* @hide
* Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}.
*
* @param viewId The id of the view whose tint should change
* @param tint the tint to apply, may be {@code null} to clear tint
*/
public void setProgressTintList(@IdRes int viewId, ColorStateList tint) {
addAction(new ReflectionAction(viewId, "setProgressTintList",
ReflectionAction.COLOR_STATE_LIST, tint));
}
/**
* @hide
* Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}.
*
* @param viewId The id of the view whose tint should change
* @param tint the tint to apply, may be {@code null} to clear tint
*/
public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) {
addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
ReflectionAction.COLOR_STATE_LIST, tint));
}
/**
* @hide
* Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}.
*
* @param viewId The id of the view whose tint should change
* @param tint the tint to apply, may be {@code null} to clear tint
*/
public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) {
addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
ReflectionAction.COLOR_STATE_LIST, tint));
}
/**
* Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
*
* @param viewId The id of the view whose text color should change
* @param color Sets the text color for all the states (normal, selected,
* focused) to be this color.
*/
public void setTextColor(@IdRes int viewId, @ColorInt int color) {
setInt(viewId, "setTextColor", color);
}
/**
* @hide
* Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}.
*
* @param viewId The id of the view whose text color should change
* @param colors the text colors to set
*/
public void setTextColor(@IdRes int viewId, ColorStateList colors) {
addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST,
colors));
}
/**
* Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
*
* @param appWidgetId The id of the app widget which contains the specified view. (This
* parameter is ignored in this deprecated method)
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
* @deprecated This method has been deprecated. See
* {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
*/
@Deprecated
public void setRemoteAdapter(int appWidgetId, @IdRes int viewId, Intent intent) {
setRemoteAdapter(viewId, intent);
}
/**
* Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
* Can only be used for App Widgets.
*
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
*/
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
/**
* Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
* ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
* This is a simpler but less flexible approach to populating collection widgets. Its use is
* encouraged for most scenarios, as long as the total memory within the list of RemoteViews
* is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
* RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
* possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
*
* This API is supported in the compatibility library for previous API levels, see
* RemoteViewsCompat.
*
* @param viewId The id of the {@link AdapterView}
* @param list The list of RemoteViews which will populate the view specified by viewId.
* @param viewTypeCount The maximum number of unique layout id's used to construct the list of
* RemoteViews. This count cannot change during the life-cycle of a given widget, so this
* parameter should account for the maximum possible number of types that may appear in the
* See {@link Adapter#getViewTypeCount()}.
*
* @hide
* @deprecated this appears to have no users outside of UnsupportedAppUsage?
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Deprecated
public void setRemoteAdapter(@IdRes int viewId, ArrayList NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
* Setting margins in pixels will behave poorly when the RemoteViews object is used on a
* display with a different density.
*
* @param viewId The id of the view to change
* @param type The margin being set e.g. {@link #MARGIN_END}
* @param value a value for the margin the given units.
* @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
* @hide
*/
public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value,
@ComplexDimensionUnit int units) {
addAction(new LayoutParamAction(viewId, type, value, units));
}
/**
* Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may
* provide the value in any dimension units.
*
* NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0,
* {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}.
* Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a
* display with a different density.
*
* @param width Width of the view in the given units
* @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
* @hide
*/
public void setViewLayoutWidth(@IdRes int viewId, float width,
@ComplexDimensionUnit int units) {
addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units));
}
/**
* Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with
* the result of {@link Resources#getDimensionPixelSize(int)}.
*
* @param widthDimen the dimension resource for the view's width
* @hide
*/
public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) {
addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen));
}
/**
* Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may
* provide the value in any dimension units.
*
* NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0,
* {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}.
* Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a
* display with a different density.
*
* @param height height of the view in the given units
* @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
* @hide
*/
public void setViewLayoutHeight(@IdRes int viewId, float height,
@ComplexDimensionUnit int units) {
addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units));
}
/**
* Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with
* the result of {@link Resources#getDimensionPixelSize(int)}.
*
* @param heightDimen a dimen resource to read the height from.
* @hide
*/
public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) {
addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen));
}
/**
* Call a method taking one boolean on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setBoolean(@IdRes int viewId, String methodName, boolean value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
}
/**
* Call a method taking one byte on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setByte(@IdRes int viewId, String methodName, byte value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
}
/**
* Call a method taking one short on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setShort(@IdRes int viewId, String methodName, short value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
}
/**
* Call a method taking one int on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setInt(@IdRes int viewId, String methodName, int value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
}
/**
* Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*
* @hide
*/
public void setColorStateList(@IdRes int viewId, String methodName, ColorStateList value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST,
value));
}
/**
* Call a method taking one long on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setLong(@IdRes int viewId, String methodName, long value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
}
/**
* Call a method taking one float on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setFloat(@IdRes int viewId, String methodName, float value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
}
/**
* Call a method taking one double on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setDouble(@IdRes int viewId, String methodName, double value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
}
/**
* Call a method taking one char on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setChar(@IdRes int viewId, String methodName, char value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
}
/**
* Call a method taking one String on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setString(@IdRes int viewId, String methodName, String value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
}
/**
* Call a method taking one CharSequence on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
/**
* Call a method taking one Uri on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setUri(@IdRes int viewId, String methodName, Uri value) {
if (value != null) {
// Resolve any filesystem path before sending remotely
value = value.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
value.checkFileUriExposed("RemoteViews.setUri()");
}
}
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
}
/**
* Call a method taking one Bitmap on a view in the layout for this RemoteViews.
* @more
* The bitmap will be flattened into the parcel if this object is
* sent across processes, so it may end up using a lot of memory, and may be fairly slow. Caller beware: this may throw
*
* @param context Default context to use
* @param parent Parent that the resulting view hierarchy will be attached to. This method
* does not attach the hierarchy. The caller should do so when appropriate.
* @return The inflated view hierarchy
*/
public View apply(Context context, ViewGroup parent) {
return apply(context, parent, null);
}
/** @hide */
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent);
rvToApply.performApply(result, parent, handler);
return result;
}
/** @hide */
public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler,
@StyleRes int applyThemeResId) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent, applyThemeResId);
rvToApply.performApply(result, parent, handler);
return result;
}
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
return inflateView(context, rv, parent, 0);
}
private View inflateView(Context context, RemoteViews rv, ViewGroup parent,
@StyleRes int applyThemeResId) {
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
// are loaded without requiring cross user persmissions.
final Context contextForResources = getContextForResources(context);
Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);
// If mApplyThemeResId is not given, Theme.DeviceDefault will be used.
if (applyThemeResId != 0) {
inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId);
}
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Clone inflater so we load resources from correct context and
// we don't add a filter to the static version returned by getSystemService.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this);
View v = inflater.inflate(rv.getLayoutId(), parent, false);
v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
return v;
}
/**
* A static filter is much lighter than RemoteViews itself. It's optimized here only for
* RemoteVies class. Subclasses should always override this and return true if not overriding
* {@link this#onLoadClass(Class)}.
*
* @hide
*/
protected boolean shouldUseStaticFilter() {
return this.getClass().equals(RemoteViews.class);
}
/**
* Implement this interface to receive a callback when
* {@link #applyAsync} or {@link #reapplyAsync} is finished.
* @hide
*/
public interface OnViewAppliedListener {
/**
* Callback when the RemoteView has finished inflating,
* but no actions have been applied yet.
*/
default void onViewInflated(View v) {};
void onViewApplied(View v);
void onError(Exception e);
}
/**
* Applies the views asynchronously, moving as much of the task on the background
* thread as possible.
*
* @see #apply(Context, ViewGroup)
* @param context Default context to use
* @param parent Parent that the resulting view hierarchy will be attached to. This method
* does not attach the hierarchy. The caller should do so when appropriate.
* @param listener the callback to run when all actions have been applied. May be null.
* @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
* @return CancellationSignal
* @hide
*/
public CancellationSignal applyAsync(
Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
return applyAsync(context, parent, executor, listener, null);
}
/** @hide */
public CancellationSignal applyAsync(Context context, ViewGroup parent,
Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
}
private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
OnViewAppliedListener listener, OnClickHandler handler) {
return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
handler, null);
}
private class AsyncApplyTask extends AsyncTask Caller beware: this may throw
*
* @param v The view to apply the actions to. This should be the result of
* the {@link #apply(Context,ViewGroup)} call.
*/
public void reapply(Context context, View v) {
reapply(context, v, null);
}
/** @hide */
public void reapply(Context context, View v, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
// In the case that a view has this RemoteViews applied in one orientation, is persisted
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasLandscapeAndPortraitLayouts()) {
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
}
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
}
/**
* Applies all the actions to the provided view, moving as much of the task on the background
* thread as possible.
*
* @see #reapply(Context, View)
* @param context Default context to use
* @param v The view to apply the actions to. This should be the result of
* the {@link #apply(Context,ViewGroup)} call.
* @param listener the callback to run when all actions have been applied. May be null.
* @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
* @return CancellationSignal
* @hide
*/
public CancellationSignal reapplyAsync(
Context context, View v, Executor executor, OnViewAppliedListener listener) {
return reapplyAsync(context, v, executor, listener, null);
}
/** @hide */
public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
OnViewAppliedListener listener, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
// In the case that a view has this RemoteViews applied in one orientation, is persisted
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasLandscapeAndPortraitLayouts()) {
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
}
return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
context, listener, handler, v).startTaskOnExecutor(executor);
}
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
/**
* Returns true if the RemoteViews contains potentially costly operations and should be
* applied asynchronously.
*
* @hide
*/
public boolean prefersAsyncApply() {
if (mActions != null) {
final int count = mActions.size();
for (int i = 0; i < count; i++) {
if (mActions.get(i).prefersAsyncApply()) {
return true;
}
}
}
return false;
}
private Context getContextForResources(Context context) {
if (mApplication != null) {
if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
&& context.getPackageName().equals(mApplication.packageName)) {
return context;
}
try {
return context.createApplicationContext(mApplication,
Context.CONTEXT_RESTRICTED);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found");
}
}
return context;
}
/**
* Returns the number of actions in this RemoteViews. Can be used as a sequence number.
*
* @hide
*/
public int getSequenceNumber() {
return (mActions == null) ? 0 : mActions.size();
}
/**
* Used to restrict the views which can be inflated
*
* @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
* @deprecated Used by system to enforce safe inflation of {@link RemoteViews}. Apps should not
* override this method. Changing of this method will NOT affect the process where RemoteViews
* is rendered.
*/
@Deprecated
public boolean onLoadClass(Class clazz) {
return clazz.isAnnotationPresent(RemoteView.class);
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
if (!hasLandscapeAndPortraitLayouts()) {
dest.writeInt(MODE_NORMAL);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
}
if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) {
dest.writeInt(0);
} else {
dest.writeInt(1);
mApplication.writeToParcel(dest, flags);
}
dest.writeInt(mLayoutId);
dest.writeInt(mLightBackgroundLayoutId);
writeActionsToParcel(dest);
} else {
dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
}
mLandscape.writeToParcel(dest, flags);
// Both RemoteViews already share the same package and user
mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
}
dest.writeInt(mApplyFlags);
}
private void writeActionsToParcel(Parcel parcel) {
int count;
if (mActions != null) {
count = mActions.size();
} else {
count = 0;
}
parcel.writeInt(count);
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
parcel.writeInt(a.getActionTag());
a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
? PARCELABLE_ELIDE_DUPLICATES : 0);
}
}
private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
if (packageName == null) {
return null;
}
// Get the application for the passed in package and user.
Application application = ActivityThread.currentApplication();
if (application == null) {
throw new IllegalStateException("Cannot create remote views out of an aplication.");
}
ApplicationInfo applicationInfo = application.getApplicationInfo();
if (UserHandle.getUserId(applicationInfo.uid) != userId
|| !applicationInfo.packageName.equals(packageName)) {
try {
Context context = application.getBaseContext().createPackageContextAsUser(
packageName, 0, new UserHandle(userId));
applicationInfo = context.getApplicationInfo();
} catch (NameNotFoundException nnfe) {
throw new IllegalArgumentException("No such package " + packageName);
}
}
return applicationInfo;
}
/**
* Returns true if the {@link #mApplication} is same as the provided info.
*
* @hide
*/
public boolean hasSameAppInfo(ApplicationInfo info) {
return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
}
/**
* Parcelable.Creator that instantiates RemoteViews objects
*/
public static final @android.annotation.NonNull Parcelable.Creator