• 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.app.Activity;
20 import android.app.ActivityOptions;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.ContextWrapper;
25 import android.content.Intent;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.LauncherActivityInfo;
28 import android.content.pm.LauncherApps;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.res.Resources;
31 import android.graphics.Color;
32 import android.graphics.Rect;
33 import android.os.Bundle;
34 import android.os.CancellationSignal;
35 import android.os.Parcelable;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.util.Pair;
39 import android.util.SparseArray;
40 import android.view.Gravity;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.accessibility.AccessibilityNodeInfo;
44 import android.widget.Adapter;
45 import android.widget.AdapterView;
46 import android.widget.BaseAdapter;
47 import android.widget.FrameLayout;
48 import android.widget.RemoteViews;
49 import android.widget.RemoteViews.OnClickHandler;
50 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
51 import android.widget.TextView;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.concurrent.Executor;
56 
57 /**
58  * Provides the glue to show AppWidget views. This class offers automatic animation
59  * between updates, and will try recycling old views for each incoming
60  * {@link RemoteViews}.
61  */
62 public class AppWidgetHostView extends FrameLayout {
63 
64     static final String TAG = "AppWidgetHostView";
65     private static final String KEY_JAILED_ARRAY = "jail";
66 
67     static final boolean LOGD = false;
68 
69     static final int VIEW_MODE_NOINIT = 0;
70     static final int VIEW_MODE_CONTENT = 1;
71     static final int VIEW_MODE_ERROR = 2;
72     static final int VIEW_MODE_DEFAULT = 3;
73 
74     // When we're inflating the initialLayout for a AppWidget, we only allow
75     // views that are allowed in RemoteViews.
76     private static final LayoutInflater.Filter INFLATER_FILTER =
77             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
78 
79     Context mContext;
80     Context mRemoteContext;
81 
82     @UnsupportedAppUsage
83     int mAppWidgetId;
84     @UnsupportedAppUsage
85     AppWidgetProviderInfo mInfo;
86     View mView;
87     int mViewMode = VIEW_MODE_NOINIT;
88     int mLayoutId = -1;
89     private OnClickHandler mOnClickHandler;
90     private boolean mOnLightBackground;
91 
92     private Executor mAsyncExecutor;
93     private CancellationSignal mLastExecutionSignal;
94 
95     /**
96      * Create a host view.  Uses default fade animations.
97      */
AppWidgetHostView(Context context)98     public AppWidgetHostView(Context context) {
99         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
100     }
101 
102     /**
103      * @hide
104      */
AppWidgetHostView(Context context, OnClickHandler handler)105     public AppWidgetHostView(Context context, OnClickHandler handler) {
106         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
107         mOnClickHandler = getHandler(handler);
108     }
109 
110     /**
111      * Create a host view. Uses specified animations when pushing
112      * {@link #updateAppWidget(RemoteViews)}.
113      *
114      * @param animationIn Resource ID of in animation to use
115      * @param animationOut Resource ID of out animation to use
116      */
117     @SuppressWarnings({"UnusedDeclaration"})
AppWidgetHostView(Context context, int animationIn, int animationOut)118     public AppWidgetHostView(Context context, int animationIn, int animationOut) {
119         super(context);
120         mContext = context;
121         // We want to segregate the view ids within AppWidgets to prevent
122         // problems when those ids collide with view ids in the AppWidgetHost.
123         setIsRootNamespace(true);
124     }
125 
126     /**
127      * Pass the given handler to RemoteViews when updating this widget. Unless this
128      * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
129      * should be made.
130      * @param handler
131      * @hide
132      */
setOnClickHandler(OnClickHandler handler)133     public void setOnClickHandler(OnClickHandler handler) {
134         mOnClickHandler = getHandler(handler);
135     }
136 
137     /**
138      * Set the AppWidget that will be displayed by this view. This method also adds default padding
139      * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
140      * and can be overridden in order to add custom padding.
141      */
setAppWidget(int appWidgetId, AppWidgetProviderInfo info)142     public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
143         mAppWidgetId = appWidgetId;
144         mInfo = info;
145 
146         // We add padding to the AppWidgetHostView if necessary
147         Rect padding = getDefaultPadding();
148         setPadding(padding.left, padding.top, padding.right, padding.bottom);
149 
150         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
151         // a widget, eg. for some widgets in safe mode.
152         if (info != null) {
153             String description = info.loadLabel(getContext().getPackageManager());
154             if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
155                 description = Resources.getSystem().getString(
156                         com.android.internal.R.string.suspended_widget_accessibility, description);
157             }
158             setContentDescription(description);
159         }
160     }
161 
162     /**
163      * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
164      * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
165      * that widget developers do not add extra padding to their widgets. This will help
166      * achieve consistency among widgets.
167      *
168      * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in
169      * order for the AppWidgetHost to account for the automatic padding when computing the number
170      * of cells to allocate to a particular widget.
171      *
172      * @param context the current context
173      * @param component the component name of the widget
174      * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
175      *                returned
176      * @return default padding for this widget, in pixels
177      */
getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)178     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
179             Rect padding) {
180         return getDefaultPaddingForWidget(context, padding);
181     }
182 
getDefaultPaddingForWidget(Context context, Rect padding)183     private static Rect getDefaultPaddingForWidget(Context context, Rect padding) {
184         if (padding == null) {
185             padding = new Rect(0, 0, 0, 0);
186         } else {
187             padding.set(0, 0, 0, 0);
188         }
189         Resources r = context.getResources();
190         padding.left = r.getDimensionPixelSize(
191                 com.android.internal.R.dimen.default_app_widget_padding_left);
192         padding.right = r.getDimensionPixelSize(
193                 com.android.internal.R.dimen.default_app_widget_padding_right);
194         padding.top = r.getDimensionPixelSize(
195                 com.android.internal.R.dimen.default_app_widget_padding_top);
196         padding.bottom = r.getDimensionPixelSize(
197                 com.android.internal.R.dimen.default_app_widget_padding_bottom);
198         return padding;
199     }
200 
getDefaultPadding()201     private Rect getDefaultPadding() {
202         return getDefaultPaddingForWidget(mContext, null);
203     }
204 
getAppWidgetId()205     public int getAppWidgetId() {
206         return mAppWidgetId;
207     }
208 
getAppWidgetInfo()209     public AppWidgetProviderInfo getAppWidgetInfo() {
210         return mInfo;
211     }
212 
213     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)214     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
215         final SparseArray<Parcelable> jail = new SparseArray<>();
216         super.dispatchSaveInstanceState(jail);
217 
218         Bundle bundle = new Bundle();
219         bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
220         container.put(generateId(), bundle);
221     }
222 
generateId()223     private int generateId() {
224         final int id = getId();
225         return id == View.NO_ID ? mAppWidgetId : id;
226     }
227 
228     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)229     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
230         final Parcelable parcelable = container.get(generateId());
231 
232         SparseArray<Parcelable> jail = null;
233         if (parcelable instanceof Bundle) {
234             jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY);
235         }
236 
237         if (jail == null) jail = new SparseArray<>();
238 
239         try  {
240             super.dispatchRestoreInstanceState(jail);
241         } catch (Exception e) {
242             Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
243                     + (mInfo == null ? "null" : mInfo.provider), e);
244         }
245     }
246 
247     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)248     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
249         try {
250             super.onLayout(changed, left, top, right, bottom);
251         } catch (final RuntimeException e) {
252             Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e);
253             removeViewInLayout(mView);
254             View child = getErrorView();
255             prepareView(child);
256             addViewInLayout(child, 0, child.getLayoutParams());
257             measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
258                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
259             child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
260                     child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
261             mView = child;
262             mViewMode = VIEW_MODE_ERROR;
263         }
264     }
265 
266     /**
267      * Provide guidance about the size of this widget to the AppWidgetManager. The widths and
268      * heights should correspond to the full area the AppWidgetHostView is given. Padding added by
269      * the framework will be accounted for automatically. This information gets embedded into the
270      * AppWidget options and causes a callback to the AppWidgetProvider.
271      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
272      *
273      * @param newOptions The bundle of options, in addition to the size information,
274      *          can be null.
275      * @param minWidth The minimum width in dips that the widget will be displayed at.
276      * @param minHeight The maximum height in dips that the widget will be displayed at.
277      * @param maxWidth The maximum width in dips that the widget will be displayed at.
278      * @param maxHeight The maximum height in dips that the widget will be displayed at.
279      *
280      */
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)281     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
282             int maxHeight) {
283         updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
284     }
285 
286     /**
287      * @hide
288      */
289     @UnsupportedAppUsage
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)290     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
291             int maxHeight, boolean ignorePadding) {
292         if (newOptions == null) {
293             newOptions = new Bundle();
294         }
295 
296         Rect padding = getDefaultPadding();
297         float density = getResources().getDisplayMetrics().density;
298 
299         int xPaddingDips = (int) ((padding.left + padding.right) / density);
300         int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
301 
302         int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips);
303         int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips);
304         int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips);
305         int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips);
306 
307         AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
308 
309         // We get the old options to see if the sizes have changed
310         Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId);
311         boolean needsUpdate = false;
312         if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) ||
313                 newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) ||
314                 newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) ||
315                 newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) {
316             needsUpdate = true;
317         }
318 
319         if (needsUpdate) {
320             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth);
321             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
322             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
323             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
324             updateAppWidgetOptions(newOptions);
325         }
326     }
327 
328     /**
329      * Specify some extra information for the widget provider. Causes a callback to the
330      * AppWidgetProvider.
331      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
332      *
333      * @param options The bundle of options information.
334      */
updateAppWidgetOptions(Bundle options)335     public void updateAppWidgetOptions(Bundle options) {
336         AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
337     }
338 
339     /** {@inheritDoc} */
340     @Override
generateLayoutParams(AttributeSet attrs)341     public LayoutParams generateLayoutParams(AttributeSet attrs) {
342         // We're being asked to inflate parameters, probably by a LayoutInflater
343         // in a remote Context. To help resolve any remote references, we
344         // inflate through our last mRemoteContext when it exists.
345         final Context context = mRemoteContext != null ? mRemoteContext : mContext;
346         return new FrameLayout.LayoutParams(context, attrs);
347     }
348 
349     /**
350      * Sets an executor which can be used for asynchronously inflating. CPU intensive tasks like
351      * view inflation or loading images will be performed on the executor. The updates will still
352      * be applied on the UI thread.
353      *
354      * @param executor the executor to use or null.
355      */
setExecutor(Executor executor)356     public void setExecutor(Executor executor) {
357         if (mLastExecutionSignal != null) {
358             mLastExecutionSignal.cancel();
359             mLastExecutionSignal = null;
360         }
361 
362         mAsyncExecutor = executor;
363     }
364 
365     /**
366      * Sets whether the widget is being displayed on a light/white background and use an
367      * alternate UI if available.
368      * @see RemoteViews#setLightBackgroundLayoutId(int)
369      */
setOnLightBackground(boolean onLightBackground)370     public void setOnLightBackground(boolean onLightBackground) {
371         mOnLightBackground = onLightBackground;
372     }
373 
374     /**
375      * Update the AppWidgetProviderInfo for this view, and reset it to the
376      * initial layout.
377      */
resetAppWidget(AppWidgetProviderInfo info)378     void resetAppWidget(AppWidgetProviderInfo info) {
379         setAppWidget(mAppWidgetId, info);
380         mViewMode = VIEW_MODE_NOINIT;
381         updateAppWidget(null);
382     }
383 
384     /**
385      * Process a set of {@link RemoteViews} coming in as an update from the
386      * AppWidget provider. Will animate into these new views as needed
387      */
updateAppWidget(RemoteViews remoteViews)388     public void updateAppWidget(RemoteViews remoteViews) {
389         applyRemoteViews(remoteViews, true);
390     }
391 
392     /**
393      * @hide
394      */
applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible)395     protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
396         boolean recycled = false;
397         View content = null;
398         Exception exception = null;
399 
400         if (mLastExecutionSignal != null) {
401             mLastExecutionSignal.cancel();
402             mLastExecutionSignal = null;
403         }
404 
405         if (remoteViews == null) {
406             if (mViewMode == VIEW_MODE_DEFAULT) {
407                 // We've already done this -- nothing to do.
408                 return;
409             }
410             content = getDefaultView();
411             mLayoutId = -1;
412             mViewMode = VIEW_MODE_DEFAULT;
413         } else {
414             if (mOnLightBackground) {
415                 remoteViews = remoteViews.getDarkTextViews();
416             }
417 
418             if (mAsyncExecutor != null && useAsyncIfPossible) {
419                 inflateAsync(remoteViews);
420                 return;
421             }
422             // Prepare a local reference to the remote Context so we're ready to
423             // inflate any requested LayoutParams.
424             mRemoteContext = getRemoteContext();
425             int layoutId = remoteViews.getLayoutId();
426             // If our stale view has been prepared to match active, and the new
427             // layout matches, try recycling it
428             if (content == null && layoutId == mLayoutId) {
429                 try {
430                     remoteViews.reapply(mContext, mView, mOnClickHandler);
431                     content = mView;
432                     recycled = true;
433                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
434                 } catch (RuntimeException e) {
435                     exception = e;
436                 }
437             }
438 
439             // Try normal RemoteView inflation
440             if (content == null) {
441                 try {
442                     content = remoteViews.apply(mContext, this, mOnClickHandler);
443                     if (LOGD) Log.d(TAG, "had to inflate new layout");
444                 } catch (RuntimeException e) {
445                     exception = e;
446                 }
447             }
448 
449             mLayoutId = layoutId;
450             mViewMode = VIEW_MODE_CONTENT;
451         }
452 
453         applyContent(content, recycled, exception);
454     }
455 
applyContent(View content, boolean recycled, Exception exception)456     private void applyContent(View content, boolean recycled, Exception exception) {
457         if (content == null) {
458             if (mViewMode == VIEW_MODE_ERROR) {
459                 // We've already done this -- nothing to do.
460                 return ;
461             }
462             if (exception != null) {
463                 Log.w(TAG, "Error inflating RemoteViews : " + exception.toString());
464             }
465             content = getErrorView();
466             mViewMode = VIEW_MODE_ERROR;
467         }
468 
469         if (!recycled) {
470             prepareView(content);
471             addView(content);
472         }
473 
474         if (mView != content) {
475             removeView(mView);
476             mView = content;
477         }
478     }
479 
inflateAsync(RemoteViews remoteViews)480     private void inflateAsync(RemoteViews remoteViews) {
481         // Prepare a local reference to the remote Context so we're ready to
482         // inflate any requested LayoutParams.
483         mRemoteContext = getRemoteContext();
484         int layoutId = remoteViews.getLayoutId();
485 
486         // If our stale view has been prepared to match active, and the new
487         // layout matches, try recycling it
488         if (layoutId == mLayoutId && mView != null) {
489             try {
490                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
491                         mView,
492                         mAsyncExecutor,
493                         new ViewApplyListener(remoteViews, layoutId, true),
494                         mOnClickHandler);
495             } catch (Exception e) {
496                 // Reapply failed. Try apply
497             }
498         }
499         if (mLastExecutionSignal == null) {
500             mLastExecutionSignal = remoteViews.applyAsync(mContext,
501                     this,
502                     mAsyncExecutor,
503                     new ViewApplyListener(remoteViews, layoutId, false),
504                     mOnClickHandler);
505         }
506     }
507 
508     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
509         private final RemoteViews mViews;
510         private final boolean mIsReapply;
511         private final int mLayoutId;
512 
ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)513         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
514             mViews = views;
515             mLayoutId = layoutId;
516             mIsReapply = isReapply;
517         }
518 
519         @Override
onViewApplied(View v)520         public void onViewApplied(View v) {
521             AppWidgetHostView.this.mLayoutId = mLayoutId;
522             mViewMode = VIEW_MODE_CONTENT;
523 
524             applyContent(v, mIsReapply, null);
525         }
526 
527         @Override
onError(Exception e)528         public void onError(Exception e) {
529             if (mIsReapply) {
530                 // Try a fresh replay
531                 mLastExecutionSignal = mViews.applyAsync(mContext,
532                         AppWidgetHostView.this,
533                         mAsyncExecutor,
534                         new ViewApplyListener(mViews, mLayoutId, false),
535                         mOnClickHandler);
536             } else {
537                 applyContent(null, false, e);
538             }
539         }
540     }
541 
542     /**
543      * Process data-changed notifications for the specified view in the specified
544      * set of {@link RemoteViews} views.
545      */
viewDataChanged(int viewId)546     void viewDataChanged(int viewId) {
547         View v = findViewById(viewId);
548         if ((v != null) && (v instanceof AdapterView<?>)) {
549             AdapterView<?> adapterView = (AdapterView<?>) v;
550             Adapter adapter = adapterView.getAdapter();
551             if (adapter instanceof BaseAdapter) {
552                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
553                 baseAdapter.notifyDataSetChanged();
554             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
555                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
556                 // connected to its associated service, and hence the adapter hasn't been set.
557                 // In this case, we need to defer the notify call until it has been set.
558                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
559             }
560         }
561     }
562 
563     /**
564      * Build a {@link Context} cloned into another package name, usually for the
565      * purposes of reading remote resources.
566      * @hide
567      */
getRemoteContext()568     protected Context getRemoteContext() {
569         try {
570             // Return if cloned successfully, otherwise default
571             return mContext.createApplicationContext(
572                     mInfo.providerInfo.applicationInfo,
573                     Context.CONTEXT_RESTRICTED);
574         } catch (NameNotFoundException e) {
575             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
576             return mContext;
577         }
578     }
579 
580     /**
581      * Prepare the given view to be shown. This might include adjusting
582      * {@link FrameLayout.LayoutParams} before inserting.
583      */
prepareView(View view)584     protected void prepareView(View view) {
585         // Take requested dimensions from child, but apply default gravity.
586         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
587         if (requested == null) {
588             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
589                     LayoutParams.MATCH_PARENT);
590         }
591 
592         requested.gravity = Gravity.CENTER;
593         view.setLayoutParams(requested);
594     }
595 
596     /**
597      * Inflate and return the default layout requested by AppWidget provider.
598      */
getDefaultView()599     protected View getDefaultView() {
600         if (LOGD) {
601             Log.d(TAG, "getDefaultView");
602         }
603         View defaultView = null;
604         Exception exception = null;
605 
606         try {
607             if (mInfo != null) {
608                 Context theirContext = getRemoteContext();
609                 mRemoteContext = theirContext;
610                 LayoutInflater inflater = (LayoutInflater)
611                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
612                 inflater = inflater.cloneInContext(theirContext);
613                 inflater.setFilter(INFLATER_FILTER);
614                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
615                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
616 
617                 int layoutId = mInfo.initialLayout;
618                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
619                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
620                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
621                         int kgLayoutId = mInfo.initialKeyguardLayout;
622                         // If a default keyguard layout is not specified, use the standard
623                         // default layout.
624                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
625                     }
626                 }
627                 defaultView = inflater.inflate(layoutId, this, false);
628                 if (!(defaultView instanceof AdapterView)) {
629                     // AdapterView does not support onClickListener
630                     defaultView.setOnClickListener(this::onDefaultViewClicked);
631                 }
632             } else {
633                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
634             }
635         } catch (RuntimeException e) {
636             exception = e;
637         }
638 
639         if (exception != null) {
640             Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
641         }
642 
643         if (defaultView == null) {
644             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
645             defaultView = getErrorView();
646         }
647 
648         return defaultView;
649     }
650 
onDefaultViewClicked(View view)651     private void onDefaultViewClicked(View view) {
652         if (mInfo != null) {
653             LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
654             List<LauncherActivityInfo> activities = launcherApps.getActivityList(
655                     mInfo.provider.getPackageName(), mInfo.getProfile());
656             if (!activities.isEmpty()) {
657                 LauncherActivityInfo ai = activities.get(0);
658                 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(),
659                         RemoteViews.getSourceBounds(view), null);
660             }
661         }
662     }
663 
664     /**
665      * Inflate and return a view that represents an error state.
666      */
getErrorView()667     protected View getErrorView() {
668         TextView tv = new TextView(mContext);
669         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
670         // TODO: get this color from somewhere.
671         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
672         return tv;
673     }
674 
675     /** @hide */
676     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)677     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
678         super.onInitializeAccessibilityNodeInfoInternal(info);
679         info.setClassName(AppWidgetHostView.class.getName());
680     }
681 
682     /** @hide */
createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)683     public ActivityOptions createSharedElementActivityOptions(
684             int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) {
685         Context parentContext = getContext();
686         while ((parentContext instanceof ContextWrapper)
687                 && !(parentContext instanceof Activity)) {
688             parentContext = ((ContextWrapper) parentContext).getBaseContext();
689         }
690         if (!(parentContext instanceof Activity)) {
691             return null;
692         }
693 
694         List<Pair<View, String>> sharedElements = new ArrayList<>();
695         Bundle extras = new Bundle();
696 
697         for (int i = 0; i < sharedViewIds.length; i++) {
698             View view = findViewById(sharedViewIds[i]);
699             if (view != null) {
700                 sharedElements.add(Pair.create(view, sharedViewNames[i]));
701 
702                 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view));
703             }
704         }
705 
706         if (!sharedElements.isEmpty()) {
707             fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras);
708             final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
709                     (Activity) parentContext,
710                     sharedElements.toArray(new Pair[sharedElements.size()]));
711             opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
712             return opts;
713         }
714         return null;
715     }
716 
getHandler(OnClickHandler handler)717     private OnClickHandler getHandler(OnClickHandler handler) {
718         return (view, pendingIntent, response) -> {
719             AppWidgetManager.getInstance(mContext).noteAppWidgetTapped(mAppWidgetId);
720             if (handler != null) {
721                 return handler.onClickHandler(view, pendingIntent, response);
722             } else {
723                 return RemoteViews.startPendingIntent(view, pendingIntent,
724                         response.getLaunchOptions(view));
725             }
726         };
727     }
728 }
729