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