|  | /* | 
|  | * 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 static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; | 
|  |  | 
|  | import android.annotation.ColorInt; | 
|  | import android.annotation.DimenRes; | 
|  | import android.annotation.NonNull; | 
|  | 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.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.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.Log; | 
|  | import android.view.LayoutInflater; | 
|  | import android.view.LayoutInflater.Filter; | 
|  | import android.view.RemotableViewMethod; | 
|  | import android.view.View; | 
|  | import android.view.View.OnClickListener; | 
|  | import android.view.ViewGroup; | 
|  | import android.view.ViewStub; | 
|  | import android.widget.AdapterView.OnItemClickListener; | 
|  |  | 
|  | import com.android.internal.R; | 
|  | import com.android.internal.util.NotificationColorUtil; | 
|  | 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; | 
|  |  | 
|  | /** | 
|  | * 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. | 
|  | * | 
|  | * <p>{@code RemoteViews} is limited to support for the following layouts:</p> | 
|  | * <ul> | 
|  | *   <li>{@link android.widget.AdapterViewFlipper}</li> | 
|  | *   <li>{@link android.widget.FrameLayout}</li> | 
|  | *   <li>{@link android.widget.GridLayout}</li> | 
|  | *   <li>{@link android.widget.GridView}</li> | 
|  | *   <li>{@link android.widget.LinearLayout}</li> | 
|  | *   <li>{@link android.widget.ListView}</li> | 
|  | *   <li>{@link android.widget.RelativeLayout}</li> | 
|  | *   <li>{@link android.widget.StackView}</li> | 
|  | *   <li>{@link android.widget.ViewFlipper}</li> | 
|  | * </ul> | 
|  | * <p>And the following widgets:</p> | 
|  | * <ul> | 
|  | *   <li>{@link android.widget.AnalogClock}</li> | 
|  | *   <li>{@link android.widget.Button}</li> | 
|  | *   <li>{@link android.widget.Chronometer}</li> | 
|  | *   <li>{@link android.widget.ImageButton}</li> | 
|  | *   <li>{@link android.widget.ImageView}</li> | 
|  | *   <li>{@link android.widget.ProgressBar}</li> | 
|  | *   <li>{@link android.widget.TextClock}</li> | 
|  | *   <li>{@link android.widget.TextView}</li> | 
|  | * </ul> | 
|  | * <p>Descendants of these classes are not supported.</p> | 
|  | */ | 
|  | 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"; | 
|  |  | 
|  | /** | 
|  | * 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_PENDING_INTENT_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_ON_CLICK_FILL_IN_INTENT_TAG = 9; | 
|  | 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; | 
|  |  | 
|  | /** | 
|  | * Application that hosts the remote views. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public ApplicationInfo mApplication; | 
|  |  | 
|  | /** | 
|  | * The resource ID of the layout file. (Added to the parcel) | 
|  | */ | 
|  | private final int mLayoutId; | 
|  |  | 
|  | /** | 
|  | * An array of actions to perform on the view tree once it has been | 
|  | * inflated | 
|  | */ | 
|  | private ArrayList<Action> mActions; | 
|  |  | 
|  | /** | 
|  | * Maps bitmaps to unique indicies to avoid Bitmap duplication. | 
|  | */ | 
|  | private BitmapCache mBitmapCache; | 
|  |  | 
|  | /** | 
|  | * Indicates whether or not this RemoteViews object is contained as a child of any other | 
|  | * RemoteViews. | 
|  | */ | 
|  | 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. | 
|  | */ | 
|  | private static final int MODE_NORMAL = 0; | 
|  | private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; | 
|  |  | 
|  | /** | 
|  | * Used in conjunction with the special constructor | 
|  | * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait | 
|  | * RemoteViews. | 
|  | */ | 
|  | private RemoteViews mLandscape = null; | 
|  | 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; | 
|  |  | 
|  | /** Class cookies of the Parcel this instance was read from. */ | 
|  | private final Map<Class, Object> mClassCookies; | 
|  |  | 
|  | private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); | 
|  |  | 
|  | private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); | 
|  |  | 
|  | /** | 
|  | * This key is used to perform lookups in sMethods without causing allocations. | 
|  | */ | 
|  | private static final MethodKey sLookupKey = new MethodKey(); | 
|  |  | 
|  | /** | 
|  | * @hide | 
|  | */ | 
|  | public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) { | 
|  | mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reduces all images and ensures that they are all below the given sizes. | 
|  | * | 
|  | * @param maxWidth the maximum width allowed | 
|  | * @param maxHeight the maximum height allowed | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void reduceImageSizes(int maxWidth, int maxHeight) { | 
|  | ArrayList<Bitmap> cache = mBitmapCache.mBitmaps; | 
|  | for (int i = 0; i < cache.size(); i++) { | 
|  | Bitmap bitmap = cache.get(i); | 
|  | cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Override all text colors in this layout and replace them by the given text color. | 
|  | * | 
|  | * @param textColor The color to use. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void overrideTextColors(int textColor) { | 
|  | addAction(new OverrideTextColorsAction(textColor)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set that it is disallowed to reapply another remoteview with the same layout as this view. | 
|  | * This should be done if an action is destroying the view tree of the base layout. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void setReapplyDisallowed() { | 
|  | mReapplyDisallowed = true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Stores information related to reflection method lookup. | 
|  | */ | 
|  | static class MethodKey { | 
|  | public Class targetClass; | 
|  | public Class paramClass; | 
|  | public String methodName; | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object o) { | 
|  | if (!(o instanceof MethodKey)) { | 
|  | return false; | 
|  | } | 
|  | MethodKey p = (MethodKey) o; | 
|  | return Objects.equals(p.targetClass, targetClass) | 
|  | && Objects.equals(p.paramClass, paramClass) | 
|  | && Objects.equals(p.methodName, methodName); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) | 
|  | ^ Objects.hashCode(methodName); | 
|  | } | 
|  |  | 
|  | public void set(Class targetClass, Class paramClass, String methodName) { | 
|  | this.targetClass = targetClass; | 
|  | this.paramClass = paramClass; | 
|  | this.methodName = methodName; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Stores information related to reflection method lookup result. | 
|  | */ | 
|  | static class MethodArgs { | 
|  | public MethodHandle syncMethod; | 
|  | public MethodHandle asyncMethod; | 
|  | public String asyncMethodName; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * This annotation indicates that a subclass of View is allowed to be used | 
|  | * with the {@link RemoteViews} mechanism. | 
|  | */ | 
|  | @Target({ ElementType.TYPE }) | 
|  | @Retention(RetentionPolicy.RUNTIME) | 
|  | public @interface RemoteView { | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Exception to send when something goes wrong executing an action | 
|  | * | 
|  | */ | 
|  | public static class ActionException extends RuntimeException { | 
|  | public ActionException(Exception ex) { | 
|  | super(ex); | 
|  | } | 
|  | public ActionException(String message) { | 
|  | super(message); | 
|  | } | 
|  | /** | 
|  | * @hide | 
|  | */ | 
|  | public ActionException(Throwable t) { | 
|  | super(t); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static class OnClickHandler { | 
|  |  | 
|  | private int mEnterAnimationId; | 
|  |  | 
|  | public boolean onClickHandler(View view, PendingIntent pendingIntent, | 
|  | Intent fillInIntent) { | 
|  | return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED); | 
|  | } | 
|  |  | 
|  | public boolean onClickHandler(View view, PendingIntent pendingIntent, | 
|  | Intent fillInIntent, int windowingMode) { | 
|  | try { | 
|  | // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? | 
|  | Context context = view.getContext(); | 
|  | ActivityOptions opts; | 
|  | if (mEnterAnimationId != 0) { | 
|  | opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0); | 
|  | } else { | 
|  | opts = ActivityOptions.makeBasic(); | 
|  | } | 
|  |  | 
|  | if (windowingMode != WINDOWING_MODE_UNDEFINED) { | 
|  | opts.setLaunchWindowingMode(windowingMode); | 
|  | } | 
|  | context.startIntentSender( | 
|  | pendingIntent.getIntentSender(), fillInIntent, | 
|  | Intent.FLAG_ACTIVITY_NEW_TASK, | 
|  | Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); | 
|  | } catch (IntentSender.SendIntentException e) { | 
|  | android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); | 
|  | return false; | 
|  | } catch (Exception e) { | 
|  | android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " + | 
|  | "unknown exception: ", e); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | public void setEnterAnimationId(int enterAnimationId) { | 
|  | mEnterAnimationId = enterAnimationId; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Base class for all actions that can be performed on an | 
|  | * inflated view. | 
|  | * | 
|  | *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! | 
|  | */ | 
|  | private abstract static class Action implements Parcelable { | 
|  | public abstract void apply(View root, ViewGroup rootParent, | 
|  | OnClickHandler handler) throws ActionException; | 
|  |  | 
|  | public static final int MERGE_REPLACE = 0; | 
|  | public static final int MERGE_APPEND = 1; | 
|  | public static final int MERGE_IGNORE = 2; | 
|  |  | 
|  | public int describeContents() { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | public void setBitmapCache(BitmapCache bitmapCache) { | 
|  | // Do nothing | 
|  | } | 
|  |  | 
|  | public int mergeBehavior() { | 
|  | return MERGE_REPLACE; | 
|  | } | 
|  |  | 
|  | public abstract int getActionTag(); | 
|  |  | 
|  | public String getUniqueKey() { | 
|  | return (getActionTag() + "_" + viewId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * This is called on the background thread. It should perform any non-ui computations | 
|  | * and return the final action which will run on the UI thread. | 
|  | * Override this if some of the tasks can be performed async. | 
|  | */ | 
|  | public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public boolean prefersAsyncApply() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Overridden by subclasses which have (or inherit) an ApplicationInfo instance | 
|  | * as member variable | 
|  | */ | 
|  | public boolean hasSameAppInfo(ApplicationInfo parentInfo) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int viewId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Action class used during async inflation of RemoteViews. Subclasses are not parcelable. | 
|  | */ | 
|  | private static abstract class RuntimeAction extends Action { | 
|  | @Override | 
|  | public final int getActionTag() { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final void writeToParcel(Parcel dest, int flags) { | 
|  | throw new UnsupportedOperationException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Constant used during async execution. It is not parcelable. | 
|  | private static final Action ACTION_NOOP = new RuntimeAction() { | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Merges the passed RemoteViews actions with this RemoteViews actions according to | 
|  | * action-specific merge rules. | 
|  | * | 
|  | * @param newRv | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void mergeRemoteViews(RemoteViews newRv) { | 
|  | if (newRv == null) return; | 
|  | // We first copy the new RemoteViews, as the process of merging modifies the way the actions | 
|  | // reference the bitmap cache. We don't want to modify the object as it may need to | 
|  | // be merged and applied multiple times. | 
|  | RemoteViews copy = new RemoteViews(newRv); | 
|  |  | 
|  | HashMap<String, Action> map = new HashMap<String, Action>(); | 
|  | if (mActions == null) { | 
|  | mActions = new ArrayList<Action>(); | 
|  | } | 
|  |  | 
|  | int count = mActions.size(); | 
|  | for (int i = 0; i < count; i++) { | 
|  | Action a = mActions.get(i); | 
|  | map.put(a.getUniqueKey(), a); | 
|  | } | 
|  |  | 
|  | ArrayList<Action> newActions = copy.mActions; | 
|  | if (newActions == null) return; | 
|  | count = newActions.size(); | 
|  | for (int i = 0; i < count; i++) { | 
|  | Action a = newActions.get(i); | 
|  | String key = newActions.get(i).getUniqueKey(); | 
|  | int mergeBehavior = newActions.get(i).mergeBehavior(); | 
|  | if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { | 
|  | mActions.remove(map.get(key)); | 
|  | map.remove(key); | 
|  | } | 
|  |  | 
|  | // If the merge behavior is ignore, we don't bother keeping the extra action | 
|  | if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { | 
|  | mActions.add(a); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache | 
|  | mBitmapCache = new BitmapCache(); | 
|  | setBitmapCache(mBitmapCache); | 
|  | } | 
|  |  | 
|  | private static class RemoteViewsContextWrapper extends ContextWrapper { | 
|  | private final Context mContextForResources; | 
|  |  | 
|  | RemoteViewsContextWrapper(Context context, Context contextForResources) { | 
|  | super(context); | 
|  | mContextForResources = contextForResources; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Resources getResources() { | 
|  | return mContextForResources.getResources(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Resources.Theme getTheme() { | 
|  | return mContextForResources.getTheme(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getPackageName() { | 
|  | return mContextForResources.getPackageName(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private class SetEmptyView extends Action { | 
|  | int emptyViewId; | 
|  |  | 
|  | SetEmptyView(int viewId, int emptyViewId) { | 
|  | this.viewId = viewId; | 
|  | this.emptyViewId = emptyViewId; | 
|  | } | 
|  |  | 
|  | SetEmptyView(Parcel in) { | 
|  | this.viewId = in.readInt(); | 
|  | this.emptyViewId = in.readInt(); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel out, int flags) { | 
|  | out.writeInt(this.viewId); | 
|  | out.writeInt(this.emptyViewId); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View view = root.findViewById(viewId); | 
|  | if (!(view instanceof AdapterView<?>)) return; | 
|  |  | 
|  | AdapterView<?> adapterView = (AdapterView<?>) view; | 
|  |  | 
|  | final View emptyView = root.findViewById(emptyViewId); | 
|  | if (emptyView == null) return; | 
|  |  | 
|  | adapterView.setEmptyView(emptyView); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_EMPTY_VIEW_ACTION_TAG; | 
|  | } | 
|  | } | 
|  |  | 
|  | private class SetOnClickFillInIntent extends Action { | 
|  | public SetOnClickFillInIntent(int id, Intent fillInIntent) { | 
|  | this.viewId = id; | 
|  | this.fillInIntent = fillInIntent; | 
|  | } | 
|  |  | 
|  | public SetOnClickFillInIntent(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | fillInIntent = parcel.readTypedObject(Intent.CREATOR); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeTypedObject(fillInIntent, 0 /* no flags */); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  |  | 
|  | if (!mIsWidgetCollectionChild) { | 
|  | Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " + | 
|  | "only from RemoteViewsFactory (ie. on collection items)."); | 
|  | return; | 
|  | } | 
|  | if (target == root) { | 
|  | target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent); | 
|  | } else if (fillInIntent != null) { | 
|  | OnClickListener listener = new OnClickListener() { | 
|  | public void onClick(View v) { | 
|  | // Insure that this view is a child of an AdapterView | 
|  | View parent = (View) v.getParent(); | 
|  | // Break the for loop on the first encounter of: | 
|  | //    1) an AdapterView, | 
|  | //    2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or | 
|  | //    3) a null parent. | 
|  | // 2) and 3) are unexpected and catch the case where a child is not | 
|  | // correctly parented in an AdapterView. | 
|  | while (parent != null && !(parent instanceof AdapterView<?>) | 
|  | && !((parent instanceof AppWidgetHostView) && | 
|  | !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) { | 
|  | parent = (View) parent.getParent(); | 
|  | } | 
|  |  | 
|  | if (!(parent instanceof AdapterView<?>)) { | 
|  | // Somehow they've managed to get this far without having | 
|  | // and AdapterView as a parent. | 
|  | Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Insure that a template pending intent has been set on an ancestor | 
|  | if (!(parent.getTag() instanceof PendingIntent)) { | 
|  | Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" + | 
|  | " calling setPendingIntentTemplate on parent."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | PendingIntent pendingIntent = (PendingIntent) parent.getTag(); | 
|  |  | 
|  | final Rect rect = getSourceBounds(v); | 
|  |  | 
|  | fillInIntent.setSourceBounds(rect); | 
|  | handler.onClickHandler(v, pendingIntent, fillInIntent); | 
|  | } | 
|  |  | 
|  | }; | 
|  | target.setOnClickListener(listener); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_ON_CLICK_FILL_IN_INTENT_TAG; | 
|  | } | 
|  |  | 
|  | Intent fillInIntent; | 
|  | } | 
|  |  | 
|  | private class SetPendingIntentTemplate extends Action { | 
|  | public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { | 
|  | this.viewId = id; | 
|  | this.pendingIntentTemplate = pendingIntentTemplate; | 
|  | } | 
|  |  | 
|  | public SetPendingIntentTemplate(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  |  | 
|  | // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense | 
|  | if (target instanceof AdapterView<?>) { | 
|  | AdapterView<?> av = (AdapterView<?>) target; | 
|  | // The PendingIntent template is stored in the view's tag. | 
|  | OnItemClickListener listener = new OnItemClickListener() { | 
|  | public void onItemClick(AdapterView<?> parent, View view, | 
|  | int position, long id) { | 
|  | // The view should be a frame layout | 
|  | if (view instanceof ViewGroup) { | 
|  | ViewGroup vg = (ViewGroup) view; | 
|  |  | 
|  | // AdapterViews contain their children in a frame | 
|  | // so we need to go one layer deeper here. | 
|  | if (parent instanceof AdapterViewAnimator) { | 
|  | vg = (ViewGroup) vg.getChildAt(0); | 
|  | } | 
|  | if (vg == null) return; | 
|  |  | 
|  | Intent fillInIntent = null; | 
|  | int childCount = vg.getChildCount(); | 
|  | for (int i = 0; i < childCount; i++) { | 
|  | Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); | 
|  | if (tag instanceof Intent) { | 
|  | fillInIntent = (Intent) tag; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (fillInIntent == null) return; | 
|  |  | 
|  | final Rect rect = getSourceBounds(view); | 
|  |  | 
|  | final Intent intent = new Intent(); | 
|  | intent.setSourceBounds(rect); | 
|  | handler.onClickHandler(view, pendingIntentTemplate, fillInIntent); | 
|  | } | 
|  | } | 
|  | }; | 
|  | av.setOnItemClickListener(listener); | 
|  | av.setTag(pendingIntentTemplate); | 
|  | } else { | 
|  | Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" + | 
|  | "an AdapterView (id: " + viewId + ")"); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_PENDING_INTENT_TEMPLATE_TAG; | 
|  | } | 
|  |  | 
|  | PendingIntent pendingIntentTemplate; | 
|  | } | 
|  |  | 
|  | private class SetRemoteViewsAdapterList extends Action { | 
|  | public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) { | 
|  | this.viewId = id; | 
|  | this.list = list; | 
|  | this.viewTypeCount = viewTypeCount; | 
|  | } | 
|  |  | 
|  | public SetRemoteViewsAdapterList(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | viewTypeCount = parcel.readInt(); | 
|  | list = parcel.createTypedArrayList(RemoteViews.CREATOR); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeInt(viewTypeCount); | 
|  | dest.writeTypedList(list, flags); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  |  | 
|  | // Ensure that we are applying to an AppWidget root | 
|  | if (!(rootParent instanceof AppWidgetHostView)) { | 
|  | Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + | 
|  | "AppWidgets (root id: " + viewId + ")"); | 
|  | return; | 
|  | } | 
|  | // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it | 
|  | if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { | 
|  | Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + | 
|  | "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (target instanceof AbsListView) { | 
|  | AbsListView v = (AbsListView) target; | 
|  | Adapter a = v.getAdapter(); | 
|  | if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { | 
|  | ((RemoteViewsListAdapter) a).setViewsList(list); | 
|  | } else { | 
|  | v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); | 
|  | } | 
|  | } else if (target instanceof AdapterViewAnimator) { | 
|  | AdapterViewAnimator v = (AdapterViewAnimator) target; | 
|  | Adapter a = v.getAdapter(); | 
|  | if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { | 
|  | ((RemoteViewsListAdapter) a).setViewsList(list); | 
|  | } else { | 
|  | v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_REMOTE_VIEW_ADAPTER_LIST_TAG; | 
|  | } | 
|  |  | 
|  | int viewTypeCount; | 
|  | ArrayList<RemoteViews> list; | 
|  | } | 
|  |  | 
|  | private class SetRemoteViewsAdapterIntent extends Action { | 
|  | public SetRemoteViewsAdapterIntent(int id, Intent intent) { | 
|  | this.viewId = id; | 
|  | this.intent = intent; | 
|  | } | 
|  |  | 
|  | public SetRemoteViewsAdapterIntent(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | intent = parcel.readTypedObject(Intent.CREATOR); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeTypedObject(intent, flags); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  |  | 
|  | // Ensure that we are applying to an AppWidget root | 
|  | if (!(rootParent instanceof AppWidgetHostView)) { | 
|  | Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + | 
|  | "AppWidgets (root id: " + viewId + ")"); | 
|  | return; | 
|  | } | 
|  | // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it | 
|  | if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { | 
|  | Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + | 
|  | "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // 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()); | 
|  | if (target instanceof AbsListView) { | 
|  | AbsListView v = (AbsListView) target; | 
|  | v.setRemoteViewsAdapter(intent, isAsync); | 
|  | v.setRemoteViewsOnClickHandler(handler); | 
|  | } else if (target instanceof AdapterViewAnimator) { | 
|  | AdapterViewAnimator v = (AdapterViewAnimator) target; | 
|  | v.setRemoteViewsAdapter(intent, isAsync); | 
|  | v.setRemoteViewsOnClickHandler(handler); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Action initActionAsync(ViewTree root, ViewGroup rootParent, | 
|  | OnClickHandler handler) { | 
|  | SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent); | 
|  | copy.isAsync = true; | 
|  | return copy; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG; | 
|  | } | 
|  |  | 
|  | Intent intent; | 
|  | boolean isAsync = false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling | 
|  | * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} | 
|  | * to launch the provided {@link PendingIntent}. | 
|  | */ | 
|  | private class SetOnClickPendingIntent extends Action { | 
|  | public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { | 
|  | this.viewId = id; | 
|  | this.pendingIntent = pendingIntent; | 
|  | } | 
|  |  | 
|  | public SetOnClickPendingIntent(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  |  | 
|  | // 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) { | 
|  | Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " + | 
|  | "(id: " + viewId + ")"); | 
|  | ApplicationInfo appInfo = root.getContext().getApplicationInfo(); | 
|  |  | 
|  | // We let this slide for HC and ICS so as to not break compatibility. It should have | 
|  | // been disabled from the outset, but was left open by accident. | 
|  | if (appInfo != null && | 
|  | appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the pendingIntent is null, we clear the onClickListener | 
|  | OnClickListener listener = null; | 
|  | if (pendingIntent != null) { | 
|  | listener = new OnClickListener() { | 
|  | public void onClick(View v) { | 
|  | // Find target view location in screen coordinates and | 
|  | // fill into PendingIntent before sending. | 
|  | final Rect rect = getSourceBounds(v); | 
|  |  | 
|  | final Intent intent = new Intent(); | 
|  | intent.setSourceBounds(rect); | 
|  | handler.onClickHandler(v, pendingIntent, intent); | 
|  | } | 
|  | }; | 
|  | } | 
|  | target.setOnClickListener(listener); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_ON_CLICK_PENDING_INTENT_TAG; | 
|  | } | 
|  |  | 
|  | PendingIntent pendingIntent; | 
|  | } | 
|  |  | 
|  | private static Rect getSourceBounds(View v) { | 
|  | final float appScale = v.getContext().getResources() | 
|  | .getCompatibilityInfo().applicationScale; | 
|  | final int[] pos = new int[2]; | 
|  | v.getLocationOnScreen(pos); | 
|  |  | 
|  | final Rect rect = new Rect(); | 
|  | rect.left = (int) (pos[0] * appScale + 0.5f); | 
|  | rect.top = (int) (pos[1] * appScale + 0.5f); | 
|  | rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); | 
|  | rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); | 
|  | return rect; | 
|  | } | 
|  |  | 
|  | private MethodHandle getMethod(View view, String methodName, Class<?> paramType, | 
|  | boolean async) { | 
|  | MethodArgs result; | 
|  | Class<? extends View> klass = view.getClass(); | 
|  |  | 
|  | synchronized (sMethods) { | 
|  | // The key is defined by the view class, param class and method name. | 
|  | sLookupKey.set(klass, paramType, methodName); | 
|  | result = sMethods.get(sLookupKey); | 
|  |  | 
|  | if (result == null) { | 
|  | Method method; | 
|  | try { | 
|  | if (paramType == null) { | 
|  | method = klass.getMethod(methodName); | 
|  | } else { | 
|  | method = klass.getMethod(methodName, paramType); | 
|  | } | 
|  | if (!method.isAnnotationPresent(RemotableViewMethod.class)) { | 
|  | throw new ActionException("view: " + klass.getName() | 
|  | + " can't use method with RemoteViews: " | 
|  | + methodName + getParameters(paramType)); | 
|  | } | 
|  |  | 
|  | result = new MethodArgs(); | 
|  | result.syncMethod = MethodHandles.publicLookup().unreflect(method); | 
|  | result.asyncMethodName = | 
|  | method.getAnnotation(RemotableViewMethod.class).asyncImpl(); | 
|  | } catch (NoSuchMethodException | IllegalAccessException ex) { | 
|  | throw new ActionException("view: " + klass.getName() + " doesn't have method: " | 
|  | + methodName + getParameters(paramType)); | 
|  | } | 
|  |  | 
|  | MethodKey key = new MethodKey(); | 
|  | key.set(klass, paramType, methodName); | 
|  | sMethods.put(key, result); | 
|  | } | 
|  |  | 
|  | if (!async) { | 
|  | return result.syncMethod; | 
|  | } | 
|  | // Check this so see if async method is implemented or not. | 
|  | if (result.asyncMethodName.isEmpty()) { | 
|  | return null; | 
|  | } | 
|  | // Async method is lazily loaded. If it is not yet loaded, load now. | 
|  | if (result.asyncMethod == null) { | 
|  | MethodType asyncType = result.syncMethod.type() | 
|  | .dropParameterTypes(0, 1).changeReturnType(Runnable.class); | 
|  | try { | 
|  | result.asyncMethod = MethodHandles.publicLookup().findVirtual( | 
|  | klass, result.asyncMethodName, asyncType); | 
|  | } catch (NoSuchMethodException | IllegalAccessException ex) { | 
|  | throw new ActionException("Async implementation declared as " | 
|  | + result.asyncMethodName + " but not defined for " + methodName | 
|  | + ": public Runnable " + result.asyncMethodName + " (" | 
|  | + TextUtils.join(",", asyncType.parameterArray()) + ")"); | 
|  | } | 
|  | } | 
|  | return result.asyncMethod; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static String getParameters(Class<?> paramType) { | 
|  | if (paramType == null) return "()"; | 
|  | return "(" + paramType + ")"; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling | 
|  | * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, | 
|  | * on the {@link Drawable} of a given view. | 
|  | * <p> | 
|  | * 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()}. | 
|  | * <p> | 
|  | */ | 
|  | private class SetDrawableTint extends Action { | 
|  | SetDrawableTint(int id, boolean targetBackground, | 
|  | 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; | 
|  | int colorFilter; | 
|  | PorterDuff.Mode filterMode; | 
|  | } | 
|  |  | 
|  | private final class ViewContentNavigation extends Action { | 
|  | final boolean mNext; | 
|  |  | 
|  | ViewContentNavigation(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 { | 
|  |  | 
|  | ArrayList<Bitmap> mBitmaps; | 
|  | int mBitmapMemory = -1; | 
|  |  | 
|  | public BitmapCache() { | 
|  | mBitmaps = new ArrayList<>(); | 
|  | } | 
|  |  | 
|  | public BitmapCache(Parcel source) { | 
|  | mBitmaps = source.createTypedArrayList(Bitmap.CREATOR); | 
|  | } | 
|  |  | 
|  | public int getBitmapId(Bitmap b) { | 
|  | if (b == null) { | 
|  | return -1; | 
|  | } else { | 
|  | if (mBitmaps.contains(b)) { | 
|  | return mBitmaps.indexOf(b); | 
|  | } else { | 
|  | mBitmaps.add(b); | 
|  | mBitmapMemory = -1; | 
|  | return (mBitmaps.size() - 1); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public Bitmap getBitmapForId(int id) { | 
|  | if (id == -1 || id >= mBitmaps.size()) { | 
|  | return null; | 
|  | } else { | 
|  | return mBitmaps.get(id); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void writeBitmapsToParcel(Parcel dest, int flags) { | 
|  | dest.writeTypedList(mBitmaps, flags); | 
|  | } | 
|  |  | 
|  | public int getBitmapMemory() { | 
|  | if (mBitmapMemory < 0) { | 
|  | mBitmapMemory = 0; | 
|  | int count = mBitmaps.size(); | 
|  | for (int i = 0; i < count; i++) { | 
|  | mBitmapMemory += mBitmaps.get(i).getAllocationByteCount(); | 
|  | } | 
|  | } | 
|  | return mBitmapMemory; | 
|  | } | 
|  | } | 
|  |  | 
|  | private class BitmapReflectionAction extends Action { | 
|  | int bitmapId; | 
|  | Bitmap bitmap; | 
|  | String methodName; | 
|  |  | 
|  | BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { | 
|  | this.bitmap = bitmap; | 
|  | this.viewId = viewId; | 
|  | this.methodName = methodName; | 
|  | bitmapId = mBitmapCache.getBitmapId(bitmap); | 
|  | } | 
|  |  | 
|  | BitmapReflectionAction(Parcel in) { | 
|  | viewId = in.readInt(); | 
|  | methodName = in.readString(); | 
|  | bitmapId = in.readInt(); | 
|  | bitmap = mBitmapCache.getBitmapForId(bitmapId); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeString(methodName); | 
|  | dest.writeInt(bitmapId); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, | 
|  | OnClickHandler handler) throws ActionException { | 
|  | ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, | 
|  | bitmap); | 
|  | ra.apply(root, rootParent, handler); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void setBitmapCache(BitmapCache bitmapCache) { | 
|  | bitmapId = bitmapCache.getBitmapId(bitmap); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return BITMAP_REFLECTION_ACTION_TAG; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Base class for the reflection actions. | 
|  | */ | 
|  | private final class ReflectionAction extends Action { | 
|  | static final int BOOLEAN = 1; | 
|  | static final int BYTE = 2; | 
|  | static final int SHORT = 3; | 
|  | static final int INT = 4; | 
|  | static final int LONG = 5; | 
|  | static final int FLOAT = 6; | 
|  | static final int DOUBLE = 7; | 
|  | static final int CHAR = 8; | 
|  | static final int STRING = 9; | 
|  | static final int CHAR_SEQUENCE = 10; | 
|  | static final int URI = 11; | 
|  | // BITMAP actions are never stored in the list of actions. They are only used locally | 
|  | // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. | 
|  | static final int BITMAP = 12; | 
|  | static final int BUNDLE = 13; | 
|  | static final int INTENT = 14; | 
|  | static final int COLOR_STATE_LIST = 15; | 
|  | static final int ICON = 16; | 
|  |  | 
|  | String methodName; | 
|  | int type; | 
|  | Object value; | 
|  |  | 
|  | ReflectionAction(int viewId, String methodName, int type, Object value) { | 
|  | this.viewId = viewId; | 
|  | this.methodName = methodName; | 
|  | this.type = type; | 
|  | this.value = value; | 
|  | } | 
|  |  | 
|  | ReflectionAction(Parcel in) { | 
|  | this.viewId = in.readInt(); | 
|  | this.methodName = in.readString(); | 
|  | this.type = in.readInt(); | 
|  | //noinspection ConstantIfStatement | 
|  | if (false) { | 
|  | Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId) | 
|  | + " methodName=" + this.methodName + " type=" + this.type); | 
|  | } | 
|  |  | 
|  | // For some values that may have been null, we first check a flag to see if they were | 
|  | // written to the parcel. | 
|  | switch (this.type) { | 
|  | case BOOLEAN: | 
|  | this.value = in.readBoolean(); | 
|  | break; | 
|  | case BYTE: | 
|  | this.value = in.readByte(); | 
|  | break; | 
|  | case SHORT: | 
|  | this.value = (short)in.readInt(); | 
|  | break; | 
|  | case INT: | 
|  | this.value = in.readInt(); | 
|  | break; | 
|  | case LONG: | 
|  | this.value = in.readLong(); | 
|  | break; | 
|  | case FLOAT: | 
|  | this.value = in.readFloat(); | 
|  | break; | 
|  | case DOUBLE: | 
|  | this.value = in.readDouble(); | 
|  | break; | 
|  | case CHAR: | 
|  | this.value = (char)in.readInt(); | 
|  | break; | 
|  | case STRING: | 
|  | this.value = in.readString(); | 
|  | break; | 
|  | case CHAR_SEQUENCE: | 
|  | this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); | 
|  | break; | 
|  | case URI: | 
|  | this.value = in.readTypedObject(Uri.CREATOR); | 
|  | break; | 
|  | case BITMAP: | 
|  | this.value = in.readTypedObject(Bitmap.CREATOR); | 
|  | break; | 
|  | case BUNDLE: | 
|  | this.value = in.readBundle(); | 
|  | break; | 
|  | case INTENT: | 
|  | this.value = in.readTypedObject(Intent.CREATOR); | 
|  | break; | 
|  | case COLOR_STATE_LIST: | 
|  | this.value = in.readTypedObject(ColorStateList.CREATOR); | 
|  | break; | 
|  | case ICON: | 
|  | this.value = in.readTypedObject(Icon.CREATOR); | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel out, int flags) { | 
|  | out.writeInt(this.viewId); | 
|  | out.writeString(this.methodName); | 
|  | out.writeInt(this.type); | 
|  | //noinspection ConstantIfStatement | 
|  | if (false) { | 
|  | Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId) | 
|  | + " methodName=" + this.methodName + " type=" + this.type); | 
|  | } | 
|  |  | 
|  | // For some values which are null, we record an integer flag to indicate whether | 
|  | // we have written a valid value to the parcel. | 
|  | switch (this.type) { | 
|  | case BOOLEAN: | 
|  | out.writeBoolean((Boolean) this.value); | 
|  | break; | 
|  | case BYTE: | 
|  | out.writeByte((Byte) this.value); | 
|  | break; | 
|  | case SHORT: | 
|  | out.writeInt((Short) this.value); | 
|  | break; | 
|  | case INT: | 
|  | out.writeInt((Integer) this.value); | 
|  | break; | 
|  | case LONG: | 
|  | out.writeLong((Long) this.value); | 
|  | break; | 
|  | case FLOAT: | 
|  | out.writeFloat((Float) this.value); | 
|  | break; | 
|  | case DOUBLE: | 
|  | out.writeDouble((Double) this.value); | 
|  | break; | 
|  | case CHAR: | 
|  | out.writeInt((int)((Character)this.value).charValue()); | 
|  | break; | 
|  | case STRING: | 
|  | out.writeString((String)this.value); | 
|  | break; | 
|  | case CHAR_SEQUENCE: | 
|  | TextUtils.writeToParcel((CharSequence)this.value, out, flags); | 
|  | break; | 
|  | case BUNDLE: | 
|  | out.writeBundle((Bundle) this.value); | 
|  | break; | 
|  | case URI: | 
|  | case BITMAP: | 
|  | case INTENT: | 
|  | case COLOR_STATE_LIST: | 
|  | case ICON: | 
|  | out.writeTypedObject((Parcelable) this.value, flags); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | private Class<?> getParameterType() { | 
|  | switch (this.type) { | 
|  | case BOOLEAN: | 
|  | return boolean.class; | 
|  | case BYTE: | 
|  | return byte.class; | 
|  | case SHORT: | 
|  | return short.class; | 
|  | case INT: | 
|  | return int.class; | 
|  | case LONG: | 
|  | return long.class; | 
|  | case FLOAT: | 
|  | return float.class; | 
|  | case DOUBLE: | 
|  | return double.class; | 
|  | case CHAR: | 
|  | return char.class; | 
|  | case STRING: | 
|  | return String.class; | 
|  | case CHAR_SEQUENCE: | 
|  | return CharSequence.class; | 
|  | case URI: | 
|  | return Uri.class; | 
|  | case BITMAP: | 
|  | return Bitmap.class; | 
|  | case BUNDLE: | 
|  | return Bundle.class; | 
|  | case INTENT: | 
|  | return Intent.class; | 
|  | case COLOR_STATE_LIST: | 
|  | return ColorStateList.class; | 
|  | case ICON: | 
|  | return Icon.class; | 
|  | default: | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View view = root.findViewById(viewId); | 
|  | if (view == null) return; | 
|  |  | 
|  | Class<?> param = getParameterType(); | 
|  | if (param == null) { | 
|  | throw new ActionException("bad type: " + this.type); | 
|  | } | 
|  | try { | 
|  | getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value); | 
|  | } catch (Throwable ex) { | 
|  | throw new ActionException(ex); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View view = root.findViewById(viewId); | 
|  | if (view == null) return ACTION_NOOP; | 
|  |  | 
|  | Class<?> param = getParameterType(); | 
|  | if (param == null) { | 
|  | throw new ActionException("bad type: " + this.type); | 
|  | } | 
|  |  | 
|  | try { | 
|  | MethodHandle method = getMethod(view, this.methodName, param, true /* async */); | 
|  |  | 
|  | if (method != null) { | 
|  | Runnable endAction = (Runnable) method.invoke(view, this.value); | 
|  | if (endAction == null) { | 
|  | return ACTION_NOOP; | 
|  | } else { | 
|  | // Special case view stub | 
|  | if (endAction instanceof ViewStub.ViewReplaceRunnable) { | 
|  | root.createTree(); | 
|  | // Replace child tree | 
|  | root.findViewTreeById(viewId).replaceView( | 
|  | ((ViewStub.ViewReplaceRunnable) endAction).view); | 
|  | } | 
|  | return new RunnableAction(endAction); | 
|  | } | 
|  | } | 
|  | } catch (Throwable ex) { | 
|  | throw new ActionException(ex); | 
|  | } | 
|  |  | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public int mergeBehavior() { | 
|  | // smoothScrollBy is cumulative, everything else overwites. | 
|  | if (methodName.equals("smoothScrollBy")) { | 
|  | return MERGE_APPEND; | 
|  | } else { | 
|  | return MERGE_REPLACE; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return REFLECTION_ACTION_TAG; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getUniqueKey() { | 
|  | // Each type of reflection action corresponds to a setter, so each should be seen as | 
|  | // unique from the standpoint of merging. | 
|  | return super.getUniqueKey() + this.methodName + this.type; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean prefersAsyncApply() { | 
|  | return this.type == URI || this.type == ICON; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * This is only used for async execution of actions and it not parcelable. | 
|  | */ | 
|  | private static final class RunnableAction extends RuntimeAction { | 
|  | private final Runnable mRunnable; | 
|  |  | 
|  | RunnableAction(Runnable r) { | 
|  | mRunnable = r; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | mRunnable.run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void configureRemoteViewsAsChild(RemoteViews rv) { | 
|  | rv.setBitmapCache(mBitmapCache); | 
|  | rv.setNotRoot(); | 
|  | } | 
|  |  | 
|  | void setNotRoot() { | 
|  | mIsRoot = false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ViewGroup methods that are related to adding Views. | 
|  | */ | 
|  | private class ViewGroupActionAdd extends Action { | 
|  | private RemoteViews mNestedViews; | 
|  | private int mIndex; | 
|  |  | 
|  | ViewGroupActionAdd(int viewId, RemoteViews nestedViews) { | 
|  | this(viewId, nestedViews, -1 /* index */); | 
|  | } | 
|  |  | 
|  | ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) { | 
|  | this.viewId = viewId; | 
|  | mNestedViews = nestedViews; | 
|  | mIndex = index; | 
|  | if (nestedViews != null) { | 
|  | configureRemoteViewsAsChild(nestedViews); | 
|  | } | 
|  | } | 
|  |  | 
|  | ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, | 
|  | int depth, Map<Class, Object> classCookies) { | 
|  | viewId = parcel.readInt(); | 
|  | mIndex = parcel.readInt(); | 
|  | mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeInt(mIndex); | 
|  | mNestedViews.writeToParcel(dest, flags); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean hasSameAppInfo(ApplicationInfo parentInfo) { | 
|  | return mNestedViews.hasSameAppInfo(parentInfo); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final Context context = root.getContext(); | 
|  | final ViewGroup target = root.findViewById(viewId); | 
|  |  | 
|  | if (target == null) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Inflate nested views and add as children | 
|  | target.addView(mNestedViews.apply(context, target, handler), mIndex); | 
|  | } | 
|  |  | 
|  | @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; | 
|  |  | 
|  | // Inflate nested views and perform all the async tasks for the child remoteView. | 
|  | final Context context = root.mRoot.getContext(); | 
|  | final AsyncApplyTask task = mNestedViews.getAsyncApplyTask( | 
|  | context, targetVg, null, handler); | 
|  | final ViewTree tree = task.doInBackground(); | 
|  |  | 
|  | if (tree == null) { | 
|  | throw new ActionException(task.mError); | 
|  | } | 
|  |  | 
|  | // Update the global view tree, so that next call to findViewTreeById | 
|  | // goes through the subtree as well. | 
|  | target.addChild(tree, mIndex); | 
|  |  | 
|  | return new RuntimeAction() { | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) | 
|  | throws ActionException { | 
|  | task.onPostExecute(tree); | 
|  | targetVg.addView(task.mResult, mIndex); | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void setBitmapCache(BitmapCache bitmapCache) { | 
|  | mNestedViews.setBitmapCache(bitmapCache); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int mergeBehavior() { | 
|  | return MERGE_APPEND; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean prefersAsyncApply() { | 
|  | return mNestedViews.prefersAsyncApply(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return VIEW_GROUP_ACTION_ADD_TAG; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ViewGroup methods related to removing child views. | 
|  | */ | 
|  | private class ViewGroupActionRemove extends Action { | 
|  | /** | 
|  | * Id that indicates that all child views of the affected ViewGroup should be removed. | 
|  | * | 
|  | * <p>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(int viewId) { | 
|  | this(viewId, REMOVE_ALL_VIEWS_ID); | 
|  | } | 
|  |  | 
|  | ViewGroupActionRemove(int viewId, 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; | 
|  |  | 
|  | // Clear all children when nested views omitted | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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(int viewId, boolean isRelative, int d1, int d2, int d3, 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(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; | 
|  | } | 
|  |  | 
|  | boolean isRelative = false; | 
|  | boolean useIcons = false; | 
|  | int d1, d2, d3, d4; | 
|  | Icon i1, i2, i3, i4; | 
|  |  | 
|  | boolean drawablesLoaded = false; | 
|  | Drawable id1, id2, id3, id4; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper action to set text size on a TextView in any supported units. | 
|  | */ | 
|  | private class TextViewSizeAction extends Action { | 
|  | public TextViewSizeAction(int viewId, int units, float size) { | 
|  | this.viewId = viewId; | 
|  | this.units = units; | 
|  | this.size = size; | 
|  | } | 
|  |  | 
|  | public TextViewSizeAction(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | units = parcel.readInt(); | 
|  | size  = parcel.readFloat(); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeInt(units); | 
|  | dest.writeFloat(size); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final TextView target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  | target.setTextSize(units, size); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return TEXT_VIEW_SIZE_ACTION_TAG; | 
|  | } | 
|  |  | 
|  | int units; | 
|  | float size; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper action to set padding on a View. | 
|  | */ | 
|  | private class ViewPaddingAction extends Action { | 
|  | public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { | 
|  | this.viewId = viewId; | 
|  | this.left = left; | 
|  | this.top = top; | 
|  | this.right = right; | 
|  | this.bottom = bottom; | 
|  | } | 
|  |  | 
|  | public ViewPaddingAction(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | left = parcel.readInt(); | 
|  | top = parcel.readInt(); | 
|  | right = parcel.readInt(); | 
|  | bottom = parcel.readInt(); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeInt(left); | 
|  | dest.writeInt(top); | 
|  | dest.writeInt(right); | 
|  | dest.writeInt(bottom); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  | target.setPadding(left, top, right, bottom); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return VIEW_PADDING_ACTION_TAG; | 
|  | } | 
|  |  | 
|  | int left, top, right, bottom; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper action to set layout params on a View. | 
|  | */ | 
|  | private static class LayoutParamAction extends Action { | 
|  |  | 
|  | /** Set marginEnd */ | 
|  | public static final int LAYOUT_MARGIN_END_DIMEN = 1; | 
|  | /** Set width */ | 
|  | public static final int LAYOUT_WIDTH = 2; | 
|  | public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3; | 
|  |  | 
|  | final int mProperty; | 
|  | final int mValue; | 
|  |  | 
|  | /** | 
|  | * @param viewId ID of the view alter | 
|  | * @param property which layout parameter to alter | 
|  | * @param value new value of the layout parameter | 
|  | */ | 
|  | public LayoutParamAction(int viewId, int property, int value) { | 
|  | this.viewId = viewId; | 
|  | this.mProperty = property; | 
|  | this.mValue = value; | 
|  | } | 
|  |  | 
|  | public LayoutParamAction(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | mProperty = parcel.readInt(); | 
|  | mValue = parcel.readInt(); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeInt(mProperty); | 
|  | dest.writeInt(mValue); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) { | 
|  | return; | 
|  | } | 
|  | ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); | 
|  | if (layoutParams == null) { | 
|  | return; | 
|  | } | 
|  | switch (mProperty) { | 
|  | case LAYOUT_MARGIN_END_DIMEN: | 
|  | if (layoutParams instanceof ViewGroup.MarginLayoutParams) { | 
|  | int resolved = resolveDimenPixelOffset(target, mValue); | 
|  | ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved); | 
|  | target.setLayoutParams(layoutParams); | 
|  | } | 
|  | break; | 
|  | case LAYOUT_MARGIN_BOTTOM_DIMEN: | 
|  | if (layoutParams instanceof ViewGroup.MarginLayoutParams) { | 
|  | int resolved = resolveDimenPixelOffset(target, mValue); | 
|  | ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved; | 
|  | target.setLayoutParams(layoutParams); | 
|  | } | 
|  | break; | 
|  | case LAYOUT_WIDTH: | 
|  | layoutParams.width = mValue; | 
|  | target.setLayoutParams(layoutParams); | 
|  | break; | 
|  | default: | 
|  | throw new IllegalArgumentException("Unknown property " + mProperty); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static int resolveDimenPixelOffset(View target, int value) { | 
|  | if (value == 0) { | 
|  | return 0; | 
|  | } | 
|  | return target.getContext().getResources().getDimensionPixelOffset(value); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return LAYOUT_PARAM_ACTION_TAG; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String getUniqueKey() { | 
|  | return super.getUniqueKey() + mProperty; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper action to add a view tag with RemoteInputs. | 
|  | */ | 
|  | private class SetRemoteInputsAction extends Action { | 
|  |  | 
|  | public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) { | 
|  | this.viewId = viewId; | 
|  | this.remoteInputs = remoteInputs; | 
|  | } | 
|  |  | 
|  | public SetRemoteInputsAction(Parcel parcel) { | 
|  | viewId = parcel.readInt(); | 
|  | remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(viewId); | 
|  | dest.writeTypedArray(remoteInputs, flags); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | final View target = root.findViewById(viewId); | 
|  | if (target == null) return; | 
|  |  | 
|  | target.setTagInternal(R.id.remote_input_tag, remoteInputs); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return SET_REMOTE_INPUTS_ACTION_TAG; | 
|  | } | 
|  |  | 
|  | final Parcelable[] remoteInputs; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper action to override all textViewColors | 
|  | */ | 
|  | private class OverrideTextColorsAction extends Action { | 
|  |  | 
|  | private final int textColor; | 
|  |  | 
|  | public OverrideTextColorsAction(int textColor) { | 
|  | this.textColor = textColor; | 
|  | } | 
|  |  | 
|  | public OverrideTextColorsAction(Parcel parcel) { | 
|  | textColor = parcel.readInt(); | 
|  | } | 
|  |  | 
|  | public void writeToParcel(Parcel dest, int flags) { | 
|  | dest.writeInt(textColor); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { | 
|  | // Let's traverse the viewtree and override all textColors! | 
|  | Stack<View> viewsToProcess = new Stack<>(); | 
|  | viewsToProcess.add(root); | 
|  | while (!viewsToProcess.isEmpty()) { | 
|  | View v = viewsToProcess.pop(); | 
|  | if (v instanceof TextView) { | 
|  | TextView textView = (TextView) v; | 
|  | textView.setText(NotificationColorUtil.clearColorSpans(textView.getText())); | 
|  | textView.setTextColor(textColor); | 
|  | } | 
|  | if (v instanceof ViewGroup) { | 
|  | ViewGroup viewGroup = (ViewGroup) v; | 
|  | for (int i = 0; i < viewGroup.getChildCount(); i++) { | 
|  | viewsToProcess.push(viewGroup.getChildAt(i)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getActionTag() { | 
|  | return OVERRIDE_TEXT_COLORS_TAG; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new RemoteViews object that will display the views contained | 
|  | * in the specified layout file. | 
|  | * | 
|  | * @param packageName Name of the package that contains the layout resource | 
|  | * @param layoutId The id of the layout resource | 
|  | */ | 
|  | public RemoteViews(String packageName, int layoutId) { | 
|  | this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new RemoteViews object that will display the views contained | 
|  | * in the specified layout file. | 
|  | * | 
|  | * @param packageName Name of the package that contains the layout resource. | 
|  | * @param userId The user under which the package is running. | 
|  | * @param layoutId The id of the layout resource. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public RemoteViews(String packageName, int userId, int layoutId) { | 
|  | this(getApplicationInfo(packageName, userId), layoutId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new RemoteViews object that will display the views contained | 
|  | * in the specified layout file. | 
|  | * | 
|  | * @param application The application whose content is shown by the views. | 
|  | * @param layoutId The id of the layout resource. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | protected RemoteViews(ApplicationInfo application, int layoutId) { | 
|  | mApplication = application; | 
|  | mLayoutId = layoutId; | 
|  | mBitmapCache = new BitmapCache(); | 
|  | mClassCookies = null; | 
|  | } | 
|  |  | 
|  | private boolean hasLandscapeAndPortraitLayouts() { | 
|  | return (mLandscape != null) && (mPortrait != null); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new RemoteViews object that will inflate as the specified | 
|  | * landspace or portrait RemoteViews, depending on the current configuration. | 
|  | * | 
|  | * @param landscape The RemoteViews to inflate in landscape configuration | 
|  | * @param portrait The RemoteViews to inflate in portrait configuration | 
|  | */ | 
|  | public RemoteViews(RemoteViews landscape, RemoteViews portrait) { | 
|  | if (landscape == null || portrait == null) { | 
|  | throw new RuntimeException("Both RemoteViews must be non-null"); | 
|  | } | 
|  | if (!landscape.hasSameAppInfo(portrait.mApplication)) { | 
|  | throw new RuntimeException("Both RemoteViews must share the same package and user"); | 
|  | } | 
|  | mApplication = portrait.mApplication; | 
|  | mLayoutId = portrait.getLayoutId(); | 
|  |  | 
|  | mLandscape = landscape; | 
|  | mPortrait = portrait; | 
|  |  | 
|  | mBitmapCache = new BitmapCache(); | 
|  | configureRemoteViewsAsChild(landscape); | 
|  | configureRemoteViewsAsChild(portrait); | 
|  |  | 
|  | mClassCookies = (portrait.mClassCookies != null) | 
|  | ? portrait.mClassCookies : landscape.mClassCookies; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a copy of another RemoteViews. | 
|  | */ | 
|  | public RemoteViews(RemoteViews src) { | 
|  | mBitmapCache = src.mBitmapCache; | 
|  | mApplication = src.mApplication; | 
|  | mIsRoot = src.mIsRoot; | 
|  | mLayoutId = src.mLayoutId; | 
|  | mIsWidgetCollectionChild = src.mIsWidgetCollectionChild; | 
|  | mReapplyDisallowed = src.mReapplyDisallowed; | 
|  | mClassCookies = src.mClassCookies; | 
|  |  | 
|  | if (src.hasLandscapeAndPortraitLayouts()) { | 
|  | mLandscape = new RemoteViews(src.mLandscape); | 
|  | mPortrait = new RemoteViews(src.mPortrait); | 
|  | } | 
|  |  | 
|  | if (src.mActions != null) { | 
|  | Parcel p = Parcel.obtain(); | 
|  | p.putClassCookies(mClassCookies); | 
|  | src.writeActionsToParcel(p); | 
|  | p.setDataPosition(0); | 
|  | // Since src is already in memory, we do not care about stack overflow as it has | 
|  | // already been read once. | 
|  | readActionsFromParcel(p, 0); | 
|  | p.recycle(); | 
|  | } | 
|  |  | 
|  | // Now that everything is initialized and duplicated, setting a new BitmapCache will | 
|  | // re-initialize the cache. | 
|  | setBitmapCache(new BitmapCache()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reads a RemoteViews object from a parcel. | 
|  | * | 
|  | * @param parcel | 
|  | */ | 
|  | public RemoteViews(Parcel parcel) { | 
|  | this(parcel, null, null, 0, null); | 
|  | } | 
|  |  | 
|  | private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, | 
|  | Map<Class, Object> classCookies) { | 
|  | if (depth > MAX_NESTED_VIEWS | 
|  | && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { | 
|  | throw new IllegalArgumentException("Too many nested views."); | 
|  | } | 
|  | depth++; | 
|  |  | 
|  | int mode = parcel.readInt(); | 
|  |  | 
|  | // We only store a bitmap cache in the root of the RemoteViews. | 
|  | if (bitmapCache == null) { | 
|  | mBitmapCache = new BitmapCache(parcel); | 
|  | // Store the class cookies such that they are available when we clone this RemoteView. | 
|  | mClassCookies = parcel.copyClassCookies(); | 
|  | } else { | 
|  | setBitmapCache(bitmapCache); | 
|  | mClassCookies = classCookies; | 
|  | setNotRoot(); | 
|  | } | 
|  |  | 
|  | if (mode == MODE_NORMAL) { | 
|  | mApplication = parcel.readInt() == 0 ? info : | 
|  | ApplicationInfo.CREATOR.createFromParcel(parcel); | 
|  | mLayoutId = parcel.readInt(); | 
|  | mIsWidgetCollectionChild = parcel.readInt() == 1; | 
|  |  | 
|  | readActionsFromParcel(parcel, depth); | 
|  | } else { | 
|  | // MODE_HAS_LANDSCAPE_AND_PORTRAIT | 
|  | mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies); | 
|  | mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth, | 
|  | mClassCookies); | 
|  | mApplication = mPortrait.mApplication; | 
|  | mLayoutId = mPortrait.getLayoutId(); | 
|  | } | 
|  | mReapplyDisallowed = parcel.readInt() == 0; | 
|  | } | 
|  |  | 
|  | private void readActionsFromParcel(Parcel parcel, int depth) { | 
|  | int count = parcel.readInt(); | 
|  | if (count > 0) { | 
|  | mActions = new ArrayList<>(count); | 
|  | for (int i = 0; i < count; i++) { | 
|  | mActions.add(getActionFromParcel(parcel, depth)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private Action getActionFromParcel(Parcel parcel, int depth) { | 
|  | int tag = parcel.readInt(); | 
|  | switch (tag) { | 
|  | case SET_ON_CLICK_PENDING_INTENT_TAG: | 
|  | return new SetOnClickPendingIntent(parcel); | 
|  | case SET_DRAWABLE_TINT_TAG: | 
|  | return new SetDrawableTint(parcel); | 
|  | case REFLECTION_ACTION_TAG: | 
|  | return new ReflectionAction(parcel); | 
|  | case VIEW_GROUP_ACTION_ADD_TAG: | 
|  | return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth, | 
|  | mClassCookies); | 
|  | case VIEW_GROUP_ACTION_REMOVE_TAG: | 
|  | return new ViewGroupActionRemove(parcel); | 
|  | case VIEW_CONTENT_NAVIGATION_TAG: | 
|  | return new ViewContentNavigation(parcel); | 
|  | case SET_EMPTY_VIEW_ACTION_TAG: | 
|  | return new SetEmptyView(parcel); | 
|  | case SET_PENDING_INTENT_TEMPLATE_TAG: | 
|  | return new SetPendingIntentTemplate(parcel); | 
|  | case SET_ON_CLICK_FILL_IN_INTENT_TAG: | 
|  | return new SetOnClickFillInIntent(parcel); | 
|  | case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG: | 
|  | return new SetRemoteViewsAdapterIntent(parcel); | 
|  | case TEXT_VIEW_DRAWABLE_ACTION_TAG: | 
|  | return new TextViewDrawableAction(parcel); | 
|  | case TEXT_VIEW_SIZE_ACTION_TAG: | 
|  | return new TextViewSizeAction(parcel); | 
|  | case VIEW_PADDING_ACTION_TAG: | 
|  | return new ViewPaddingAction(parcel); | 
|  | case BITMAP_REFLECTION_ACTION_TAG: | 
|  | return new BitmapReflectionAction(parcel); | 
|  | case SET_REMOTE_VIEW_ADAPTER_LIST_TAG: | 
|  | return new SetRemoteViewsAdapterList(parcel); | 
|  | case SET_REMOTE_INPUTS_ACTION_TAG: | 
|  | return new SetRemoteInputsAction(parcel); | 
|  | case LAYOUT_PARAM_ACTION_TAG: | 
|  | return new LayoutParamAction(parcel); | 
|  | case OVERRIDE_TEXT_COLORS_TAG: | 
|  | return new OverrideTextColorsAction(parcel); | 
|  | default: | 
|  | throw new ActionException("Tag " + tag + " not found"); | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Returns a deep copy of the RemoteViews object. The RemoteView may not be | 
|  | * attached to another RemoteView -- it must be the root of a hierarchy. | 
|  | * | 
|  | * @deprecated use {@link #RemoteViews(RemoteViews)} instead. | 
|  | * @throws IllegalStateException if this is not the root of a RemoteView | 
|  | *         hierarchy | 
|  | */ | 
|  | @Override | 
|  | @Deprecated | 
|  | public RemoteViews clone() { | 
|  | Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. " | 
|  | + "May only clone the root of a RemoteView hierarchy."); | 
|  |  | 
|  | return new RemoteViews(this); | 
|  | } | 
|  |  | 
|  | public String getPackage() { | 
|  | return (mApplication != null) ? mApplication.packageName : null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the layout id of the root layout associated with this RemoteViews. In the case | 
|  | * that the RemoteViews has both a landscape and portrait root, this will return the layout | 
|  | * id associated with the portrait layout. | 
|  | * | 
|  | * @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. | 
|  | */ | 
|  | void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { | 
|  | mIsWidgetCollectionChild = isWidgetCollectionChild; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. | 
|  | */ | 
|  | private void setBitmapCache(BitmapCache bitmapCache) { | 
|  | mBitmapCache = bitmapCache; | 
|  | if (!hasLandscapeAndPortraitLayouts()) { | 
|  | if (mActions != null) { | 
|  | final int count = mActions.size(); | 
|  | for (int i= 0; i < count; ++i) { | 
|  | mActions.get(i).setBitmapCache(bitmapCache); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | mLandscape.setBitmapCache(bitmapCache); | 
|  | mPortrait.setBitmapCache(bitmapCache); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns an estimate of the bitmap heap memory usage for this RemoteViews. | 
|  | */ | 
|  | /** @hide */ | 
|  | public int estimateMemoryUsage() { | 
|  | return mBitmapCache.getBitmapMemory(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Add an action to be executed on the remote side when apply is called. | 
|  | * | 
|  | * @param a The action to add | 
|  | */ | 
|  | private void addAction(Action a) { | 
|  | if (hasLandscapeAndPortraitLayouts()) { | 
|  | throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + | 
|  | " layouts cannot be modified. Instead, fully configure the landscape and" + | 
|  | " portrait layouts individually before constructing the combined layout."); | 
|  | } | 
|  | if (mActions == null) { | 
|  | mActions = new ArrayList<>(); | 
|  | } | 
|  | mActions.add(a); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the | 
|  | * given {@link RemoteViews}. This allows users to build "nested" | 
|  | * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may | 
|  | * recycle layouts, use {@link #removeAllViews(int)} to clear any existing | 
|  | * children. | 
|  | * | 
|  | * @param viewId The id of the parent {@link ViewGroup} to add child into. | 
|  | * @param nestedView {@link RemoteViews} that describes the child. | 
|  | */ | 
|  | public void addView(int viewId, RemoteViews nestedView) { | 
|  | addAction(nestedView == null | 
|  | ? new ViewGroupActionRemove(viewId) | 
|  | : new ViewGroupActionAdd(viewId, nestedView)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the | 
|  | * given {@link RemoteViews}. | 
|  | * | 
|  | * @param viewId The id of the parent {@link ViewGroup} to add the child into. | 
|  | * @param nestedView {@link RemoteViews} of the child to add. | 
|  | * @param index The position at which to add the child. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void addView(int viewId, RemoteViews nestedView, int index) { | 
|  | addAction(new ViewGroupActionAdd(viewId, nestedView, index)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ViewGroup#removeAllViews()}. | 
|  | * | 
|  | * @param viewId The id of the parent {@link ViewGroup} to remove all | 
|  | *            children from. | 
|  | */ | 
|  | public void removeAllViews(int viewId) { | 
|  | addAction(new ViewGroupActionRemove(viewId)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any | 
|  | * child that has the {@code viewIdToKeep} as its id. | 
|  | * | 
|  | * @param viewId The id of the parent {@link ViewGroup} to remove children from. | 
|  | * @param viewIdToKeep The id of a child that should not be removed. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void removeAllViewsExceptId(int viewId, int viewIdToKeep) { | 
|  | addAction(new ViewGroupActionRemove(viewId, viewIdToKeep)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link AdapterViewAnimator#showNext()} | 
|  | * | 
|  | * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} | 
|  | */ | 
|  | public void showNext(int viewId) { | 
|  | addAction(new ViewContentNavigation(viewId, true /* next */)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} | 
|  | * | 
|  | * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} | 
|  | */ | 
|  | public void showPrevious(int viewId) { | 
|  | addAction(new ViewContentNavigation(viewId, false /* next */)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} | 
|  | * | 
|  | * @param viewId The id of the view on which to call | 
|  | *               {@link AdapterViewAnimator#setDisplayedChild(int)} | 
|  | */ | 
|  | public void setDisplayedChild(int viewId, int childIndex) { | 
|  | setInt(viewId, "setDisplayedChild", childIndex); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link View#setVisibility(int)} | 
|  | * | 
|  | * @param viewId The id of the view whose visibility should change | 
|  | * @param visibility The new visibility for the view | 
|  | */ | 
|  | public void setViewVisibility(int viewId, int visibility) { | 
|  | setInt(viewId, "setVisibility", visibility); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link TextView#setText(CharSequence)} | 
|  | * | 
|  | * @param viewId The id of the view whose text should change | 
|  | * @param text The new text for the view | 
|  | */ | 
|  | public void setTextViewText(int viewId, CharSequence text) { | 
|  | setCharSequence(viewId, "setText", text); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link TextView#setTextSize(int, float)} | 
|  | * | 
|  | * @param viewId The id of the view whose text size should change | 
|  | * @param units The units of size (e.g. COMPLEX_UNIT_SP) | 
|  | * @param size The size of the text | 
|  | */ | 
|  | public void setTextViewTextSize(int viewId, int units, float size) { | 
|  | addAction(new TextViewSizeAction(viewId, units, size)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling | 
|  | * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. | 
|  | * | 
|  | * @param viewId The id of the view whose text should change | 
|  | * @param left The id of a drawable to place to the left of the text, or 0 | 
|  | * @param top The id of a drawable to place above the text, or 0 | 
|  | * @param right The id of a drawable to place to the right of the text, or 0 | 
|  | * @param bottom The id of a drawable to place below the text, or 0 | 
|  | */ | 
|  | public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { | 
|  | addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link | 
|  | * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. | 
|  | * | 
|  | * @param viewId The id of the view whose text should change | 
|  | * @param start The id of a drawable to place before the text (relative to the | 
|  | * layout direction), or 0 | 
|  | * @param top The id of a drawable to place above the text, or 0 | 
|  | * @param end The id of a drawable to place after the text, or 0 | 
|  | * @param bottom The id of a drawable to place below the text, or 0 | 
|  | */ | 
|  | public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { | 
|  | addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link | 
|  | * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} | 
|  | * using the drawables yielded by {@link Icon#loadDrawable(Context)}. | 
|  | * | 
|  | * @param viewId The id of the view whose text should change | 
|  | * @param left an Icon to place to the left of the text, or 0 | 
|  | * @param top an Icon to place above the text, or 0 | 
|  | * @param right an Icon to place to the right of the text, or 0 | 
|  | * @param bottom an Icon to place below the text, or 0 | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) { | 
|  | addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link | 
|  | * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} | 
|  | * using the drawables yielded by {@link Icon#loadDrawable(Context)}. | 
|  | * | 
|  | * @param viewId The id of the view whose text should change | 
|  | * @param start an Icon to place before the text (relative to the | 
|  | * layout direction), or 0 | 
|  | * @param top an Icon to place above the text, or 0 | 
|  | * @param end an Icon to place after the text, or 0 | 
|  | * @param bottom an Icon to place below the text, or 0 | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) { | 
|  | addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ImageView#setImageResource(int)} | 
|  | * | 
|  | * @param viewId The id of the view whose drawable should change | 
|  | * @param srcId The new resource id for the drawable | 
|  | */ | 
|  | public void setImageViewResource(int viewId, int srcId) { | 
|  | setInt(viewId, "setImageResource", srcId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ImageView#setImageURI(Uri)} | 
|  | * | 
|  | * @param viewId The id of the view whose drawable should change | 
|  | * @param uri The Uri for the image | 
|  | */ | 
|  | public void setImageViewUri(int viewId, Uri uri) { | 
|  | setUri(viewId, "setImageURI", uri); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)} | 
|  | * | 
|  | * @param viewId The id of the view whose bitmap should change | 
|  | * @param bitmap The new Bitmap for the drawable | 
|  | */ | 
|  | public void setImageViewBitmap(int viewId, Bitmap bitmap) { | 
|  | setBitmap(viewId, "setImageBitmap", bitmap); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ImageView#setImageIcon(Icon)} | 
|  | * | 
|  | * @param viewId The id of the view whose bitmap should change | 
|  | * @param icon The new Icon for the ImageView | 
|  | */ | 
|  | public void setImageViewIcon(int viewId, Icon icon) { | 
|  | setIcon(viewId, "setImageIcon", icon); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link AdapterView#setEmptyView(View)} | 
|  | * | 
|  | * @param viewId The id of the view on which to set the empty view | 
|  | * @param emptyViewId The view id of the empty view | 
|  | */ | 
|  | public void setEmptyView(int viewId, int emptyViewId) { | 
|  | addAction(new SetEmptyView(viewId, emptyViewId)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, | 
|  | * {@link Chronometer#setFormat Chronometer.setFormat}, | 
|  | * and {@link Chronometer#start Chronometer.start()} or | 
|  | * {@link Chronometer#stop Chronometer.stop()}. | 
|  | * | 
|  | * @param viewId The id of the {@link Chronometer} to change | 
|  | * @param base The time at which the timer would have read 0:00.  This | 
|  | *             time should be based off of | 
|  | *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. | 
|  | * @param format The Chronometer format string, or null to | 
|  | *               simply display the timer value. | 
|  | * @param started True if you want the clock to be started, false if not. | 
|  | * | 
|  | * @see #setChronometerCountDown(int, boolean) | 
|  | */ | 
|  | public void setChronometer(int viewId, long base, String format, boolean started) { | 
|  | setLong(viewId, "setBase", base); | 
|  | setString(viewId, "setFormat", format); | 
|  | setBoolean(viewId, "setStarted", started); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on | 
|  | * the chronometer with the given viewId. | 
|  | * | 
|  | * @param viewId The id of the {@link Chronometer} to change | 
|  | * @param isCountDown True if you want the chronometer to count down to base instead of | 
|  | *                    counting up. | 
|  | */ | 
|  | public void setChronometerCountDown(int viewId, boolean isCountDown) { | 
|  | setBoolean(viewId, "setCountDown", isCountDown); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, | 
|  | * {@link ProgressBar#setProgress ProgressBar.setProgress}, and | 
|  | * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} | 
|  | * | 
|  | * If indeterminate is true, then the values for max and progress are ignored. | 
|  | * | 
|  | * @param viewId The id of the {@link ProgressBar} to change | 
|  | * @param max The 100% value for the progress bar | 
|  | * @param progress The current value of the progress bar. | 
|  | * @param indeterminate True if the progress bar is indeterminate, | 
|  | *                false if not. | 
|  | */ | 
|  | public void setProgressBar(int viewId, int max, int progress, | 
|  | boolean indeterminate) { | 
|  | setBoolean(viewId, "setIndeterminate", indeterminate); | 
|  | if (!indeterminate) { | 
|  | setInt(viewId, "setMax", max); | 
|  | setInt(viewId, "setProgress", progress); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling | 
|  | * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} | 
|  | * to launch the provided {@link PendingIntent}. The source bounds | 
|  | * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked | 
|  | * view in screen space. | 
|  | * Note that any activity options associated with the pendingIntent may get overridden | 
|  | * before starting the intent. | 
|  | * | 
|  | * When setting the on-click action of items within collections (eg. {@link ListView}, | 
|  | * {@link StackView} etc.), this method will not work. Instead, use {@link | 
|  | * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with | 
|  | * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. | 
|  | * | 
|  | * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked | 
|  | * @param pendingIntent The {@link PendingIntent} to send when user clicks | 
|  | */ | 
|  | public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { | 
|  | addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very | 
|  | * costly to set PendingIntents on the individual items, and is hence not permitted. Instead | 
|  | * this method should be used to set a single PendingIntent template on the collection, and | 
|  | * individual items can differentiate their on-click behavior using | 
|  | * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. | 
|  | * | 
|  | * @param viewId The id of the collection who's children will use this PendingIntent template | 
|  | *          when clicked | 
|  | * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified | 
|  | *          by a child of viewId and executed when that child is clicked | 
|  | */ | 
|  | public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { | 
|  | addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very | 
|  | * costly to set PendingIntents on the individual items, and is hence not permitted. Instead | 
|  | * a single PendingIntent template can be set on the collection, see {@link | 
|  | * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click | 
|  | * action of a given item can be distinguished by setting a fillInIntent on that item. The | 
|  | * fillInIntent is then combined with the PendingIntent template in order to determine the final | 
|  | * intent which will be executed when the item is clicked. This works as follows: any fields | 
|  | * which are left blank in the PendingIntent template, but are provided by the fillInIntent | 
|  | * will be overwritten, and the resulting PendingIntent will be used. The rest | 
|  | * of the PendingIntent template will then be filled in with the associated fields that are | 
|  | * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. | 
|  | * | 
|  | * @param viewId The id of the view on which to set the fillInIntent | 
|  | * @param fillInIntent The intent which will be combined with the parent's PendingIntent | 
|  | *        in order to determine the on-click behavior of the view specified by viewId | 
|  | */ | 
|  | public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { | 
|  | addAction(new SetOnClickFillInIntent(viewId, fillInIntent)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @hide | 
|  | * Equivalent to calling | 
|  | * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, | 
|  | * on the {@link Drawable} of a given view. | 
|  | * <p> | 
|  | * | 
|  | * @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(int viewId, boolean targetBackground, | 
|  | int colorFilter, @NonNull PorterDuff.Mode mode) { | 
|  | addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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(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(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(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(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(int viewId, @ColorInt 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, 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(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 | 
|  | */ | 
|  | public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { | 
|  | addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. | 
|  | * | 
|  | * @param viewId The id of the view to change | 
|  | * @param position Scroll to this adapter position | 
|  | */ | 
|  | public void setScrollPosition(int viewId, int position) { | 
|  | setInt(viewId, "smoothScrollToPosition", position); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}. | 
|  | * | 
|  | * @param viewId The id of the view to change | 
|  | * @param offset Scroll by this adapter position offset | 
|  | */ | 
|  | public void setRelativeScrollPosition(int viewId, int offset) { | 
|  | setInt(viewId, "smoothScrollByOffset", offset); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. | 
|  | * | 
|  | * @param viewId The id of the view to change | 
|  | * @param left the left padding in pixels | 
|  | * @param top the top padding in pixels | 
|  | * @param right the right padding in pixels | 
|  | * @param bottom the bottom padding in pixels | 
|  | */ | 
|  | public void setViewPadding(int viewId, int left, int top, int right, int bottom) { | 
|  | addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @hide | 
|  | * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. | 
|  | * Only works if the {@link View#getLayoutParams()} supports margins. | 
|  | * Hidden for now since we don't want to support this for all different layout margins yet. | 
|  | * | 
|  | * @param viewId The id of the view to change | 
|  | * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin. | 
|  | */ | 
|  | public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) { | 
|  | addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN, | 
|  | endMarginDimen)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. | 
|  | * | 
|  | * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin. | 
|  | * @hide | 
|  | */ | 
|  | public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) { | 
|  | addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN, | 
|  | bottomMarginDimen)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. | 
|  | * | 
|  | * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed | 
|  | *                    because they behave poorly when the density changes. | 
|  | * @hide | 
|  | */ | 
|  | public void setViewLayoutWidth(int viewId, int layoutWidth) { | 
|  | if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT | 
|  | && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) { | 
|  | throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT"); | 
|  | } | 
|  | mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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(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(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(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(int viewId, String methodName, int value) { | 
|  | addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, 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(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(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(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(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(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(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(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 | 
|  | * <p class="note">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.</p> | 
|  | * | 
|  | * @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 setBitmap(int viewId, String methodName, Bitmap value) { | 
|  | addAction(new BitmapReflectionAction(viewId, methodName, value)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Call a method taking one Bundle 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 setBundle(int viewId, String methodName, Bundle value) { | 
|  | addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Call a method taking one Intent 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 {@link android.content.Intent} to pass the method. | 
|  | */ | 
|  | public void setIntent(int viewId, String methodName, Intent value) { | 
|  | addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Call a method taking one Icon 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 {@link android.graphics.drawable.Icon} to pass the method. | 
|  | */ | 
|  | public void setIcon(int viewId, String methodName, Icon value) { | 
|  | addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling View.setContentDescription(CharSequence). | 
|  | * | 
|  | * @param viewId The id of the view whose content description should change. | 
|  | * @param contentDescription The new content description for the view. | 
|  | */ | 
|  | public void setContentDescription(int viewId, CharSequence contentDescription) { | 
|  | setCharSequence(viewId, "setContentDescription", contentDescription); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. | 
|  | * | 
|  | * @param viewId The id of the view whose before view in accessibility traversal to set. | 
|  | * @param nextId The id of the next in the accessibility traversal. | 
|  | **/ | 
|  | public void setAccessibilityTraversalBefore(int viewId, int nextId) { | 
|  | setInt(viewId, "setAccessibilityTraversalBefore", nextId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. | 
|  | * | 
|  | * @param viewId The id of the view whose after view in accessibility traversal to set. | 
|  | * @param nextId The id of the next in the accessibility traversal. | 
|  | **/ | 
|  | public void setAccessibilityTraversalAfter(int viewId, int nextId) { | 
|  | setInt(viewId, "setAccessibilityTraversalAfter", nextId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Equivalent to calling {@link View#setLabelFor(int)}. | 
|  | * | 
|  | * @param viewId The id of the view whose property to set. | 
|  | * @param labeledId The id of a view for which this view serves as a label. | 
|  | */ | 
|  | public void setLabelFor(int viewId, int labeledId) { | 
|  | setInt(viewId, "setLabelFor", labeledId); | 
|  | } | 
|  |  | 
|  | private RemoteViews getRemoteViewsToApply(Context context) { | 
|  | if (hasLandscapeAndPortraitLayouts()) { | 
|  | int orientation = context.getResources().getConfiguration().orientation; | 
|  | if (orientation == Configuration.ORIENTATION_LANDSCAPE) { | 
|  | return mLandscape; | 
|  | } else { | 
|  | return mPortrait; | 
|  | } | 
|  | } | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Inflates the view hierarchy represented by this object and applies | 
|  | * all of the actions. | 
|  | * | 
|  | * <p><strong>Caller beware: this may throw</strong> | 
|  | * | 
|  | * @param context Default context to use | 
|  | * @param parent Parent that the resulting view hierarchy will be attached to. This method | 
|  | * does <strong>not</strong> 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); | 
|  | loadTransitionOverride(context, handler); | 
|  |  | 
|  | rvToApply.performApply(result, parent, handler); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { | 
|  | // 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); | 
|  |  | 
|  | 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(this); | 
|  | View v = inflater.inflate(rv.getLayoutId(), parent, false); | 
|  | v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); | 
|  | return v; | 
|  | } | 
|  |  | 
|  | private static void loadTransitionOverride(Context context, | 
|  | RemoteViews.OnClickHandler handler) { | 
|  | if (handler != null && context.getResources().getBoolean( | 
|  | com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { | 
|  | TypedArray windowStyle = context.getTheme().obtainStyledAttributes( | 
|  | com.android.internal.R.styleable.Window); | 
|  | int windowAnimations = windowStyle.getResourceId( | 
|  | com.android.internal.R.styleable.Window_windowAnimationStyle, 0); | 
|  | TypedArray windowAnimationStyle = context.obtainStyledAttributes( | 
|  | windowAnimations, com.android.internal.R.styleable.WindowAnimation); | 
|  | handler.setEnterAnimationId(windowAnimationStyle.getResourceId( | 
|  | com.android.internal.R.styleable. | 
|  | WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0)); | 
|  | windowStyle.recycle(); | 
|  | windowAnimationStyle.recycle(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implement this interface to receive a callback when | 
|  | * {@link #applyAsync} or {@link #reapplyAsync} is finished. | 
|  | * @hide | 
|  | */ | 
|  | public interface OnViewAppliedListener { | 
|  | 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 <strong>not</strong> 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); | 
|  | } | 
|  |  | 
|  | private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) { | 
|  | CancellationSignal cancelSignal = new CancellationSignal(); | 
|  | cancelSignal.setOnCancelListener(task); | 
|  |  | 
|  | task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor); | 
|  | return cancelSignal; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public CancellationSignal applyAsync(Context context, ViewGroup parent, | 
|  | Executor executor, OnViewAppliedListener listener, OnClickHandler handler) { | 
|  | return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), 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<Void, Void, ViewTree> | 
|  | implements CancellationSignal.OnCancelListener { | 
|  | final RemoteViews mRV; | 
|  | final ViewGroup mParent; | 
|  | final Context mContext; | 
|  | final OnViewAppliedListener mListener; | 
|  | final OnClickHandler mHandler; | 
|  |  | 
|  | private View mResult; | 
|  | private ViewTree mTree; | 
|  | private Action[] mActions; | 
|  | private Exception mError; | 
|  |  | 
|  | private AsyncApplyTask( | 
|  | RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, | 
|  | OnClickHandler handler, View result) { | 
|  | mRV = rv; | 
|  | mParent = parent; | 
|  | mContext = context; | 
|  | mListener = listener; | 
|  | mHandler = handler; | 
|  |  | 
|  | mResult = result; | 
|  | loadTransitionOverride(context, handler); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected ViewTree doInBackground(Void... params) { | 
|  | try { | 
|  | if (mResult == null) { | 
|  | mResult = inflateView(mContext, mRV, mParent); | 
|  | } | 
|  |  | 
|  | mTree = new ViewTree(mResult); | 
|  | if (mRV.mActions != null) { | 
|  | int count = mRV.mActions.size(); | 
|  | mActions = new Action[count]; | 
|  | for (int i = 0; i < count && !isCancelled(); i++) { | 
|  | // TODO: check if isCancelled in nested views. | 
|  | mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler); | 
|  | } | 
|  | } else { | 
|  | mActions = null; | 
|  | } | 
|  | return mTree; | 
|  | } catch (Exception e) { | 
|  | mError = e; | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onPostExecute(ViewTree viewTree) { | 
|  | if (mError == null) { | 
|  | try { | 
|  | if (mActions != null) { | 
|  | OnClickHandler handler = mHandler == null | 
|  | ? DEFAULT_ON_CLICK_HANDLER : mHandler; | 
|  | for (Action a : mActions) { | 
|  | a.apply(viewTree.mRoot, mParent, handler); | 
|  | } | 
|  | } | 
|  | } catch (Exception e) { | 
|  | mError = e; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (mListener != null) { | 
|  | if (mError != null) { | 
|  | mListener.onError(mError); | 
|  | } else { | 
|  | mListener.onViewApplied(viewTree.mRoot); | 
|  | } | 
|  | } else if (mError != null) { | 
|  | if (mError instanceof ActionException) { | 
|  | throw (ActionException) mError; | 
|  | } else { | 
|  | throw new ActionException(mError); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onCancel() { | 
|  | cancel(true); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Applies all of the actions to the provided view. | 
|  | * | 
|  | * <p><strong>Caller beware: this may throw</strong> | 
|  | * | 
|  | * @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 startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), | 
|  | context, listener, handler, v), 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(); | 
|  | } | 
|  |  | 
|  | /* (non-Javadoc) | 
|  | * Used to restrict the views which can be inflated | 
|  | * | 
|  | * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) | 
|  | */ | 
|  | 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(mIsWidgetCollectionChild ? 1 : 0); | 
|  | 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(mReapplyDisallowed ? 1 : 0); | 
|  | } | 
|  |  | 
|  | 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 Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { | 
|  | public RemoteViews createFromParcel(Parcel parcel) { | 
|  | return new RemoteViews(parcel); | 
|  | } | 
|  |  | 
|  | public RemoteViews[] newArray(int size) { | 
|  | return new RemoteViews[size]; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * A representation of the view hierarchy. Only views which have a valid ID are added | 
|  | * and can be searched. | 
|  | */ | 
|  | private static class ViewTree { | 
|  | private static final int INSERT_AT_END_INDEX = -1; | 
|  | private View mRoot; | 
|  | private ArrayList<ViewTree> mChildren; | 
|  |  | 
|  | private ViewTree(View root) { | 
|  | mRoot = root; | 
|  | } | 
|  |  | 
|  | public void createTree() { | 
|  | if (mChildren != null) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | mChildren = new ArrayList<>(); | 
|  | if (mRoot instanceof ViewGroup) { | 
|  | ViewGroup vg = (ViewGroup) mRoot; | 
|  | int count = vg.getChildCount(); | 
|  | for (int i = 0; i < count; i++) { | 
|  | addViewChild(vg.getChildAt(i)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public ViewTree findViewTreeById(int id) { | 
|  | if (mRoot.getId() == id) { | 
|  | return this; | 
|  | } | 
|  | if (mChildren == null) { | 
|  | return null; | 
|  | } | 
|  | for (ViewTree tree : mChildren) { | 
|  | ViewTree result = tree.findViewTreeById(id); | 
|  | if (result != null) { | 
|  | return result; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | public void replaceView(View v) { | 
|  | mRoot = v; | 
|  | mChildren = null; | 
|  | createTree(); | 
|  | } | 
|  |  | 
|  | public <T extends View> T findViewById(int id) { | 
|  | if (mChildren == null) { | 
|  | return mRoot.findViewById(id); | 
|  | } | 
|  | ViewTree tree = findViewTreeById(id); | 
|  | return tree == null ? null : (T) tree.mRoot; | 
|  | } | 
|  |  | 
|  | public void addChild(ViewTree child) { | 
|  | addChild(child, INSERT_AT_END_INDEX); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds the given {@link ViewTree} as a child at the given index. | 
|  | * | 
|  | * @param index The position at which to add the child or -1 to add last. | 
|  | */ | 
|  | public void addChild(ViewTree child, int index) { | 
|  | if (mChildren == null) { | 
|  | mChildren = new ArrayList<>(); | 
|  | } | 
|  | child.createTree(); | 
|  |  | 
|  | if (index == INSERT_AT_END_INDEX) { | 
|  | mChildren.add(child); | 
|  | return; | 
|  | } | 
|  |  | 
|  | mChildren.add(index, child); | 
|  | } | 
|  |  | 
|  | private void addViewChild(View v) { | 
|  | // ViewTree only contains Views which can be found using findViewById. | 
|  | // If isRootNamespace is true, this view is skipped. | 
|  | // @see ViewGroup#findViewTraversal(int) | 
|  | if (v.isRootNamespace()) { | 
|  | return; | 
|  | } | 
|  | final ViewTree target; | 
|  |  | 
|  | // If the view has a valid id, i.e., if can be found using findViewById, add it to the | 
|  | // tree, otherwise skip this view and add its children instead. | 
|  | if (v.getId() != 0) { | 
|  | ViewTree tree = new ViewTree(v); | 
|  | mChildren.add(tree); | 
|  | target = tree; | 
|  | } else { | 
|  | target = this; | 
|  | } | 
|  |  | 
|  | if (v instanceof ViewGroup) { | 
|  | if (target.mChildren == null) { | 
|  | target.mChildren = new ArrayList<>(); | 
|  | ViewGroup vg = (ViewGroup) v; | 
|  | int count = vg.getChildCount(); | 
|  | for (int i = 0; i < count; i++) { | 
|  | target.addViewChild(vg.getChildAt(i)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |