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.Color;
35 import android.graphics.PointF;
36 import android.graphics.Rect;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.os.CancellationSignal;
40 import android.os.Parcelable;
41 import android.util.AttributeSet;
42 import android.util.Log;
43 import android.util.Pair;
44 import android.util.SizeF;
45 import android.util.SparseArray;
46 import android.util.SparseIntArray;
47 import android.view.Gravity;
48 import android.view.LayoutInflater;
49 import android.view.View;
50 import android.view.accessibility.AccessibilityNodeInfo;
51 import android.widget.Adapter;
52 import android.widget.AdapterView;
53 import android.widget.BaseAdapter;
54 import android.widget.FrameLayout;
55 import android.widget.RemoteViews;
56 import android.widget.RemoteViews.InteractionHandler;
57 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
58 import android.widget.TextView;
59 
60 import java.util.ArrayList;
61 import java.util.List;
62 import java.util.concurrent.Executor;
63 
64 /**
65  * Provides the glue to show AppWidget views. This class offers automatic animation
66  * between updates, and will try recycling old views for each incoming
67  * {@link RemoteViews}.
68  */
69 public class AppWidgetHostView extends FrameLayout {
70 
71     static final String TAG = "AppWidgetHostView";
72     private static final String KEY_JAILED_ARRAY = "jail";
73     private static final String KEY_INFLATION_ID = "inflation_id";
74 
75     static final boolean LOGD = false;
76 
77     static final int VIEW_MODE_NOINIT = 0;
78     static final int VIEW_MODE_CONTENT = 1;
79     static final int VIEW_MODE_ERROR = 2;
80     static final int VIEW_MODE_DEFAULT = 3;
81 
82     // Set of valid colors resources.
83     private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
84     private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
85 
86     // When we're inflating the initialLayout for a AppWidget, we only allow
87     // views that are allowed in RemoteViews.
88     private static final LayoutInflater.Filter INFLATER_FILTER =
89             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
90 
91     Context mContext;
92     Context mRemoteContext;
93 
94     @UnsupportedAppUsage
95     int mAppWidgetId;
96     @UnsupportedAppUsage
97     AppWidgetProviderInfo mInfo;
98     View mView;
99     int mViewMode = VIEW_MODE_NOINIT;
100     // If true, we should not try to re-apply the RemoteViews on the next inflation.
101     boolean mColorMappingChanged = false;
102     private InteractionHandler mInteractionHandler;
103     private boolean mOnLightBackground;
104     private SizeF mCurrentSize = null;
105     private RemoteViews.ColorResources mColorResources = null;
106     private SparseIntArray mColorMapping = 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             removeViewInLayout(mView);
316             View child = getErrorView();
317             prepareView(child);
318             addViewInLayout(child, 0, child.getLayoutParams());
319             measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
320                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
321             child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
322                     child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
323             mView = child;
324             mViewMode = VIEW_MODE_ERROR;
325         }
326     }
327 
328     /**
329      * Provide guidance about the size of this widget to the AppWidgetManager. The widths and
330      * heights should correspond to the full area the AppWidgetHostView is given. Padding added by
331      * the framework will be accounted for automatically. This information gets embedded into the
332      * AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of
333      * sizes is explicitly set to an empty list.
334      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
335      *
336      * @param newOptions The bundle of options, in addition to the size information,
337      *          can be null.
338      * @param minWidth The minimum width in dips that the widget will be displayed at.
339      * @param minHeight The maximum height in dips that the widget will be displayed at.
340      * @param maxWidth The maximum width in dips that the widget will be displayed at.
341      * @param maxHeight The maximum height in dips that the widget will be displayed at.
342      * @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead.
343      */
344     @Deprecated
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)345     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
346             int maxHeight) {
347         updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
348     }
349 
350     /**
351      * Provide guidance about the size of this widget to the AppWidgetManager. The sizes should
352      * correspond to the full area the AppWidgetHostView is given. Padding added by the framework
353      * will be accounted for automatically.
354      *
355      * This method will update the option bundle with the list of sizes and the min/max bounds for
356      * width and height.
357      *
358      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
359      *
360      * @param newOptions The bundle of options, in addition to the size information.
361      * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider
362      *              again. Typically, this will be size of the widget in landscape and portrait.
363      *              On some foldables, this might include the size on the outer and inner screens.
364      */
updateAppWidgetSize(@onNull Bundle newOptions, @NonNull List<SizeF> sizes)365     public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<SizeF> sizes) {
366         AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
367 
368         Rect padding = getDefaultPadding();
369         float density = getResources().getDisplayMetrics().density;
370 
371         float xPaddingDips = (padding.left + padding.right) / density;
372         float yPaddingDips = (padding.top + padding.bottom) / density;
373 
374         ArrayList<SizeF> paddedSizes = new ArrayList<>(sizes.size());
375         float minWidth = Float.MAX_VALUE;
376         float maxWidth = 0;
377         float minHeight = Float.MAX_VALUE;
378         float maxHeight = 0;
379         for (int i = 0; i < sizes.size(); i++) {
380             SizeF size = sizes.get(i);
381             SizeF paddedSize = new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips),
382                     Math.max(0.f, size.getHeight() - yPaddingDips));
383             paddedSizes.add(paddedSize);
384             minWidth = Math.min(minWidth, paddedSize.getWidth());
385             maxWidth = Math.max(maxWidth, paddedSize.getWidth());
386             minHeight = Math.min(minHeight, paddedSize.getHeight());
387             maxHeight = Math.max(maxHeight, paddedSize.getHeight());
388         }
389         if (paddedSizes.equals(
390                 widgetManager.getAppWidgetOptions(mAppWidgetId).<SizeF>getParcelableArrayList(
391                         AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
392             return;
393         }
394         Bundle options = newOptions.deepCopy();
395         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth);
396         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight);
397         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth);
398         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight);
399         options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
400         updateAppWidgetOptions(options);
401     }
402 
403     /**
404      * @hide
405      */
406     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)407     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
408             int maxHeight, boolean ignorePadding) {
409         if (newOptions == null) {
410             newOptions = new Bundle();
411         }
412 
413         Rect padding = getDefaultPadding();
414         float density = getResources().getDisplayMetrics().density;
415 
416         int xPaddingDips = (int) ((padding.left + padding.right) / density);
417         int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
418 
419         int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips);
420         int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips);
421         int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips);
422         int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips);
423 
424         AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
425 
426         // We get the old options to see if the sizes have changed
427         Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId);
428         boolean needsUpdate = false;
429         if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) ||
430                 newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) ||
431                 newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) ||
432                 newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) {
433             needsUpdate = true;
434         }
435 
436         if (needsUpdate) {
437             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth);
438             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
439             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
440             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
441             newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
442                     new ArrayList<PointF>());
443             updateAppWidgetOptions(newOptions);
444         }
445     }
446 
447     /**
448      * Specify some extra information for the widget provider. Causes a callback to the
449      * AppWidgetProvider.
450      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
451      *
452      * @param options The bundle of options information.
453      */
updateAppWidgetOptions(Bundle options)454     public void updateAppWidgetOptions(Bundle options) {
455         AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
456     }
457 
458     /** {@inheritDoc} */
459     @Override
generateLayoutParams(AttributeSet attrs)460     public LayoutParams generateLayoutParams(AttributeSet attrs) {
461         // We're being asked to inflate parameters, probably by a LayoutInflater
462         // in a remote Context. To help resolve any remote references, we
463         // inflate through our last mRemoteContext when it exists.
464         final Context context = mRemoteContext != null ? mRemoteContext : mContext;
465         return new FrameLayout.LayoutParams(context, attrs);
466     }
467 
468     /**
469      * Sets an executor which can be used for asynchronously inflating. CPU intensive tasks like
470      * view inflation or loading images will be performed on the executor. The updates will still
471      * be applied on the UI thread.
472      *
473      * @param executor the executor to use or null.
474      */
setExecutor(Executor executor)475     public void setExecutor(Executor executor) {
476         if (mLastExecutionSignal != null) {
477             mLastExecutionSignal.cancel();
478             mLastExecutionSignal = null;
479         }
480 
481         mAsyncExecutor = executor;
482     }
483 
484     /**
485      * Sets whether the widget is being displayed on a light/white background and use an
486      * alternate UI if available.
487      * @see RemoteViews#setLightBackgroundLayoutId(int)
488      */
setOnLightBackground(boolean onLightBackground)489     public void setOnLightBackground(boolean onLightBackground) {
490         mOnLightBackground = onLightBackground;
491     }
492 
493     /**
494      * Update the AppWidgetProviderInfo for this view, and reset it to the
495      * initial layout.
496      */
resetAppWidget(AppWidgetProviderInfo info)497     void resetAppWidget(AppWidgetProviderInfo info) {
498         setAppWidget(mAppWidgetId, info);
499         mViewMode = VIEW_MODE_NOINIT;
500         updateAppWidget(null);
501     }
502 
503     /**
504      * Process a set of {@link RemoteViews} coming in as an update from the
505      * AppWidget provider. Will animate into these new views as needed
506      */
updateAppWidget(RemoteViews remoteViews)507     public void updateAppWidget(RemoteViews remoteViews) {
508         mLastInflatedRemoteViews = remoteViews;
509         applyRemoteViews(remoteViews, true);
510     }
511 
512     /**
513      * Reapply the last inflated remote views, or the default view is none was inflated.
514      */
reapplyLastRemoteViews()515     private void reapplyLastRemoteViews() {
516         SparseArray<Parcelable> savedState = new SparseArray<>();
517         saveHierarchyState(savedState);
518         applyRemoteViews(mLastInflatedRemoteViews, true);
519         restoreHierarchyState(savedState);
520     }
521 
522     /**
523      * @hide
524      */
applyRemoteViews(@ullable RemoteViews remoteViews, boolean useAsyncIfPossible)525     protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) {
526         boolean recycled = false;
527         View content = null;
528         Exception exception = null;
529 
530         // Block state restore until the end of the apply.
531         mLastInflatedRemoteViewsId = -1;
532 
533         if (mLastExecutionSignal != null) {
534             mLastExecutionSignal.cancel();
535             mLastExecutionSignal = null;
536         }
537 
538         if (remoteViews == null) {
539             if (mViewMode == VIEW_MODE_DEFAULT) {
540                 // We've already done this -- nothing to do.
541                 return;
542             }
543             content = getDefaultView();
544             mViewMode = VIEW_MODE_DEFAULT;
545         } else {
546             // Select the remote view we are actually going to apply.
547             RemoteViews rvToApply = remoteViews.getRemoteViewsToApply(mContext, mCurrentSize);
548             if (mOnLightBackground) {
549                 rvToApply = rvToApply.getDarkTextViews();
550             }
551 
552             if (mAsyncExecutor != null && useAsyncIfPossible) {
553                 inflateAsync(rvToApply);
554                 return;
555             }
556             // Prepare a local reference to the remote Context so we're ready to
557             // inflate any requested LayoutParams.
558             mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
559 
560             if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) {
561                 try {
562                     rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
563                             mColorResources);
564                     content = mView;
565                     mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
566                     recycled = true;
567                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
568                 } catch (RuntimeException e) {
569                     exception = e;
570                 }
571             }
572 
573             // Try normal RemoteView inflation
574             if (content == null) {
575                 try {
576                     content = rvToApply.apply(mContext, this, mInteractionHandler,
577                             mCurrentSize, mColorResources);
578                     mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
579                     if (LOGD) Log.d(TAG, "had to inflate new layout");
580                 } catch (RuntimeException e) {
581                     exception = e;
582                 }
583             }
584 
585             mViewMode = VIEW_MODE_CONTENT;
586         }
587 
588         applyContent(content, recycled, exception);
589     }
590 
applyContent(View content, boolean recycled, Exception exception)591     private void applyContent(View content, boolean recycled, Exception exception) {
592         mColorMappingChanged = false;
593         if (content == null) {
594             if (mViewMode == VIEW_MODE_ERROR) {
595                 // We've already done this -- nothing to do.
596                 return ;
597             }
598             if (exception != null) {
599                 Log.w(TAG, "Error inflating RemoteViews", exception);
600             }
601             content = getErrorView();
602             mViewMode = VIEW_MODE_ERROR;
603         }
604 
605         if (!recycled) {
606             prepareView(content);
607             addView(content);
608         }
609 
610         if (mView != content) {
611             removeView(mView);
612             mView = content;
613         }
614     }
615 
inflateAsync(@onNull RemoteViews remoteViews)616     private void inflateAsync(@NonNull RemoteViews remoteViews) {
617         // Prepare a local reference to the remote Context so we're ready to
618         // inflate any requested LayoutParams.
619         mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
620         int layoutId = remoteViews.getLayoutId();
621 
622         if (mLastExecutionSignal != null) {
623             mLastExecutionSignal.cancel();
624         }
625 
626         // If our stale view has been prepared to match active, and the new
627         // layout matches, try recycling it
628         if (!mColorMappingChanged && remoteViews.canRecycleView(mView)) {
629             try {
630                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
631                         mView,
632                         mAsyncExecutor,
633                         new ViewApplyListener(remoteViews, layoutId, true),
634                         mInteractionHandler,
635                         mCurrentSize,
636                         mColorResources);
637             } catch (Exception e) {
638                 // Reapply failed. Try apply
639             }
640         }
641         if (mLastExecutionSignal == null) {
642             mLastExecutionSignal = remoteViews.applyAsync(mContext,
643                     this,
644                     mAsyncExecutor,
645                     new ViewApplyListener(remoteViews, layoutId, false),
646                     mInteractionHandler,
647                     mCurrentSize,
648                     mColorResources);
649         }
650     }
651 
652     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
653         private final RemoteViews mViews;
654         private final boolean mIsReapply;
655         private final int mLayoutId;
656 
ViewApplyListener( RemoteViews views, int layoutId, boolean isReapply)657         ViewApplyListener(
658                 RemoteViews views,
659                 int layoutId,
660                 boolean isReapply) {
661             mViews = views;
662             mLayoutId = layoutId;
663             mIsReapply = isReapply;
664         }
665 
666         @Override
onViewApplied(View v)667         public void onViewApplied(View v) {
668             mViewMode = VIEW_MODE_CONTENT;
669 
670             applyContent(v, mIsReapply, null);
671 
672             mLastInflatedRemoteViewsId = mViews.computeUniqueId(mLastInflatedRemoteViews);
673             restoreInstanceState();
674             mLastExecutionSignal = null;
675         }
676 
677         @Override
onError(Exception e)678         public void onError(Exception e) {
679             if (mIsReapply) {
680                 // Try a fresh replay
681                 mLastExecutionSignal = mViews.applyAsync(mContext,
682                         AppWidgetHostView.this,
683                         mAsyncExecutor,
684                         new ViewApplyListener(mViews, mLayoutId, false),
685                         mInteractionHandler,
686                         mCurrentSize);
687             } else {
688                 applyContent(null, false, e);
689             }
690             mLastExecutionSignal = null;
691         }
692     }
693 
694     /**
695      * Process data-changed notifications for the specified view in the specified
696      * set of {@link RemoteViews} views.
697      */
viewDataChanged(int viewId)698     void viewDataChanged(int viewId) {
699         View v = findViewById(viewId);
700         if ((v != null) && (v instanceof AdapterView<?>)) {
701             AdapterView<?> adapterView = (AdapterView<?>) v;
702             Adapter adapter = adapterView.getAdapter();
703             if (adapter instanceof BaseAdapter) {
704                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
705                 baseAdapter.notifyDataSetChanged();
706             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
707                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
708                 // connected to its associated service, and hence the adapter hasn't been set.
709                 // In this case, we need to defer the notify call until it has been set.
710                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
711             }
712         }
713     }
714 
715     /**
716      * Build a {@link Context} cloned into another package name, usually for the
717      * purposes of reading remote resources.
718      * @hide
719      */
getRemoteContextEnsuringCorrectCachedApkPath()720     protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
721         try {
722             ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo;
723             LoadedApk.checkAndUpdateApkPaths(expectedAppInfo);
724             // Return if cloned successfully, otherwise default
725             Context newContext = mContext.createApplicationContext(
726                     mInfo.providerInfo.applicationInfo,
727                     Context.CONTEXT_RESTRICTED);
728             if (mColorResources != null) {
729                 mColorResources.apply(newContext);
730             }
731             return newContext;
732         } catch (NameNotFoundException e) {
733             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
734             return mContext;
735         } catch (NullPointerException e) {
736             Log.e(TAG, "Error trying to create the remote context.", e);
737             return mContext;
738         }
739     }
740 
741     /**
742      * Prepare the given view to be shown. This might include adjusting
743      * {@link FrameLayout.LayoutParams} before inserting.
744      */
prepareView(View view)745     protected void prepareView(View view) {
746         // Take requested dimensions from child, but apply default gravity.
747         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
748         if (requested == null) {
749             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
750                     LayoutParams.MATCH_PARENT);
751         }
752 
753         requested.gravity = Gravity.CENTER;
754         view.setLayoutParams(requested);
755     }
756 
757     /**
758      * Inflate and return the default layout requested by AppWidget provider.
759      */
getDefaultView()760     protected View getDefaultView() {
761         if (LOGD) {
762             Log.d(TAG, "getDefaultView");
763         }
764         View defaultView = null;
765         Exception exception = null;
766 
767         try {
768             if (mInfo != null) {
769                 Context theirContext = getRemoteContextEnsuringCorrectCachedApkPath();
770                 mRemoteContext = theirContext;
771                 LayoutInflater inflater = (LayoutInflater)
772                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
773                 inflater = inflater.cloneInContext(theirContext);
774                 inflater.setFilter(INFLATER_FILTER);
775                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
776                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
777 
778                 int layoutId = mInfo.initialLayout;
779                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
780                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
781                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
782                         int kgLayoutId = mInfo.initialKeyguardLayout;
783                         // If a default keyguard layout is not specified, use the standard
784                         // default layout.
785                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
786                     }
787                 }
788                 defaultView = inflater.inflate(layoutId, this, false);
789                 if (!(defaultView instanceof AdapterView)) {
790                     // AdapterView does not support onClickListener
791                     defaultView.setOnClickListener(this::onDefaultViewClicked);
792                 }
793             } else {
794                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
795             }
796         } catch (RuntimeException e) {
797             exception = e;
798         }
799 
800         if (exception != null) {
801             Log.w(TAG, "Error inflating AppWidget " + mInfo, exception);
802         }
803 
804         if (defaultView == null) {
805             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
806             defaultView = getErrorView();
807         }
808 
809         return defaultView;
810     }
811 
onDefaultViewClicked(View view)812     private void onDefaultViewClicked(View view) {
813         if (mInfo != null) {
814             LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
815             List<LauncherActivityInfo> activities = launcherApps.getActivityList(
816                     mInfo.provider.getPackageName(), mInfo.getProfile());
817             if (!activities.isEmpty()) {
818                 LauncherActivityInfo ai = activities.get(0);
819                 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(),
820                         RemoteViews.getSourceBounds(view), null);
821             }
822         }
823     }
824 
825     /**
826      * Inflate and return a view that represents an error state.
827      */
getErrorView()828     protected View getErrorView() {
829         TextView tv = new TextView(mContext);
830         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
831         // TODO: get this color from somewhere.
832         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
833         return tv;
834     }
835 
836     /** @hide */
837     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)838     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
839         super.onInitializeAccessibilityNodeInfoInternal(info);
840         info.setClassName(AppWidgetHostView.class.getName());
841     }
842 
843     /** @hide */
createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)844     public ActivityOptions createSharedElementActivityOptions(
845             int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) {
846         Context parentContext = getContext();
847         while ((parentContext instanceof ContextWrapper)
848                 && !(parentContext instanceof Activity)) {
849             parentContext = ((ContextWrapper) parentContext).getBaseContext();
850         }
851         if (!(parentContext instanceof Activity)) {
852             return null;
853         }
854 
855         List<Pair<View, String>> sharedElements = new ArrayList<>();
856         Bundle extras = new Bundle();
857 
858         for (int i = 0; i < sharedViewIds.length; i++) {
859             View view = findViewById(sharedViewIds[i]);
860             if (view != null) {
861                 sharedElements.add(Pair.create(view, sharedViewNames[i]));
862 
863                 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view));
864             }
865         }
866 
867         if (!sharedElements.isEmpty()) {
868             fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras);
869             final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
870                     (Activity) parentContext,
871                     sharedElements.toArray(new Pair[sharedElements.size()]));
872             opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
873             return opts;
874         }
875         return null;
876     }
877 
getHandler(InteractionHandler handler)878     private InteractionHandler getHandler(InteractionHandler handler) {
879         return (view, pendingIntent, response) -> {
880             AppWidgetManager.getInstance(mContext).noteAppWidgetTapped(mAppWidgetId);
881             if (handler != null) {
882                 return handler.onInteraction(view, pendingIntent, response);
883             } else {
884                 return RemoteViews.startPendingIntent(view, pendingIntent,
885                         response.getLaunchOptions(view));
886             }
887         };
888     }
889 
890     /**
891      * Set the dynamically overloaded color resources.
892      *
893      * {@code colorMapping} maps a predefined set of color resources to their ARGB
894      * representation. Any entry not in the predefined set of colors will be ignored.
895      *
896      * Calling this method will trigger a full re-inflation of the App Widget.
897      *
898      * The color resources that can be overloaded are the ones whose name is prefixed with
899      * {@code system_neutral} or {@code system_accent}, for example
900      * {@link android.R.color#system_neutral1_500}.
901      */
setColorResources(@onNull SparseIntArray colorMapping)902     public void setColorResources(@NonNull SparseIntArray colorMapping) {
903         if (mColorMapping != null && isSameColorMapping(mColorMapping, colorMapping)) {
904             return;
905         }
906         mColorMapping = colorMapping.clone();
907         mColorResources = RemoteViews.ColorResources.create(mContext, mColorMapping);
908         mColorMappingChanged = true;
909         mViewMode = VIEW_MODE_NOINIT;
910         reapplyLastRemoteViews();
911     }
912 
913     /** Check if, in the current context, the two color mappings are equivalent. */
isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors)914     private boolean isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors) {
915         if (oldColors.size() != newColors.size()) {
916             return false;
917         }
918         for (int i = 0; i < oldColors.size(); i++) {
919             if (oldColors.keyAt(i) != newColors.keyAt(i)
920                     || oldColors.valueAt(i) != newColors.valueAt(i)) {
921                 return false;
922             }
923         }
924         return true;
925     }
926 
927     /**
928      * Reset the dynamically overloaded resources, reverting to the default values for
929      * all the colors.
930      *
931      * If colors were defined before, calling this method will trigger a full re-inflation of the
932      * App Widget.
933      */
resetColorResources()934     public void resetColorResources() {
935         if (mColorResources != null) {
936             mColorResources = null;
937             mColorMapping = null;
938             mColorMappingChanged = true;
939             mViewMode = VIEW_MODE_NOINIT;
940             reapplyLastRemoteViews();
941         }
942     }
943 }
944