• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.appwidget;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.Activity;
22 import android.app.ActivityOptions;
23 import android.app.LoadedApk;
24 import android.compat.annotation.UnsupportedAppUsage;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.ContextWrapper;
28 import android.content.Intent;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.LauncherActivityInfo;
31 import android.content.pm.LauncherApps;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.res.Resources;
34 import android.graphics.Canvas;
35 import android.graphics.Color;
36 import android.graphics.PointF;
37 import android.graphics.Rect;
38 import android.os.Build;
39 import android.os.Bundle;
40 import android.os.CancellationSignal;
41 import android.os.Parcelable;
42 import android.util.AttributeSet;
43 import android.util.Log;
44 import android.util.Pair;
45 import android.util.SizeF;
46 import android.util.SparseArray;
47 import android.util.SparseIntArray;
48 import android.view.Gravity;
49 import android.view.LayoutInflater;
50 import android.view.View;
51 import android.view.accessibility.AccessibilityNodeInfo;
52 import android.widget.Adapter;
53 import android.widget.AdapterView;
54 import android.widget.BaseAdapter;
55 import android.widget.FrameLayout;
56 import android.widget.RemoteViews;
57 import android.widget.RemoteViews.InteractionHandler;
58 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
59 import android.widget.TextView;
60 
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.concurrent.Executor;
64 
65 /**
66  * Provides the glue to show AppWidget views. This class offers automatic animation
67  * between updates, and will try recycling old views for each incoming
68  * {@link RemoteViews}.
69  */
70 public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppWidgetHostListener {
71 
72     static final String TAG = "AppWidgetHostView";
73     private static final String KEY_JAILED_ARRAY = "jail";
74     private static final String KEY_INFLATION_ID = "inflation_id";
75 
76     static final boolean LOGD = false;
77 
78     static final int VIEW_MODE_NOINIT = 0;
79     static final int VIEW_MODE_CONTENT = 1;
80     static final int VIEW_MODE_ERROR = 2;
81     static final int VIEW_MODE_DEFAULT = 3;
82 
83     // Set of valid colors resources.
84     private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
85     private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
86 
87     // When we're inflating the initialLayout for a AppWidget, we only allow
88     // views that are allowed in RemoteViews.
89     private static final LayoutInflater.Filter INFLATER_FILTER =
90             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
91 
92     Context mContext;
93     Context mRemoteContext;
94 
95     @UnsupportedAppUsage
96     int mAppWidgetId;
97     @UnsupportedAppUsage
98     AppWidgetProviderInfo mInfo;
99     View mView;
100     int mViewMode = VIEW_MODE_NOINIT;
101     // If true, we should not try to re-apply the RemoteViews on the next inflation.
102     boolean mColorMappingChanged = false;
103     private InteractionHandler mInteractionHandler;
104     private boolean mOnLightBackground;
105     private SizeF mCurrentSize = null;
106     private RemoteViews.ColorResources mColorResources = null;
107     // Stores the last remote views last inflated.
108     private RemoteViews mLastInflatedRemoteViews = null;
109     private long mLastInflatedRemoteViewsId = -1;
110 
111     private Executor mAsyncExecutor;
112     private CancellationSignal mLastExecutionSignal;
113     private SparseArray<Parcelable> mDelayedRestoredState;
114     private long mDelayedRestoredInflationId;
115 
116     /**
117      * Create a host view.  Uses default fade animations.
118      */
AppWidgetHostView(Context context)119     public AppWidgetHostView(Context context) {
120         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
121     }
122 
123     /**
124      * @hide
125      */
AppWidgetHostView(Context context, InteractionHandler handler)126     public AppWidgetHostView(Context context, InteractionHandler handler) {
127         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
128         mInteractionHandler = getHandler(handler);
129     }
130 
131     /**
132      * Create a host view. Uses specified animations when pushing
133      * {@link #updateAppWidget(RemoteViews)}.
134      *
135      * @param animationIn Resource ID of in animation to use
136      * @param animationOut Resource ID of out animation to use
137      */
138     @SuppressWarnings({"UnusedDeclaration"})
AppWidgetHostView(Context context, int animationIn, int animationOut)139     public AppWidgetHostView(Context context, int animationIn, int animationOut) {
140         super(context);
141         mContext = context;
142         // We want to segregate the view ids within AppWidgets to prevent
143         // problems when those ids collide with view ids in the AppWidgetHost.
144         setIsRootNamespace(true);
145     }
146 
147     /**
148      * Pass the given handler to RemoteViews when updating this widget. Unless this
149      * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
150      * should be made.
151      * @param handler
152      * @hide
153      */
setInteractionHandler(InteractionHandler handler)154     public void setInteractionHandler(InteractionHandler handler) {
155         mInteractionHandler = getHandler(handler);
156     }
157 
158     /**
159      * Set the AppWidget that will be displayed by this view. This method also adds default padding
160      * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
161      * and can be overridden in order to add custom padding.
162      */
setAppWidget(int appWidgetId, AppWidgetProviderInfo info)163     public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
164         mAppWidgetId = appWidgetId;
165         mInfo = info;
166 
167         // We add padding to the AppWidgetHostView if necessary
168         Rect padding = getDefaultPadding();
169         setPadding(padding.left, padding.top, padding.right, padding.bottom);
170 
171         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
172         // a widget, eg. for some widgets in safe mode.
173         if (info != null) {
174             String description = info.loadLabel(getContext().getPackageManager());
175             if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
176                 description = Resources.getSystem().getString(
177                         com.android.internal.R.string.suspended_widget_accessibility, description);
178             }
179             setContentDescription(description);
180         }
181     }
182 
183     /**
184      * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
185      * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
186      * that widget developers do not add extra padding to their widgets. This will help
187      * achieve consistency among widgets.
188      *
189      * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in
190      * order for the AppWidgetHost to account for the automatic padding when computing the number
191      * of cells to allocate to a particular widget.
192      *
193      * @param context the current context
194      * @param component the component name of the widget
195      * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
196      *                returned
197      * @return default padding for this widget, in pixels
198      */
getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)199     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
200             Rect padding) {
201         return getDefaultPaddingForWidget(context, padding);
202     }
203 
getDefaultPaddingForWidget(Context context, Rect padding)204     private static Rect getDefaultPaddingForWidget(Context context, Rect padding) {
205         if (padding == null) {
206             padding = new Rect(0, 0, 0, 0);
207         } else {
208             padding.set(0, 0, 0, 0);
209         }
210         Resources r = context.getResources();
211         padding.left = r.getDimensionPixelSize(
212                 com.android.internal.R.dimen.default_app_widget_padding_left);
213         padding.right = r.getDimensionPixelSize(
214                 com.android.internal.R.dimen.default_app_widget_padding_right);
215         padding.top = r.getDimensionPixelSize(
216                 com.android.internal.R.dimen.default_app_widget_padding_top);
217         padding.bottom = r.getDimensionPixelSize(
218                 com.android.internal.R.dimen.default_app_widget_padding_bottom);
219         return padding;
220     }
221 
getDefaultPadding()222     private Rect getDefaultPadding() {
223         return getDefaultPaddingForWidget(mContext, null);
224     }
225 
getAppWidgetId()226     public int getAppWidgetId() {
227         return mAppWidgetId;
228     }
229 
getAppWidgetInfo()230     public AppWidgetProviderInfo getAppWidgetInfo() {
231         return mInfo;
232     }
233 
234     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)235     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
236         final SparseArray<Parcelable> jail = new SparseArray<>();
237         super.dispatchSaveInstanceState(jail);
238 
239         Bundle bundle = new Bundle();
240         bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
241         bundle.putLong(KEY_INFLATION_ID, mLastInflatedRemoteViewsId);
242         container.put(generateId(), bundle);
243         container.put(generateId(), bundle);
244     }
245 
generateId()246     private int generateId() {
247         final int id = getId();
248         return id == View.NO_ID ? mAppWidgetId : id;
249     }
250 
251     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)252     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
253         final Parcelable parcelable = container.get(generateId());
254 
255         SparseArray<Parcelable> jail = null;
256         long inflationId = -1;
257         if (parcelable instanceof Bundle) {
258             Bundle bundle = (Bundle) parcelable;
259             jail = bundle.getSparseParcelableArray(KEY_JAILED_ARRAY);
260             inflationId = bundle.getLong(KEY_INFLATION_ID, -1);
261         }
262 
263         if (jail == null) jail = new SparseArray<>();
264 
265         mDelayedRestoredState = jail;
266         mDelayedRestoredInflationId = inflationId;
267         restoreInstanceState();
268     }
269 
restoreInstanceState()270     void restoreInstanceState() {
271         long inflationId = mDelayedRestoredInflationId;
272         SparseArray<Parcelable> state = mDelayedRestoredState;
273         if (inflationId == -1 || inflationId != mLastInflatedRemoteViewsId) {
274             return; // We don't restore.
275         }
276         mDelayedRestoredInflationId = -1;
277         mDelayedRestoredState = null;
278         try  {
279             super.dispatchRestoreInstanceState(state);
280         } catch (Exception e) {
281             Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
282                     + (mInfo == null ? "null" : mInfo.provider), e);
283         }
284     }
285 
computeSizeFromLayout(int left, int top, int right, int bottom)286     private SizeF computeSizeFromLayout(int left, int top, int right, int bottom) {
287         float density = getResources().getDisplayMetrics().density;
288         return new SizeF(
289                 (right - left - getPaddingLeft() - getPaddingRight()) / density,
290                 (bottom - top - getPaddingTop() - getPaddingBottom()) / density
291         );
292     }
293 
294     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)295     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
296         try {
297             SizeF oldSize = mCurrentSize;
298             SizeF newSize = computeSizeFromLayout(left, top, right, bottom);
299             mCurrentSize = newSize;
300             if (mLastInflatedRemoteViews != null) {
301                 RemoteViews toApply = mLastInflatedRemoteViews.getRemoteViewsToApplyIfDifferent(
302                         oldSize, newSize);
303                 if (toApply != null) {
304                     applyRemoteViews(toApply, false);
305                     measureChildWithMargins(mView,
306                             MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
307                             0 /* widthUsed */,
308                             MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY),
309                             0 /* heightUsed */);
310                 }
311             }
312             super.onLayout(changed, left, top, right, bottom);
313         } catch (final RuntimeException e) {
314             Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e);
315             handleViewError();
316         }
317     }
318 
319     /**
320      * Remove bad view and replace with error message view
321      */
handleViewError()322     private void handleViewError() {
323         removeViewInLayout(mView);
324         View child = getErrorView();
325         prepareView(child);
326         addViewInLayout(child, 0, child.getLayoutParams());
327         measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
328                 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
329         child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
330                 child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
331         mView = child;
332         mViewMode = VIEW_MODE_ERROR;
333     }
334 
335     /**
336      * Provide guidance about the size of this widget to the AppWidgetManager. The widths and
337      * heights should correspond to the full area the AppWidgetHostView is given. Padding added by
338      * the framework will be accounted for automatically. This information gets embedded into the
339      * AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of
340      * sizes is explicitly set to an empty list.
341      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
342      *
343      * @param newOptions The bundle of options, in addition to the size information,
344      *          can be null.
345      * @param minWidth The minimum width in dips that the widget will be displayed at.
346      * @param minHeight The maximum height in dips that the widget will be displayed at.
347      * @param maxWidth The maximum width in dips that the widget will be displayed at.
348      * @param maxHeight The maximum height in dips that the widget will be displayed at.
349      * @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead.
350      */
351     @Deprecated
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)352     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
353             int maxHeight) {
354         updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
355     }
356 
357     /**
358      * Provide guidance about the size of this widget to the AppWidgetManager. The sizes should
359      * correspond to the full area the AppWidgetHostView is given. Padding added by the framework
360      * will be accounted for automatically.
361      *
362      * This method will update the option bundle with the list of sizes and the min/max bounds for
363      * width and height.
364      *
365      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
366      *
367      * @param newOptions The bundle of options, in addition to the size information.
368      * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider
369      *              again. Typically, this will be size of the widget in landscape and portrait.
370      *              On some foldables, this might include the size on the outer and inner screens.
371      */
updateAppWidgetSize(@onNull Bundle newOptions, @NonNull List<SizeF> sizes)372     public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<SizeF> sizes) {
373         AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
374 
375         Rect padding = getDefaultPadding();
376         float density = getResources().getDisplayMetrics().density;
377 
378         float xPaddingDips = (padding.left + padding.right) / density;
379         float yPaddingDips = (padding.top + padding.bottom) / density;
380 
381         ArrayList<SizeF> paddedSizes = new ArrayList<>(sizes.size());
382         float minWidth = Float.MAX_VALUE;
383         float maxWidth = 0;
384         float minHeight = Float.MAX_VALUE;
385         float maxHeight = 0;
386         for (int i = 0; i < sizes.size(); i++) {
387             SizeF size = sizes.get(i);
388             SizeF paddedSize = new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips),
389                     Math.max(0.f, size.getHeight() - yPaddingDips));
390             paddedSizes.add(paddedSize);
391             minWidth = Math.min(minWidth, paddedSize.getWidth());
392             maxWidth = Math.max(maxWidth, paddedSize.getWidth());
393             minHeight = Math.min(minHeight, paddedSize.getHeight());
394             maxHeight = Math.max(maxHeight, paddedSize.getHeight());
395         }
396         if (paddedSizes.equals(
397                 widgetManager.getAppWidgetOptions(mAppWidgetId).<SizeF>getParcelableArrayList(
398                         AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
399             return;
400         }
401         Bundle options = newOptions.deepCopy();
402         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth);
403         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight);
404         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth);
405         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight);
406         options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
407         updateAppWidgetOptions(options);
408     }
409 
410     /**
411      * @hide
412      */
413     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)414     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
415             int maxHeight, boolean ignorePadding) {
416         if (newOptions == null) {
417             newOptions = new Bundle();
418         }
419 
420         Rect padding = getDefaultPadding();
421         float density = getResources().getDisplayMetrics().density;
422 
423         int xPaddingDips = (int) ((padding.left + padding.right) / density);
424         int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
425 
426         int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips);
427         int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips);
428         int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips);
429         int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips);
430 
431         AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
432 
433         // We get the old options to see if the sizes have changed
434         Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId);
435         boolean needsUpdate = false;
436         if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) ||
437                 newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) ||
438                 newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) ||
439                 newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) {
440             needsUpdate = true;
441         }
442 
443         if (needsUpdate) {
444             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth);
445             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
446             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
447             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
448             newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
449                     new ArrayList<PointF>());
450             updateAppWidgetOptions(newOptions);
451         }
452     }
453 
454     /**
455      * Specify some extra information for the widget provider. Causes a callback to the
456      * AppWidgetProvider.
457      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
458      *
459      * @param options The bundle of options information.
460      */
updateAppWidgetOptions(Bundle options)461     public void updateAppWidgetOptions(Bundle options) {
462         AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
463     }
464 
465     /** {@inheritDoc} */
466     @Override
generateLayoutParams(AttributeSet attrs)467     public LayoutParams generateLayoutParams(AttributeSet attrs) {
468         // We're being asked to inflate parameters, probably by a LayoutInflater
469         // in a remote Context. To help resolve any remote references, we
470         // inflate through our last mRemoteContext when it exists.
471         final Context context = mRemoteContext != null ? mRemoteContext : mContext;
472         return new FrameLayout.LayoutParams(context, attrs);
473     }
474 
475     /**
476      * Sets an executor which can be used for asynchronously inflating. CPU intensive tasks like
477      * view inflation or loading images will be performed on the executor. The updates will still
478      * be applied on the UI thread.
479      *
480      * @param executor the executor to use or null.
481      */
setExecutor(Executor executor)482     public void setExecutor(Executor executor) {
483         if (mLastExecutionSignal != null) {
484             mLastExecutionSignal.cancel();
485             mLastExecutionSignal = null;
486         }
487 
488         mAsyncExecutor = executor;
489     }
490 
491     /**
492      * Sets whether the widget is being displayed on a light/white background and use an
493      * alternate UI if available.
494      * @see RemoteViews#setLightBackgroundLayoutId(int)
495      */
setOnLightBackground(boolean onLightBackground)496     public void setOnLightBackground(boolean onLightBackground) {
497         mOnLightBackground = onLightBackground;
498     }
499 
500     /**
501      * Update the AppWidgetProviderInfo for this view, and reset it to the
502      * initial layout.
503      *
504      * @hide
505      */
506     @Override
onUpdateProviderInfo(@ullable AppWidgetProviderInfo info)507     public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
508         setAppWidget(mAppWidgetId, info);
509         mViewMode = VIEW_MODE_NOINIT;
510         updateAppWidget(null);
511     }
512 
513     /**
514      * Process a set of {@link RemoteViews} coming in as an update from the
515      * AppWidget provider. Will animate into these new views as needed
516      */
517     @Override
updateAppWidget(RemoteViews remoteViews)518     public void updateAppWidget(RemoteViews remoteViews) {
519         mLastInflatedRemoteViews = remoteViews;
520         applyRemoteViews(remoteViews, true);
521     }
522 
523     /**
524      * Reapply the last inflated remote views, or the default view is none was inflated.
525      */
reapplyLastRemoteViews()526     private void reapplyLastRemoteViews() {
527         SparseArray<Parcelable> savedState = new SparseArray<>();
528         saveHierarchyState(savedState);
529         applyRemoteViews(mLastInflatedRemoteViews, true);
530         restoreHierarchyState(savedState);
531     }
532 
533     /**
534      * @hide
535      */
applyRemoteViews(@ullable RemoteViews remoteViews, boolean useAsyncIfPossible)536     protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) {
537         boolean recycled = false;
538         View content = null;
539         Exception exception = null;
540 
541         // Block state restore until the end of the apply.
542         mLastInflatedRemoteViewsId = -1;
543 
544         if (mLastExecutionSignal != null) {
545             mLastExecutionSignal.cancel();
546             mLastExecutionSignal = null;
547         }
548 
549         if (remoteViews == null) {
550             if (mViewMode == VIEW_MODE_DEFAULT) {
551                 // We've already done this -- nothing to do.
552                 return;
553             }
554             content = getDefaultView();
555             mViewMode = VIEW_MODE_DEFAULT;
556         } else {
557             // Select the remote view we are actually going to apply.
558             RemoteViews rvToApply = remoteViews.getRemoteViewsToApply(mContext, mCurrentSize);
559             if (mOnLightBackground) {
560                 rvToApply = rvToApply.getDarkTextViews();
561             }
562 
563             if (mAsyncExecutor != null && useAsyncIfPossible) {
564                 inflateAsync(rvToApply);
565                 return;
566             }
567             // Prepare a local reference to the remote Context so we're ready to
568             // inflate any requested LayoutParams.
569             mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
570 
571             if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) {
572                 try {
573                     rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
574                             mColorResources);
575                     content = mView;
576                     mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
577                     recycled = true;
578                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
579                 } catch (RuntimeException e) {
580                     exception = e;
581                 }
582             }
583 
584             // Try normal RemoteView inflation
585             if (content == null) {
586                 try {
587                     content = rvToApply.apply(mContext, this, mInteractionHandler,
588                             mCurrentSize, mColorResources);
589                     mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
590                     if (LOGD) Log.d(TAG, "had to inflate new layout");
591                 } catch (RuntimeException e) {
592                     exception = e;
593                 }
594             }
595 
596             mViewMode = VIEW_MODE_CONTENT;
597         }
598 
599         applyContent(content, recycled, exception);
600     }
601 
applyContent(View content, boolean recycled, Exception exception)602     private void applyContent(View content, boolean recycled, Exception exception) {
603         mColorMappingChanged = false;
604         if (content == null) {
605             if (mViewMode == VIEW_MODE_ERROR) {
606                 // We've already done this -- nothing to do.
607                 return ;
608             }
609             if (exception != null) {
610                 Log.w(TAG, "Error inflating RemoteViews", exception);
611             }
612             content = getErrorView();
613             mViewMode = VIEW_MODE_ERROR;
614         }
615 
616         if (!recycled) {
617             prepareView(content);
618             addView(content);
619         }
620 
621         if (mView != content) {
622             removeView(mView);
623             mView = content;
624         }
625     }
626 
inflateAsync(@onNull RemoteViews remoteViews)627     private void inflateAsync(@NonNull RemoteViews remoteViews) {
628         // Prepare a local reference to the remote Context so we're ready to
629         // inflate any requested LayoutParams.
630         mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
631         int layoutId = remoteViews.getLayoutId();
632 
633         if (mLastExecutionSignal != null) {
634             mLastExecutionSignal.cancel();
635         }
636 
637         // If our stale view has been prepared to match active, and the new
638         // layout matches, try recycling it
639         if (!mColorMappingChanged && remoteViews.canRecycleView(mView)) {
640             try {
641                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
642                         mView,
643                         mAsyncExecutor,
644                         new ViewApplyListener(remoteViews, layoutId, true),
645                         mInteractionHandler,
646                         mCurrentSize,
647                         mColorResources);
648             } catch (Exception e) {
649                 // Reapply failed. Try apply
650             }
651         }
652         if (mLastExecutionSignal == null) {
653             mLastExecutionSignal = remoteViews.applyAsync(mContext,
654                     this,
655                     mAsyncExecutor,
656                     new ViewApplyListener(remoteViews, layoutId, false),
657                     mInteractionHandler,
658                     mCurrentSize,
659                     mColorResources);
660         }
661     }
662 
663     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
664         private final RemoteViews mViews;
665         private final boolean mIsReapply;
666         private final int mLayoutId;
667 
ViewApplyListener( RemoteViews views, int layoutId, boolean isReapply)668         ViewApplyListener(
669                 RemoteViews views,
670                 int layoutId,
671                 boolean isReapply) {
672             mViews = views;
673             mLayoutId = layoutId;
674             mIsReapply = isReapply;
675         }
676 
677         @Override
onViewApplied(View v)678         public void onViewApplied(View v) {
679             mViewMode = VIEW_MODE_CONTENT;
680 
681             applyContent(v, mIsReapply, null);
682 
683             mLastInflatedRemoteViewsId = mViews.computeUniqueId(mLastInflatedRemoteViews);
684             restoreInstanceState();
685             mLastExecutionSignal = null;
686         }
687 
688         @Override
onError(Exception e)689         public void onError(Exception e) {
690             if (mIsReapply) {
691                 // Try a fresh replay
692                 mLastExecutionSignal = mViews.applyAsync(mContext,
693                         AppWidgetHostView.this,
694                         mAsyncExecutor,
695                         new ViewApplyListener(mViews, mLayoutId, false),
696                         mInteractionHandler,
697                         mCurrentSize);
698             } else {
699                 applyContent(null, false, e);
700             }
701             mLastExecutionSignal = null;
702         }
703     }
704 
705     /**
706      * Process data-changed notifications for the specified view in the specified
707      * set of {@link RemoteViews} views.
708      *
709      * @hide
710      */
711     @Override
onViewDataChanged(int viewId)712     public void onViewDataChanged(int viewId) {
713         View v = findViewById(viewId);
714         if ((v != null) && (v instanceof AdapterView<?>)) {
715             AdapterView<?> adapterView = (AdapterView<?>) v;
716             Adapter adapter = adapterView.getAdapter();
717             if (adapter instanceof BaseAdapter) {
718                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
719                 baseAdapter.notifyDataSetChanged();
720             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
721                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
722                 // connected to its associated service, and hence the adapter hasn't been set.
723                 // In this case, we need to defer the notify call until it has been set.
724                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
725             }
726         }
727     }
728 
729     /**
730      * Build a {@link Context} cloned into another package name, usually for the
731      * purposes of reading remote resources.
732      * @hide
733      */
getRemoteContextEnsuringCorrectCachedApkPath()734     protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
735         try {
736             ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo;
737             LoadedApk.checkAndUpdateApkPaths(expectedAppInfo);
738             // Return if cloned successfully, otherwise default
739             Context newContext = mContext.createApplicationContext(
740                     mInfo.providerInfo.applicationInfo,
741                     Context.CONTEXT_RESTRICTED);
742             if (mColorResources != null) {
743                 mColorResources.apply(newContext);
744             }
745             return newContext;
746         } catch (NameNotFoundException e) {
747             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
748             return mContext;
749         } catch (NullPointerException e) {
750             Log.e(TAG, "Error trying to create the remote context.", e);
751             return mContext;
752         }
753     }
754 
755     /**
756      * Prepare the given view to be shown. This might include adjusting
757      * {@link FrameLayout.LayoutParams} before inserting.
758      */
prepareView(View view)759     protected void prepareView(View view) {
760         // Take requested dimensions from child, but apply default gravity.
761         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
762         if (requested == null) {
763             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
764                     LayoutParams.MATCH_PARENT);
765         }
766 
767         requested.gravity = Gravity.CENTER;
768         view.setLayoutParams(requested);
769     }
770 
771     /**
772      * Inflate and return the default layout requested by AppWidget provider.
773      */
getDefaultView()774     protected View getDefaultView() {
775         if (LOGD) {
776             Log.d(TAG, "getDefaultView");
777         }
778         View defaultView = null;
779         Exception exception = null;
780 
781         try {
782             if (mInfo != null) {
783                 Context theirContext = getRemoteContextEnsuringCorrectCachedApkPath();
784                 mRemoteContext = theirContext;
785                 LayoutInflater inflater = (LayoutInflater)
786                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
787                 inflater = inflater.cloneInContext(theirContext);
788                 inflater.setFilter(INFLATER_FILTER);
789                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
790                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
791 
792                 int layoutId = mInfo.initialLayout;
793                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
794                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
795                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
796                         int kgLayoutId = mInfo.initialKeyguardLayout;
797                         // If a default keyguard layout is not specified, use the standard
798                         // default layout.
799                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
800                     }
801                 }
802                 defaultView = inflater.inflate(layoutId, this, false);
803                 if (!(defaultView instanceof AdapterView)) {
804                     // AdapterView does not support onClickListener
805                     defaultView.setOnClickListener(this::onDefaultViewClicked);
806                 }
807             } else {
808                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
809             }
810         } catch (RuntimeException e) {
811             exception = e;
812         }
813 
814         if (exception != null) {
815             Log.w(TAG, "Error inflating AppWidget " + mInfo, exception);
816         }
817 
818         if (defaultView == null) {
819             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
820             defaultView = getErrorView();
821         }
822 
823         return defaultView;
824     }
825 
onDefaultViewClicked(View view)826     private void onDefaultViewClicked(View view) {
827         if (mInfo != null) {
828             LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
829             List<LauncherActivityInfo> activities = launcherApps.getActivityList(
830                     mInfo.provider.getPackageName(), mInfo.getProfile());
831             if (!activities.isEmpty()) {
832                 LauncherActivityInfo ai = activities.get(0);
833                 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(),
834                         RemoteViews.getSourceBounds(view), null);
835             }
836         }
837     }
838 
839     /**
840      * Inflate and return a view that represents an error state.
841      */
getErrorView()842     protected View getErrorView() {
843         TextView tv = new TextView(mContext);
844         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
845         // TODO: get this color from somewhere.
846         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
847         return tv;
848     }
849 
850     /** @hide */
851     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)852     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
853         super.onInitializeAccessibilityNodeInfoInternal(info);
854         info.setClassName(AppWidgetHostView.class.getName());
855     }
856 
857     /** @hide */
createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)858     public ActivityOptions createSharedElementActivityOptions(
859             int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) {
860         Context parentContext = getContext();
861         while ((parentContext instanceof ContextWrapper)
862                 && !(parentContext instanceof Activity)) {
863             parentContext = ((ContextWrapper) parentContext).getBaseContext();
864         }
865         if (!(parentContext instanceof Activity)) {
866             return null;
867         }
868 
869         List<Pair<View, String>> sharedElements = new ArrayList<>();
870         Bundle extras = new Bundle();
871 
872         for (int i = 0; i < sharedViewIds.length; i++) {
873             View view = findViewById(sharedViewIds[i]);
874             if (view != null) {
875                 sharedElements.add(Pair.create(view, sharedViewNames[i]));
876 
877                 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view));
878             }
879         }
880 
881         if (!sharedElements.isEmpty()) {
882             fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras);
883             final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
884                     (Activity) parentContext,
885                     sharedElements.toArray(new Pair[sharedElements.size()]));
886             opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
887             return opts;
888         }
889         return null;
890     }
891 
getHandler(InteractionHandler handler)892     private InteractionHandler getHandler(InteractionHandler handler) {
893         return (view, pendingIntent, response) -> {
894             AppWidgetManager.getInstance(mContext).noteAppWidgetTapped(mAppWidgetId);
895             if (handler != null) {
896                 return handler.onInteraction(view, pendingIntent, response);
897             } else {
898                 return RemoteViews.startPendingIntent(view, pendingIntent,
899                         response.getLaunchOptions(view));
900             }
901         };
902     }
903 
904     /**
905      * Set the dynamically overloaded color resources.
906      *
907      * {@code colorMapping} maps a predefined set of color resources to their ARGB
908      * representation. Any entry not in the predefined set of colors will be ignored.
909      *
910      * Calling this method will trigger a full re-inflation of the App Widget.
911      *
912      * The color resources that can be overloaded are the ones whose name is prefixed with
913      * {@code system_neutral} or {@code system_accent}, for example
914      * {@link android.R.color#system_neutral1_500}.
915      */
setColorResources(@onNull SparseIntArray colorMapping)916     public void setColorResources(@NonNull SparseIntArray colorMapping) {
917         if (mColorResources != null
918                 && isSameColorMapping(mColorResources.getColorMapping(), colorMapping)) {
919             return;
920         }
921         setColorResources(RemoteViews.ColorResources.create(mContext, colorMapping));
922     }
923 
924     /** @hide **/
setColorResources(RemoteViews.ColorResources colorResources)925     public void setColorResources(RemoteViews.ColorResources colorResources) {
926         if (colorResources == mColorResources) {
927             return;
928         }
929         mColorResources = colorResources;
930         mColorMappingChanged = true;
931         mViewMode = VIEW_MODE_NOINIT;
932         reapplyLastRemoteViews();
933     }
934 
935     /** Check if, in the current context, the two color mappings are equivalent. */
isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors)936     private boolean isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors) {
937         if (oldColors.size() != newColors.size()) {
938             return false;
939         }
940         for (int i = 0; i < oldColors.size(); i++) {
941             if (oldColors.keyAt(i) != newColors.keyAt(i)
942                     || oldColors.valueAt(i) != newColors.valueAt(i)) {
943                 return false;
944             }
945         }
946         return true;
947     }
948 
949     /**
950      * Reset the dynamically overloaded resources, reverting to the default values for
951      * all the colors.
952      *
953      * If colors were defined before, calling this method will trigger a full re-inflation of the
954      * App Widget.
955      */
resetColorResources()956     public void resetColorResources() {
957         if (mColorResources != null) {
958             mColorResources = null;
959             mColorMappingChanged = true;
960             mViewMode = VIEW_MODE_NOINIT;
961             reapplyLastRemoteViews();
962         }
963     }
964 
965     @Override
dispatchDraw(@onNull Canvas canvas)966     protected void dispatchDraw(@NonNull Canvas canvas) {
967         try {
968             super.dispatchDraw(canvas);
969         } catch (Exception e) {
970             // Catch draw exceptions that may be caused by RemoteViews
971             Log.e(TAG, "Drawing view failed: " + e);
972             post(this::handleViewError);
973         }
974     }
975 }
976